dolibarr 23.0.3
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 <bafbes@gmail.com>
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-2025 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-2025 MDW <mdeweerd@users.noreply.github.com>
21 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
22 * Copyright (C) 2025 Alexandre Janniaux <alexandre.janniaux@gmail.com>
23 * Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
24*
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <https://www.gnu.org/licenses/>.
37 */
38
45require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
46require_once DOL_DOCUMENT_ROOT.'/core/class/commontrigger.class.php';
47
53abstract class CommonObject
54{
55 use DolDeprecationHandler;
56 use CommonTrigger;
57
61 public $module;
62
66 public $db;
67
71 public $id;
72
76 public $entity;
77
82 public $error;
83
87 public $errorhidden;
88
92 public $errors = array();
93
97 public $warnings = array();
98
102 private $validateFieldsErrors = array();
103
107 public $element;
108
113 public $fk_element;
114
120 public $element_for_permission;
121
125 public $table_element;
126
130 public $table_rowid;
131
135 public $table_element_line = '';
136
141 public $ismultientitymanaged;
142
146 public $import_key;
147
151 public $array_options = array();
152
153
198 public $fields = array();
199
205 public $array_languages = null; // Value is array() when load already tried
206
210 public $contacts_ids;
211
215 public $contacts_ids_internal;
216
220 public $linked_objects;
221
225 public $linkedObjectsIds;
226
230 public $linkedObjects;
231
235 private $linkedObjectsFullLoaded = array();
236
240 public $oldcopy;
241
245 public $oldref;
246
250 protected $table_ref_field = '';
251
255 public $restrictiononfksoc = 0;
256
257
258 // The following vars are used by some objects only.
259 // We keep these properties in CommonObject in order to provide common methods using them.
260
264 public $context = array();
265
269 public $actionmsg;
273 public $actionmsg2;
274
278 public $canvas;
279
284 public $project;
285
290 public $fk_project;
291
297 public $fk_projet;
298
303 public $contact;
304
309 public $contact_id;
310
315 public $thirdparty;
316
321 public $user;
322
327 public $product;
328
333 public $origin_type;
334
339 public $origin_id;
340
345
351 public $origin;
352
361 private $expedition;
362
368 private $livraison;
369
375 private $commandeFournisseur;
376
377
381 public $ref;
382
386 public $ref_ext;
387
391 public $ref_previous;
392
396 public $ref_next;
397
401 public $newref;
402
409 public $statut;
410
419 public $status;
420
425 public $country;
426
431 public $country_id;
432
437 public $country_code;
438
443 public $state;
444
449 public $state_id;
450
455 public $state_code;
456
461 public $region_id;
462
467 public $region_code;
468
473 public $region;
474
475
480 public $barcode_type;
481
486 public $barcode_type_code;
487
492 public $barcode_type_label;
493
498 public $barcode_type_coder;
499
504 public $mode_reglement_id;
505
510 public $cond_reglement_id;
511
515 public $demand_reason_id;
516
521 public $transport_mode_id;
522
530 private $cond_reglement; // Private to call DolDeprecationHandler
534 protected $depr_cond_reglement; // Internal value for deprecation
535
541 public $fk_delivery_address;
542
547 public $shipping_method_id;
548
553 public $shipping_method;
554
555 // Multicurrency
559 public $fk_multicurrency;
560
565 public $multicurrency_code;
566
571 public $multicurrency_tx;
572
576 public $multicurrency_total_ht;
577
581 public $multicurrency_total_tva;
582
586 public $multicurrency_total_localtax1; // not in database
587
591 public $multicurrency_total_localtax2; // not in database
592
596 public $multicurrency_total_ttc;
597
598
603 public $model_pdf;
604
609 public $last_main_doc;
610
616 public $fk_bank;
617
622 public $fk_account;
623
628 public $note_public;
629
634 public $note_private;
635
641 public $note;
642
647 public $total_ht;
648
653 public $total_tva;
654
659 public $total_localtax1;
660
665 public $total_localtax2;
666
671 public $total_ttc;
672
673
677 public $lines;
678
682 public $actiontypecode;
683
688 public $comments = array();
689
693 public $name;
694
698 public $lastname;
699
703 public $firstname;
704
709 public $civility_id;
710
714 public $civility_code;
715
716 // Dates
720 public $date_creation;
721
725 public $date_validation;
726
730 public $date_modification;
731
737 public $tms;
738
744 private $date_update;
745
749 public $date_cloture;
750
754 public $user_creation_id;
755
759 public $user_validation_id;
760
764 public $user_closing_id;
765
769 public $user_modification_id;
770
775 public $fk_user_creat;
776
781 public $fk_user_modif;
782
783
787 public $next_prev_filter;
788
792 public $specimen = 0;
793
797 public $sendtoid;
798
799
804 public $alreadypaid;
805
810 public $totalpaid;
811
816 public $totalpaid_multicurrency;
817
818
822 public $labelStatus = array();
823
827 public $labelStatusShort = array();
828
832 public $tpl;
833
834
838 public $showphoto_on_popup;
839
843 public $nb = array();
844
848 public $nbphoto;
849
853 public $output;
854
858 public $extraparams = array();
859
863 protected $childtables = array();
864
870 protected $childtablesoncascade = array();
871
875 public $cond_reglement_supplier_id;
876
882 public $deposit_percent;
883
884
888 public $retained_warranty_fk_cond_reglement;
889
893 public $warehouse_id;
894
898 public $isextrafieldmanaged = 0;
899
900
901 // No constructor as it is an abstract class
902
908 protected function deprecatedProperties()
909 {
910 return array(
911 'alreadypaid' => 'totalpaid',
912 'cond_reglement' => 'depr_cond_reglement',
913 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
914 'commandeFournisseur' => 'origin_object',
915 'expedition' => 'origin_object',
916 'fk_project' => 'fk_project',
917 'livraison' => 'origin_object',
918 'projet' => 'project',
919 'statut' => 'status',
920 );
921 }
922
923
934 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
935 {
936 global $db, $conf;
937
938 $sql = "SELECT rowid";
939 $sql .= " FROM ".$db->prefix().$element;
940 $sql .= " WHERE entity IN (".getEntity($element).")";
941
942 if ($id > 0) {
943 $sql .= " AND rowid = ".((int) $id);
944 } elseif ($ref) {
945 $sql .= " AND ref = '".$db->escape($ref)."'";
946 } elseif ($ref_ext) {
947 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
948 } else {
949 $error = 'ErrorWrongParameters';
950 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
951 return -1;
952 }
953 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
954 $sql .= " AND entity = ".((int) $conf->entity);
955 }
956
957 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
958 $resql = $db->query($sql);
959 if ($resql) {
960 $num = $db->num_rows($resql);
961 if ($num > 0) {
962 return 1;
963 } else {
964 return 0;
965 }
966 }
967 return -1;
968 }
969
975 public function isEmpty()
976 {
977 return (empty($this->id));
978 }
979
987 {
988 if (!empty($object->error)) {
989 $this->error = $object->error;
990 }
991 if (!empty($object->errors)) {
992 $this->errors = array_merge($this->errors, $object->errors);
993 }
994 }
995
1003 public function getTooltipContentArray($params)
1004 {
1005 return [];
1006 }
1007
1015 public function getTooltipContent($params)
1016 {
1017 global $action, $extrafields, $langs, $hookmanager;
1018
1019 // If there is too much extrafields, we do not include them into tooltip
1020 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
1021
1022 $data = $this->getTooltipContentArray($params);
1023 $count = 0;
1024
1025 // Add extrafields
1026 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1027 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1028 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1029 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1030 continue;
1031 }
1032 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1033 $data['more_extrafields'] = '<br>...';
1034 break;
1035 }
1036 $enabled = 1;
1037 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1038 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1039 }
1040 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1041 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1042 }
1043 $perms = 1;
1044 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1045 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1046 }
1047 if (empty($enabled)) {
1048 continue; // 0 = Never visible field
1049 }
1050 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1051 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1052 }
1053 if (empty($perms)) {
1054 continue; // 0 = Not visible
1055 }
1056 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1057 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1058 }
1059 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1060 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1061 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1062 } else {
1063 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1064 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1065 $count++;
1066 }
1067 }
1068 $data['closedivextra'] = '</div>';
1069 }
1070
1071 $hookmanager->initHooks(array($this->element . 'dao'));
1072 $parameters = array(
1073 'tooltipcontentarray' => &$data,
1074 'params' => $params,
1075 );
1076 // Note that $action and $object may have been modified by some hooks
1077 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1078
1079 //var_dump($data);
1080 $label = implode($data);
1081
1082 return $label;
1083 }
1084
1085
1091 public function errorsToString()
1092 {
1093 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1094 }
1095
1096
1103 public function getFormatedCustomerRef($objref)
1104 {
1105 global $hookmanager;
1106
1107 $parameters = array('objref' => $objref);
1108 $action = '';
1109 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1110 if ($reshook > 0) {
1111 return $hookmanager->resArray['objref'];
1112 }
1113 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1114 }
1115
1122 public function getFormatedSupplierRef($objref)
1123 {
1124 global $hookmanager;
1125
1126 $parameters = array('objref' => $objref);
1127 $action = '';
1128 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1129 if ($reshook > 0) {
1130 return $hookmanager->resArray['objref'];
1131 }
1132 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1133 }
1134
1144 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1145 {
1146 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1147 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1148 $tmparray = getCountry($this->country_id, 'all');
1149 $this->country_code = $tmparray['code'];
1150 $this->country = $tmparray['label'];
1151 }
1152
1153 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1154 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1155 $tmparray = getState($this->state_id, 'all', null, 1);
1156 $this->state_code = $tmparray['code'];
1157 $this->state = $tmparray['label'];
1158 $this->region_code = $tmparray['region_code'];
1159 $this->region = $tmparray['region'];
1160 }
1161
1162 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1163 }
1164
1165
1174 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1175 {
1176 global $user, $dolibarr_main_url_root;
1177
1178 if (empty($this->last_main_doc)) {
1179 return ''; // No way to known which document name to use
1180 }
1181
1182 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1183 $ecmfile = new EcmFiles($this->db);
1184 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1185 if ($result < 0) {
1186 $this->error = $ecmfile->error;
1187 $this->errors = $ecmfile->errors;
1188 return -1;
1189 }
1190
1191 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1192 // Add entry into index
1193 if ($initsharekey) {
1194 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1195
1196 // 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
1197 /*
1198 $ecmfile->filepath = $rel_dir;
1199 $ecmfile->filename = $filename;
1200 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1201 $ecmfile->fullpath_orig = '';
1202 $ecmfile->gen_or_uploaded = 'generated';
1203 $ecmfile->description = ''; // indexed content
1204 $ecmfile->keywords = ''; // keyword content
1205 $ecmfile->share = getRandomPassword(true);
1206 $result = $ecmfile->create($user);
1207 if ($result < 0)
1208 {
1209 $this->error = $ecmfile->error;
1210 $this->errors = $ecmfile->errors;
1211 }
1212 */
1213 } else {
1214 return '';
1215 }
1216 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1217 // Add entry into index
1218 if ($initsharekey) {
1219 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1220 $ecmfile->share = getRandomPassword(true);
1221 $ecmfile->update($user);
1222 } else {
1223 return '';
1224 }
1225 }
1226 // Define $urlwithroot
1227 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1228 // This is to use external domain name found into config file
1229 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1230 //else
1231 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1232 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1233
1234 $forcedownload = 0;
1235
1236 $paramlink = '';
1237 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1238 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1239 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1240 if (!empty($ecmfile->share)) {
1241 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1242 }
1243 if ($forcedownload) {
1244 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1245 }
1246
1247 if ($relativelink) {
1248 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1249 } else {
1250 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1251 }
1252
1253 // Here $ecmfile->share is defined
1254 return $linktoreturn;
1255 }
1256
1257
1258 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1268 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1269 {
1270 // phpcs:enable
1271 global $user, $langs;
1272
1273 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1274
1275 // Check parameters
1276 if ($fk_socpeople <= 0) {
1277 $langs->load("errors");
1278 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1279 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1280 return -1;
1281 }
1282 if (!$type_contact) {
1283 $langs->load("errors");
1284 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1285 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1286 return -2;
1287 }
1288
1289 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && !$user->hasRight('societe', 'client', 'voir')) {
1290 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1291 $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid;
1292 $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id;
1293
1294 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1295
1296 if (!$resql_allowed_contacts) {
1297 $this->errors[] = $this->db->lasterror();
1298 return -3;
1299 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1300 if ($obj->cnt == 0) {
1301 $langs->load("companies");
1302 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1303 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1304 return -4;
1305 }
1306 }
1307 }
1308
1309 $id_type_contact = 0;
1310 if (is_numeric($type_contact)) {
1311 $id_type_contact = $type_contact;
1312 } else {
1313 // We look for id type_contact
1314 $sql = "SELECT tc.rowid";
1315 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1316 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1317 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1318 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1319 //print $sql;
1320 $resql = $this->db->query($sql);
1321 if ($resql) {
1322 $obj = $this->db->fetch_object($resql);
1323 if ($obj) {
1324 $id_type_contact = $obj->rowid;
1325 }
1326 }
1327 }
1328 if ($id_type_contact == 0) {
1329 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");
1330 return 0;
1331 }
1332
1333 $datecreate = dol_now();
1334
1335 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1336 $TListeContacts = $this->liste_contact(-1, $source);
1337 $already_added = false;
1338 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1339 foreach ($TListeContacts as $array_contact) {
1340 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1341 $already_added = true;
1342 break;
1343 }
1344 }
1345 }
1346
1347 if (!$already_added) {
1348 $this->db->begin();
1349
1350 // Insert into database
1351 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1352 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1353 $sql .= " VALUES (".$this->id.", ".((int) $fk_socpeople)." , ";
1354 $sql .= "'".$this->db->idate($datecreate)."'";
1355 $sql .= ", 4, ".((int) $id_type_contact);
1356 $sql .= ")";
1357
1358 $resql = $this->db->query($sql);
1359 if ($resql) {
1360 if (!$notrigger) {
1361 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1362 $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user);
1363 if ($result < 0) {
1364 $this->db->rollback();
1365 return -5;
1366 }
1367 }
1368
1369 $this->db->commit();
1370 return 1;
1371 } else {
1372 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1373 $this->error = $this->db->errno();
1374 $this->db->rollback();
1375 return -6;
1376 } else {
1377 $this->error = $this->db->lasterror();
1378 $this->db->rollback();
1379 return -7;
1380 }
1381 }
1382 } else {
1383 return 0;
1384 }
1385 }
1386
1387 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1395 public function copy_linked_contact($objFrom, $source = 'internal')
1396 {
1397 // phpcs:enable
1398 $contacts = $objFrom->liste_contact(-1, $source);
1399 foreach ($contacts as $contact) {
1400 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1401 return -1;
1402 }
1403 }
1404 return 1;
1405 }
1406
1407 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1417 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1418 {
1419 // phpcs:enable
1420 // Insert into database
1421 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1422 $sql .= " statut = ".((int) $statut);
1423 if ($type_contact_id) {
1424 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1425 }
1426 if ($fk_socpeople) {
1427 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1428 }
1429 $sql .= " where rowid = ".((int) $rowid);
1430
1431 $resql = $this->db->query($sql);
1432 if ($resql) {
1433 return 0;
1434 } else {
1435 $this->error = $this->db->lasterror();
1436 return -1;
1437 }
1438 }
1439
1440 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1448 public function delete_contact($rowid, $notrigger = 0)
1449 {
1450 // phpcs:enable
1451 global $user;
1452
1453 $error = 0;
1454
1455 $this->db->begin();
1456
1457 if (!$error && empty($notrigger)) {
1458 // Call trigger
1459 $this->context['contact_id'] = ((int) $rowid);
1460 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1461 $result = $this->call_trigger($triggerPrefix.'_DELETE_CONTACT', $user);
1462 if ($result < 0) {
1463 $error++;
1464 }
1465 // End call triggers
1466 }
1467
1468 if (!$error) {
1469 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1470
1471 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1472 $sql .= " WHERE rowid = ".((int) $rowid);
1473
1474 $result = $this->db->query($sql);
1475 if (!$result) {
1476 $error++;
1477 $this->errors[] = $this->db->lasterror();
1478 }
1479 }
1480
1481 if (!$error) {
1482 $this->db->commit();
1483 return 1;
1484 } else {
1485 $this->error = $this->db->lasterror();
1486 $this->db->rollback();
1487 return -1;
1488 }
1489 }
1490
1491 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1499 public function delete_linked_contact($source = '', $code = '')
1500 {
1501 // phpcs:enable
1502 $listId = '';
1503 $temp = array();
1504 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1505
1506 if (!empty($typeContact)) {
1507 foreach ($typeContact as $key => $value) {
1508 array_push($temp, $key);
1509 }
1510 $listId = implode(",", $temp);
1511 }
1512
1513 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1514 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1515 if (empty($listId)) {
1516 return 0;
1517 }
1518
1519 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1520 $sql .= " WHERE element_id = ".((int) $this->id);
1521 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1522
1523 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1524 if ($this->db->query($sql)) {
1525 return 1;
1526 } else {
1527 $this->error = $this->db->lasterror();
1528 return -1;
1529 }
1530 }
1531
1532 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1544 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1545 {
1546 // phpcs:enable
1547 global $langs;
1548
1549 $tab = array();
1550
1551 $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
1552 if ($source == 'internal') {
1553 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1554 }
1555 if ($source == 'external' || $source == 'thirdparty') {
1556 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1557 }
1558 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1559 if (empty($arrayoftcids)) {
1560 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1561 }
1562 $sql .= " FROM";
1563 if (empty($arrayoftcids)) {
1564 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1565 }
1566 $sql .= " ".$this->db->prefix()."element_contact as ec";
1567 if ($source == 'internal') { // internal contact (user)
1568 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1569 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1570 }
1571 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1572 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1573 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1574 }
1575 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1576 if (empty($arrayoftcids)) {
1577 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1578 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1579 if ($code) {
1580 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1581 }
1582 if ($source == 'internal') {
1583 $sql .= " AND tc.source = 'internal'";
1584 }
1585 if ($source == 'external' || $source == 'thirdparty') {
1586 $sql .= " AND tc.source = 'external'";
1587 }
1588 $sql .= " AND tc.active = 1";
1589 } else {
1590 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1591 }
1592 if ($status >= 0) {
1593 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1594 }
1595 if ($statusoflink >= 0) {
1596 $sql .= " AND ec.statut = ".((int) $statusoflink);
1597 }
1598 $sql .= " ORDER BY t.lastname ASC";
1599
1600 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1601 $resql = $this->db->query($sql);
1602 if ($resql) {
1603 $num = $this->db->num_rows($resql);
1604 $i = 0;
1605 while ($i < $num) {
1606 $obj = $this->db->fetch_object($resql);
1607
1608 if (!$list) {
1609 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1610 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1611 $tab[$i] = array(
1612 'parentId' => $this->id,
1613 'source' => $obj->source,
1614 'socid' => $obj->socid,
1615 'id' => $obj->id,
1616 'nom' => $obj->lastname, // For backward compatibility
1617 'civility' => $obj->civility,
1618 'lastname' => $obj->lastname,
1619 'firstname' => $obj->firstname,
1620 'email' => $obj->email,
1621 'address' => $obj->address,
1622 'zip' => $obj->zip,
1623 'town' => $obj->town,
1624 'country_id' => $obj->country_id,
1625 'country' => $obj->country,
1626 'login' => (empty($obj->login) ? '' : $obj->login),
1627 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1628 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1629 'statuscontact' => $obj->statuscontact,
1630 'rowid' => $obj->rowid,
1631 'code' => $obj->code,
1632 'libelle' => $libelle_type,
1633 'status' => (int) $obj->statuslink,
1634 'fk_c_type_contact' => $obj->fk_c_type_contact
1635 );
1636 } else {
1637 $tab[$i] = $obj->id;
1638 }
1639
1640 $i++;
1641 }
1642
1643 return $tab;
1644 } else {
1645 $this->error = $this->db->lasterror();
1646 dol_print_error($this->db);
1647 return -1;
1648 }
1649 }
1650
1651
1658 public function swapContactStatus($rowid)
1659 {
1660 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1661 $sql .= " tc.code, tc.libelle as type_label";
1662 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1663 $sql .= " WHERE ec.rowid =".((int) $rowid);
1664 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1665 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1666
1667 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1668 $resql = $this->db->query($sql);
1669 if ($resql) {
1670 $obj = $this->db->fetch_object($resql);
1671 $newstatut = ($obj->statut == 4) ? 5 : 4;
1672 $result = $this->update_contact($rowid, $newstatut);
1673 $this->db->free($resql);
1674 return $result;
1675 } else {
1676 $this->error = $this->db->error();
1677 dol_print_error($this->db);
1678 return -1;
1679 }
1680 }
1681
1682 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1693 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1694 {
1695 // phpcs:enable
1696 global $langs;
1697
1698 if (empty($order)) {
1699 $order = 'position';
1700 }
1701 if ($order == 'position') {
1702 $order .= ',code';
1703 }
1704
1705 $tab = array();
1706
1707 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1708 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1709 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1710 if ($activeonly == 1) {
1711 $sql .= " AND tc.active = 1"; // only the active types
1712 }
1713 if (!empty($source) && $source != 'all') {
1714 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1715 }
1716 if (!empty($code)) {
1717 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1718 }
1719 $sql .= $this->db->order($order, 'ASC');
1720
1721 //print "sql=".$sql;
1722 $resql = $this->db->query($sql);
1723 if (!$resql) {
1724 $this->error = $this->db->lasterror();
1725 //dol_print_error($this->db);
1726 return null;
1727 }
1728
1729 $num = $this->db->num_rows($resql);
1730 $i = 0;
1731 while ($i < $num) {
1732 $obj = $this->db->fetch_object($resql);
1733
1734 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1735 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1736 if (empty($option)) {
1737 $tab[$obj->rowid] = $libelle_type;
1738 } elseif ($option == 1) {
1739 $tab[$obj->code] = $libelle_type;
1740 } else {
1741 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1742 }
1743 $i++;
1744 }
1745
1746 return $tab;
1747 }
1748
1760 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1761 {
1762 global $langs;
1763
1764 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1765
1766 $tab = array();
1767
1768 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1769 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1770
1771 $sqlWhere = array();
1772 if (!empty($element)) {
1773 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1774 }
1775 if (!empty($excludeelement)) {
1776 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1777 }
1778
1779 if ($activeonly == 1) {
1780 $sqlWhere[] = " tc.active=1"; // only the active types
1781 }
1782
1783 if (!empty($source) && $source != 'all') {
1784 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1785 }
1786
1787 if (!empty($code)) {
1788 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1789 }
1790
1791 if (count($sqlWhere) > 0) {
1792 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1793 }
1794
1795 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1796
1797 dol_syslog(__METHOD__, LOG_DEBUG);
1798 $resql = $this->db->query($sql);
1799 if ($resql) {
1800 $num = $this->db->num_rows($resql);
1801 if ($num > 0) {
1802 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1803
1804 while ($obj = $this->db->fetch_object($resql)) {
1805 $modulename = $obj->module ?? $obj->element;
1806 if (strpos($obj->element, 'project') !== false) {
1807 $modulename = 'projet';
1808 } elseif ($obj->element == 'contrat') {
1809 $element = 'contract';
1810 } elseif ($obj->element == 'action') {
1811 $modulename = 'agenda';
1812 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1813 $modulename = 'fournisseur';
1814 }
1815 if (isModEnabled($modulename)) {
1816 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1817 $tmpelement = $obj->element;
1818 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1819 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1820 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1821 }
1822 }
1823 }
1824 return $tab;
1825 } else {
1826 $this->error = $this->db->lasterror();
1827 return null;
1828 }
1829 }
1830
1842 public function getIdContact($source, $code, $status = 0)
1843 {
1844 $result = array();
1845 $i = 0;
1846 // Particular case for shipping
1847 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1848 $id = $this->origin_id;
1849 $element = 'commande';
1850 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1851 $id = $this->origin_id;
1852 $element = 'order_supplier';
1853 } else {
1854 $id = $this->id;
1855 $element = $this->element;
1856 }
1857
1858 $sql = "SELECT ec.fk_socpeople";
1859 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1860 if ($source == 'internal') {
1861 $sql .= " ".$this->db->prefix()."user as c,";
1862 }
1863 if ($source == 'external') {
1864 $sql .= " ".$this->db->prefix()."socpeople as c,";
1865 }
1866 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1867 $sql .= " WHERE ec.element_id = ".((int) $id);
1868 $sql .= " AND ec.fk_socpeople = c.rowid";
1869 if ($source == 'internal') {
1870 $sql .= " AND c.entity IN (".getEntity('user').")";
1871 }
1872 if ($source == 'external') {
1873 $sql .= " AND c.entity IN (".getEntity('societe').")";
1874 }
1875 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1876 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1877 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1878 if ($code) {
1879 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1880 }
1881 $sql .= " AND tc.active = 1";
1882 if ($status) {
1883 $sql .= " AND ec.statut = ".((int) $status);
1884 }
1885
1886 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1887 $resql = $this->db->query($sql);
1888 if ($resql) {
1889 while ($obj = $this->db->fetch_object($resql)) {
1890 $result[$i] = $obj->fk_socpeople;
1891 $i++;
1892 }
1893 } else {
1894 $this->error = $this->db->error();
1895 return null;
1896 }
1897
1898 return $result;
1899 }
1900
1901 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1908 public function fetch_contact($contactid = null)
1909 {
1910 // phpcs:enable
1911 if (empty($contactid)) {
1912 $contactid = $this->contact_id;
1913 }
1914
1915 if (empty($contactid)) {
1916 return 0;
1917 }
1918
1919 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1920 $contact = new Contact($this->db);
1921 $result = $contact->fetch($contactid);
1922 $this->contact = $contact;
1923 return $result;
1924 }
1925
1926 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1934 public function fetch_thirdparty($force_thirdparty_id = 0)
1935 {
1936 // phpcs:enable
1937 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1938 return 0;
1939 }
1940
1941 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1942
1943 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1944 if ($force_thirdparty_id) {
1945 $idtofetch = $force_thirdparty_id;
1946 }
1947
1948 if ($idtofetch) {
1949 $thirdparty = new Societe($this->db);
1950 $result = $thirdparty->fetch($idtofetch);
1951 if ($result < 0) {
1952 $this->errors = array_merge($this->errors, $thirdparty->errors);
1953 }
1954 $this->thirdparty = $thirdparty;
1955
1956 // Use first price level if level not defined for third party
1957 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
1958 $this->thirdparty->price_level = 1;
1959 }
1960
1961 return $result;
1962 } else {
1963 return -1;
1964 }
1965 }
1966
1967
1975 public function fetchOneLike($ref)
1976 {
1977 if (!$this->table_ref_field) {
1978 return 0;
1979 }
1980
1981 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
1982 $sql .= " WHERE ".$this->table_ref_field." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
1983 $sql .= " LIMIT 1";
1984
1985 $query = $this->db->query($sql);
1986
1987 if (!$this->db->num_rows($query)) {
1988 return 0;
1989 }
1990
1991 $result = $this->db->fetch_object($query);
1992
1993 if (method_exists($this, 'fetch')) {
1994 return $this->fetch($result->rowid);
1995 } else {
1996 $this->error = 'Fetch method not implemented on '.get_class($this);
1997 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
1998 array_push($this->errors, $this->error);
1999 return -1;
2000 }
2001 }
2002
2003 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2012 public function fetch_barcode()
2013 {
2014 // phpcs:enable
2015 return $this->fetchBarCode();
2016 }
2017
2025 public function fetchBarCode()
2026 {
2027 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2028
2029 $idtype = $this->barcode_type;
2030 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
2031 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2032 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2033 } elseif ($this->element == 'societe') {
2034 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2035 } else {
2036 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2037 }
2038 }
2039
2040 if ($idtype > 0) {
2041 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
2042 $sql = "SELECT rowid, code, libelle as label, coder";
2043 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2044 $sql .= " WHERE rowid = ".((int) $idtype);
2045
2046 $resql = $this->db->query($sql);
2047 if ($resql) {
2048 $obj = $this->db->fetch_object($resql);
2049
2050 $this->barcode_type = $obj->rowid;
2051 $this->barcode_type_code = $obj->code;
2052 $this->barcode_type_label = $obj->label;
2053 $this->barcode_type_coder = $obj->coder;
2054 return 1;
2055 } else {
2056 dol_print_error($this->db);
2057 return -1;
2058 }
2059 }
2060 }
2061 return 0;
2062 }
2063
2069 public function fetchProject()
2070 {
2071 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2072
2073 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2074 $this->fk_project = $this->fk_projet; // For backward compatibility
2075 }
2076 if (empty($this->fk_project)) {
2077 return 0;
2078 }
2079
2080 $project = new Project($this->db);
2081 $result = $project->fetch($this->fk_project);
2082
2083 $this->project = $project;
2084
2085 return $result;
2086 }
2087
2088 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2096 public function fetch_project()
2097 {
2098 // phpcs:enable
2099 return $this->fetchProject();
2100 }
2101
2102 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2110 public function fetch_projet()
2111 {
2112 // phpcs:enable
2113 return $this->fetchProject();
2114 }
2115
2116 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2122 public function fetch_product()
2123 {
2124 // phpcs:enable
2125 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2126
2127 // @phan-suppress-next-line PhanUndeclaredProperty
2128 if (empty($this->fk_product)) {
2129 return 0;
2130 }
2131
2132 $product = new Product($this->db);
2133 // @phan-suppress-next-line PhanUndeclaredProperty
2134 $result = $product->fetch($this->fk_product);
2135
2136 $this->product = $product;
2137 return $result;
2138 }
2139
2140 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2147 public function fetch_user($userid)
2148 {
2149 // phpcs:enable
2150 $user = new User($this->db);
2151 $result = $user->fetch($userid);
2152 $this->user = $user;
2153 return $result;
2154 }
2155
2156 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2163 public function fetch_origin()
2164 {
2165 // phpcs:enable
2166 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2167
2168 // Manage classes with non standard name
2169 if ($tmpclassname == 'shipping') {
2170 $tmpclassname = 'Expedition';
2171 }
2172 if ($tmpclassname == 'delivery') {
2173 $tmpclassname = 'Livraison';
2174 }
2175 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2176 $tmpclassname = 'CommandeFournisseur';
2177 }
2178
2179 $classname = ucfirst($tmpclassname);
2180
2181 $this->origin_object = new $classname($this->db);
2182 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2183 $this->origin_object->fetch($this->origin_id);
2184 }
2185
2195 public function fetchObjectFrom($table, $field, $key, $element = null)
2196 {
2197 global $conf;
2198
2199 $result = false;
2200
2201 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2202 $sql .= " WHERE ".$field." = '".$this->db->escape($key)."'";
2203 if (!empty($element)) {
2204 $sql .= " AND entity IN (".getEntity($element).")";
2205 } else {
2206 $sql .= " AND entity = ".((int) $conf->entity);
2207 }
2208
2209 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2210 $resql = $this->db->query($sql);
2211 if ($resql) {
2212 $obj = $this->db->fetch_object($resql);
2213 // Test for avoid error -1
2214 if ($obj) {
2215 if (method_exists($this, 'fetch')) {
2216 $result = $this->fetch($obj->rowid);
2217 } else {
2218 $this->error = 'fetch() method not implemented on '.get_class($this);
2219 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2220 array_push($this->errors, $this->error);
2221 $result = -1;
2222 }
2223 }
2224 }
2225
2226 return $result;
2227 }
2228
2237 public function getValueFrom($table, $id, $field)
2238 {
2239 $result = false;
2240 if (!empty($id) && !empty($field) && !empty($table)) {
2241 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2242 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2243 return -1;
2244 }
2245 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2246 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2247 return -1;
2248 }
2249
2250 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2251 $sql .= " WHERE rowid = ".((int) $id);
2252
2253 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2254 $resql = $this->db->query($sql);
2255 if ($resql) {
2256 $row = $this->db->fetch_row($resql);
2257 $result = $row[0];
2258 }
2259 }
2260 return $result;
2261 }
2262
2279 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2280 {
2281 global $user;
2282
2283 if (empty($table)) {
2284 $table = $this->table_element;
2285 }
2286 if (empty($id)) {
2287 $id = $this->id;
2288 }
2289 if (empty($format)) {
2290 $format = 'text';
2291 }
2292 if (empty($id_field)) {
2293 $id_field = 'rowid';
2294 }
2295
2296 $propfield = $field;
2297
2298 // Special case
2299 if ($table == 'product') {
2300 if ($field == 'note_private') {
2301 $field = 'note';
2302 $propfield = 'note_private';
2303 } elseif ($field == 'status') {
2304 $field = 'tosell';
2305 $propfield = 'status';
2306 } elseif ($field == 'status_buy') {
2307 $field = 'tobuy';
2308 $propfield = 'status_buy';
2309 } elseif ($field == 'status_batch') {
2310 $field = 'tobatch';
2311 $propfield = 'status_batch';
2312 }
2313 }
2314
2315 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2316 $fk_user_field = 'fk_user_mod';
2317 }
2318 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2319 $fk_user_field = '';
2320 }
2321
2322 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2323 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2324 return -1;
2325 }
2326 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2327 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2328 return -1;
2329 }
2330
2331 $oldvalue = null;
2332 if ($trigkey) {
2333 $sql = "SELECT " . $this->db->sanitize($field);
2334 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2335 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2336
2337 $resql = $this->db->query($sql);
2338 if ($resql) {
2339 if ($obj = $this->db->fetch_object($resql)) {
2340 if ($format == 'date') {
2341 $oldvalue = $this->db->jdate($obj->$field);
2342 } else {
2343 $oldvalue = $obj->$field;
2344 }
2345 }
2346 } else {
2347 $this->error = $this->db->lasterror();
2348 return -1;
2349 }
2350 }
2351
2352 $error = 0;
2353
2354 dol_syslog(__METHOD__, LOG_DEBUG);
2355
2356 $this->db->begin();
2357
2358 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2359
2360 if ($format == 'text') {
2361 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2362 } elseif ($format == 'int') {
2363 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2364 } elseif ($format == 'date') {
2365 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2366 } elseif ($format == 'dategmt') {
2367 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2368 }
2369
2370 if ($fk_user_field) {
2371 if (!empty($fuser) && is_object($fuser)) {
2372 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2373 } elseif (empty($fuser) || $fuser != 'none') {
2374 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2375 }
2376 }
2377
2378 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2379
2380 $resql = $this->db->query($sql);
2381 if ($resql) {
2382 if ($trigkey) {
2383 // call trigger with updated object values
2384 if (method_exists($this, 'fetch')) {
2385 $result = $this->fetch($id);
2386 } else {
2387 $result = $this->fetchCommon($id);
2388 }
2389
2390 $this->oldcopy = clone $this;
2391 if (property_exists($this->oldcopy, $field)) {
2392 $this->oldcopy->$field = $oldvalue;
2393 }
2394 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2395 $this->oldcopy->$propfield = $oldvalue;
2396 }
2397
2398 if (empty($this->context['actionmsgmore'])) {
2399 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2400 }
2401
2402 if ($result >= 0) {
2403 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2404 }
2405 if ($result < 0) {
2406 $error++;
2407 }
2408 } else {
2409 if (property_exists($this, $field)) {
2410 $this->$field = $value;
2411 }
2412 if ($propfield != $field && property_exists($this, $propfield)) {
2413 $this->$propfield = $value;
2414 }
2415 }
2416
2417 if (!$error) {
2418 $this->db->commit();
2419 return 1;
2420 } else {
2421 if (property_exists($this, $field)) {
2422 $this->$field = $oldvalue;
2423 }
2424 if ($propfield != $field && property_exists($this, $propfield)) {
2425 $this->$propfield = $oldvalue;
2426 }
2427 $this->db->rollback();
2428 return -2;
2429 }
2430 } else {
2431 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2432 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2433 } else {
2434 $this->error = $this->db->lasterror();
2435 }
2436 $this->db->rollback();
2437 return -1;
2438 }
2439 }
2440
2441 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2452 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2453 {
2454 // phpcs:enable
2455 global $conf, $user;
2456
2457 if (!$this->table_element) {
2458 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2459 return -1;
2460 }
2461 if ($fieldid == 'none') {
2462 return 1;
2463 }
2464
2465 // For backward compatibility
2466 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2467 $fieldid = 'titre';
2468 }
2469
2470 // Security on socid
2471 $socid = 0;
2472 if ($user->socid > 0) {
2473 $socid = $user->socid;
2474 }
2475
2476 // this->ismultientitymanaged contains
2477 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2478 $aliastablesociete = 's';
2479 if ($this->element == 'societe') {
2480 $aliastablesociete = 'te'; // te as table_element
2481 }
2482 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2483 $sql = "SELECT MAX(te.".$fieldid.")";
2484 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2485 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2486 $tmparray = explode('@', $this->ismultientitymanaged);
2487 $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
2488 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2489 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2490 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2491 $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
2492 }
2493 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2494 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2495 }
2496 if ($fieldid == 'rowid') {
2497 $sql .= " WHERE te.".$fieldid." < ".((int) $this->id);
2498 } elseif ($fieldid == 'label') {
2499 $sql .= " WHERE te.".$fieldid." < '".$this->db->escape((string) $this->label)."'";
2500 } else { // Should be 'ref' or any other string field
2501 $sql .= " WHERE te.".$fieldid." < '".$this->db->escape((string) $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2502 }
2503 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2504 $sql .= " AND sc.fk_user = ".((int) $user->id);
2505 }
2506 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2507 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2508 }
2509
2510 $filtermax = $filter;
2511
2512 // Manage filter
2513 $errormessage = '';
2514 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2515 if ($errormessage) {
2516 if (!preg_match('/^\s*AND/i', $filtermax)) {
2517 $sql .= " AND ";
2518 }
2519 $sql .= $filtermax;
2520 } else {
2521 $sql .= $tmpsql;
2522 }
2523
2524 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2525 $tmparray = explode('@', $this->ismultientitymanaged);
2526 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2527 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2528 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2529 }
2530 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2531 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2532 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2533 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2534 } else {
2535 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2536 }
2537 } else {
2538 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2539 }
2540 }
2541 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2542 $tmparray = explode('@', $this->ismultientitymanaged);
2543 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2544 }
2545 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2546 $sql .= ' AND te.fk_soc = '.((int) $socid);
2547 }
2548 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2549 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2550 }
2551 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2552 $sql .= ' AND te.rowid = '.((int) $socid);
2553 }
2554 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2555
2556 $result = $this->db->query($sql);
2557 if (!$result) {
2558 $this->error = $this->db->lasterror();
2559 return -1;
2560 }
2561 $row = $this->db->fetch_row($result);
2562 $this->ref_previous = $row[0];
2563
2564 $sql = "SELECT MIN(te.".$fieldid.")";
2565 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2566 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2567 $tmparray = explode('@', $this->ismultientitymanaged);
2568 $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
2569 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2570 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2571 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2572 $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
2573 }
2574 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2575 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2576 }
2577 if ($fieldid == 'rowid') {
2578 $sql .= " WHERE te.".$fieldid." > ".((int) $this->id);
2579 } elseif ($fieldid == 'label') {
2580 $sql .= " WHERE te.".$fieldid." > '".$this->db->escape((string) $this->label)."'";
2581 } else { // Should be 'ref' or any other string field
2582 $sql .= " WHERE te.".$fieldid." > '".$this->db->escape((string) $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2583 }
2584 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2585 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2586 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2587 $userschilds = $user->getAllChildIds();
2588 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2589 }
2590 $sql .= ')';
2591 }
2592 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2593 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2594 }
2595
2596 $filtermin = $filter;
2597
2598 // Manage filter
2599 $errormessage = '';
2600 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2601 if ($errormessage) {
2602 if (!preg_match('/^\s*AND/i', $filtermin)) {
2603 $sql .= " AND ";
2604 }
2605 $sql .= $filtermin;
2606
2607 $filtermin = '';
2608 } else {
2609 $sql .= $tmpsql;
2610 }
2611
2612 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2613 $tmparray = explode('@', $this->ismultientitymanaged);
2614 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2615 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2616 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2617 }
2618 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2619 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2620 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2621 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2622 } else {
2623 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2624 }
2625 } else {
2626 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2627 }
2628 }
2629 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2630 $tmparray = explode('@', $this->ismultientitymanaged);
2631 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2632 }
2633 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2634 $sql .= ' AND te.fk_soc = '.((int) $socid);
2635 }
2636 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2637 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2638 }
2639 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2640 $sql .= ' AND te.rowid = '.((int) $socid);
2641 }
2642 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2643 // 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
2644
2645 $result = $this->db->query($sql);
2646 if (!$result) {
2647 $this->error = $this->db->lasterror();
2648 return -2;
2649 }
2650 $row = $this->db->fetch_row($result);
2651 $this->ref_next = $row[0];
2652
2653 return 1;
2654 }
2655
2656
2664 public function getListContactId($source = 'external')
2665 {
2666 $contactAlreadySelected = array();
2667 $tab = $this->liste_contact(-1, $source);
2668 $num = count($tab);
2669 $i = 0;
2670 while ($i < $num) {
2671 if ($source == 'thirdparty') {
2672 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2673 } else {
2674 $contactAlreadySelected[$i] = $tab[$i]['id'];
2675 }
2676 $i++;
2677 }
2678 return $contactAlreadySelected;
2679 }
2680
2681
2689 public function setProject($projectid, $notrigger = 0)
2690 {
2691 global $user;
2692 $error = 0;
2693
2694 if (!$this->table_element) {
2695 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2696 return -1;
2697 }
2698
2699 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2700 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2701 if ($projectid) {
2702 $sql .= " SET fk_project = ".((int) $projectid);
2703 } else {
2704 $sql .= " SET fk_project = NULL";
2705 }
2706 $sql .= ' WHERE id = '.((int) $this->id);
2707 // @phan-suppress-next-line PhanTypeMismatchProperty
2708 } elseif (!empty($this->fields['fk_project'])) { // Common case
2709 if ($projectid) {
2710 $sql .= " SET fk_project = ".((int) $projectid);
2711 } else {
2712 $sql .= " SET fk_project = NULL";
2713 }
2714 $sql .= ' WHERE rowid = '.((int) $this->id);
2715 } else { // Special case for old architecture objects
2716 if ($projectid) {
2717 $sql .= ' SET fk_projet = '.((int) $projectid);
2718 } else {
2719 $sql .= ' SET fk_projet = NULL';
2720 }
2721 $sql .= " WHERE rowid = ".((int) $this->id);
2722 }
2723
2724 $this->db->begin();
2725
2726 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2727 if ($this->db->query($sql)) {
2728 $this->fk_project = ((int) $projectid);
2729 } else {
2730 dol_print_error($this->db);
2731 $error++;
2732 }
2733
2734 // Triggers
2735 if (!$error && !$notrigger) {
2736 // Call triggers
2737 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2738 if ($result < 0) {
2739 $error++;
2740 } //Do also here what you must do to rollback action if trigger fail
2741 // End call triggers
2742 }
2743
2744 // Commit or rollback
2745 if ($error) {
2746 $this->db->rollback();
2747 return -1;
2748 } else {
2749 $this->db->commit();
2750 return 1;
2751 }
2752 }
2753
2760 public function setPaymentMethods($id)
2761 {
2762 global $user;
2763
2764 $error = 0;
2765 $notrigger = 0;
2766
2767 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2768
2769 if ($this->status >= 0 || $this->element == 'societe') {
2770 // TODO uniformize field name
2771 $fieldname = 'fk_mode_reglement';
2772 if ($this->element == 'societe') {
2773 $fieldname = 'mode_reglement';
2774 }
2775 if (get_class($this) == 'Fournisseur') {
2776 $fieldname = 'mode_reglement_supplier';
2777 }
2778 if (get_class($this) == 'Tva') {
2779 $fieldname = 'fk_typepayment';
2780 }
2781 if (get_class($this) == 'Salary') {
2782 $fieldname = 'fk_typepayment';
2783 }
2784
2785 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2786 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2787 $sql .= ' WHERE rowid = '.((int) $this->id);
2788
2789 if ($this->db->query($sql)) {
2790 $this->mode_reglement_id = $id;
2791 // for supplier
2792 if (get_class($this) == 'Fournisseur') {
2793 $this->mode_reglement_supplier_id = $id;
2794 }
2795 // Triggers
2796 if (!$error && !$notrigger) {
2797 // Call triggers
2798 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2799 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2800 if ($result < 0) {
2801 $error++;
2802 }
2803 // End call triggers
2804 }
2805 return 1;
2806 } else {
2807 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2808 $this->error = $this->db->error();
2809 return -1;
2810 }
2811 } else {
2812 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2813 $this->error = 'Status of the object is incompatible '.$this->status;
2814 return -2;
2815 }
2816 }
2817
2824 public function setMulticurrencyCode($code)
2825 {
2826 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2827 if ($this->status >= 0 || $this->element == 'societe') {
2828 $fieldname = 'multicurrency_code';
2829
2830 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2831 $sql .= " SET ".$fieldname." = '".$this->db->escape($code)."'";
2832 $sql .= ' WHERE rowid='.((int) $this->id);
2833
2834 if ($this->db->query($sql)) {
2835 $this->multicurrency_code = $code;
2836
2837 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2838 if ($rate) {
2839 $this->setMulticurrencyRate($rate, 2);
2840 }
2841
2842 return 1;
2843 } else {
2844 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2845 $this->error = $this->db->error();
2846 return -1;
2847 }
2848 } else {
2849 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2850 $this->error = 'Status of the object is incompatible '.$this->status;
2851 return -2;
2852 }
2853 }
2854
2862 public function setMulticurrencyRate($rate, $mode = 1)
2863 {
2864 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2865 if ($this->status >= 0 || $this->element == 'societe') {
2866 $fieldname = 'multicurrency_tx';
2867
2868 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2869 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".((float) $rate);
2870 $sql .= ' WHERE rowid='.((int) $this->id);
2871
2872 if ($this->db->query($sql)) {
2873 $this->multicurrency_tx = $rate;
2874
2875 // Update line price
2876 if (!empty($this->lines)) {
2877 foreach ($this->lines as &$line) {
2878 // Amounts in company currency will be recalculated
2879 if ($mode == 1) {
2880 $line->subprice = 0;
2881 }
2882
2883 // Amounts in foreign currency will be recalculated
2884 if ($mode == 2) {
2885 $line->multicurrency_subprice = 0;
2886 }
2887
2888 switch ($this->element) {
2889 case 'propal':
2892 '@phan-var-force Propal $this';
2893 '@phan-var-force PropaleLigne $line';
2894 $this->updateline(
2895 $line->id,
2896 $line->subprice,
2897 $line->qty,
2898 $line->remise_percent,
2899 $line->tva_tx,
2900 $line->localtax1_tx,
2901 $line->localtax2_tx,
2902 ($line->description ? $line->description : $line->desc),
2903 'HT',
2904 $line->info_bits,
2905 $line->special_code,
2906 $line->fk_parent_line,
2907 $line->skip_update_total,
2908 $line->fk_fournprice,
2909 $line->pa_ht,
2910 $line->label,
2911 $line->product_type,
2912 $line->date_start,
2913 $line->date_end,
2914 $line->array_options,
2915 $line->fk_unit,
2916 $line->multicurrency_subprice
2917 );
2918 break;
2919 case 'commande':
2922 '@phan-var-force Commande $this';
2923 '@phan-var-force OrderLine $line';
2924 $this->updateline(
2925 $line->id,
2926 ($line->description ? $line->description : $line->desc),
2927 $line->subprice,
2928 $line->qty,
2929 $line->remise_percent,
2930 $line->tva_tx,
2931 $line->localtax1_tx,
2932 $line->localtax2_tx,
2933 'HT',
2934 $line->info_bits,
2935 $line->date_start,
2936 $line->date_end,
2937 $line->product_type,
2938 $line->fk_parent_line,
2939 $line->skip_update_total,
2940 $line->fk_fournprice,
2941 $line->pa_ht,
2942 $line->label,
2943 $line->special_code,
2944 $line->array_options,
2945 $line->fk_unit,
2946 $line->multicurrency_subprice
2947 );
2948 break;
2949 case 'facture':
2952 '@phan-var-force Facture $this';
2953 '@phan-var-force FactureLigne $line';
2954 $this->updateline(
2955 $line->id,
2956 ($line->description ? $line->description : $line->desc),
2957 $line->subprice,
2958 $line->qty,
2959 $line->remise_percent,
2960 $line->date_start,
2961 $line->date_end,
2962 $line->tva_tx,
2963 $line->localtax1_tx,
2964 $line->localtax2_tx,
2965 'HT',
2966 $line->info_bits,
2967 $line->product_type,
2968 $line->fk_parent_line,
2969 $line->skip_update_total,
2970 $line->fk_fournprice,
2971 $line->pa_ht,
2972 $line->label,
2973 $line->special_code,
2974 $line->array_options,
2975 $line->situation_percent,
2976 $line->fk_unit,
2977 $line->multicurrency_subprice
2978 );
2979 break;
2980 case 'facturerec':
2983 '@phan-var-force FactureRec $this';
2984 '@phan-var-force FactureLigneRec $line';
2985 $this->updateline(
2986 $line->id,
2987 ($line->description ? $line->description : $line->desc),
2988 $line->subprice,
2989 $line->qty,
2990 $line->tva_tx,
2991 $line->localtax1_tx,
2992 $line->localtax2_tx,
2993 $line->fk_product,
2994 $line->remise_percent,
2995 'HT',
2996 $line->info_bits,
2997 0,
2998 0,
2999 $line->product_type,
3000 $line->rang,
3001 $line->special_code,
3002 $line->label,
3003 $line->fk_unit,
3004 $line->multicurrency_subprice,
3005 0,
3006 $line->date_start,
3007 $line->date_end,
3008 $line->fk_fournprice,
3009 $line->pa_ht,
3010 $line->fk_parent_line
3011 );
3012 break;
3013 case 'supplier_proposal':
3016 '@phan-var-force SupplierProposal $this';
3017 '@phan-var-force SupplierProposalLine $line';
3018 $this->updateline(
3019 $line->id,
3020 $line->subprice,
3021 $line->qty,
3022 $line->remise_percent,
3023 $line->tva_tx,
3024 $line->localtax1_tx,
3025 $line->localtax2_tx,
3026 ($line->description ? $line->description : $line->desc),
3027 'HT',
3028 $line->info_bits,
3029 $line->special_code,
3030 $line->fk_parent_line,
3031 $line->skip_update_total,
3032 $line->fk_fournprice,
3033 $line->pa_ht,
3034 $line->label,
3035 $line->product_type,
3036 $line->array_options,
3037 $line->ref_fourn,
3038 (int) $line->fk_unit,
3039 $line->multicurrency_subprice
3040 );
3041 break;
3042 case 'order_supplier':
3045 '@phan-var-force CommandeFournisseur $this';
3046 '@phan-var-force CommandeFournisseurLigne $line';
3047 $this->updateline(
3048 $line->id,
3049 ($line->description ? $line->description : $line->desc),
3050 $line->subprice,
3051 $line->qty,
3052 $line->remise_percent,
3053 $line->tva_tx,
3054 $line->localtax1_tx,
3055 $line->localtax2_tx,
3056 'HT',
3057 $line->info_bits,
3058 $line->product_type,
3059 0,
3060 $line->date_start,
3061 $line->date_end,
3062 $line->array_options,
3063 $line->fk_unit,
3064 $line->multicurrency_subprice,
3065 $line->ref_supplier
3066 );
3067 break;
3068 case 'invoice_supplier':
3071 '@phan-var-force FactureFournisseur $this';
3072 '@phan-var-force SupplierInvoiceLIne $line';
3073 $this->updateline(
3074 $line->id,
3075 ($line->description ? $line->description : $line->desc),
3076 $line->subprice,
3077 $line->tva_tx,
3078 $line->localtax1_tx,
3079 $line->localtax2_tx,
3080 $line->qty,
3081 0,
3082 'HT',
3083 $line->info_bits,
3084 $line->product_type,
3085 $line->remise_percent,
3086 0,
3087 $line->date_start,
3088 $line->date_end,
3089 $line->array_options,
3090 $line->fk_unit,
3091 $line->multicurrency_subprice,
3092 $line->ref_supplier
3093 );
3094 break;
3095 default:
3096 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3097 break;
3098 }
3099 }
3100 }
3101
3102 return 1;
3103 } else {
3104 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3105 $this->error = $this->db->error();
3106 return -1;
3107 }
3108 } else {
3109 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3110 $this->error = 'Status of the object is incompatible '.$this->status;
3111 return -2;
3112 }
3113 }
3114
3122 public function setPaymentTerms($id, $deposit_percent = null)
3123 {
3124 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.var_export($deposit_percent, true).')');
3125 if ($this->status >= 0 || $this->element == 'societe') {
3126 // TODO uniformize field name
3127 $fieldname = 'fk_cond_reglement';
3128 if ($this->element == 'societe') {
3129 $fieldname = 'cond_reglement';
3130 }
3131 if (get_class($this) == 'Fournisseur') {
3132 $fieldname = 'cond_reglement_supplier';
3133 }
3134
3135 if (empty($deposit_percent) || $deposit_percent < 0) {
3136 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3137 }
3138
3139 if ($deposit_percent > 100) {
3140 $deposit_percent = 100;
3141 }
3142
3143 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3144 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3145 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3146 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3147 }
3148 $sql .= ' WHERE rowid='.((int) $this->id);
3149
3150 if ($this->db->query($sql)) {
3151 $this->cond_reglement_id = $id;
3152 // for supplier
3153 if (get_class($this) == 'Fournisseur') {
3154 $this->cond_reglement_supplier_id = $id;
3155 }
3156 $this->deposit_percent = $deposit_percent;
3157 return 1;
3158 } else {
3159 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3160 $this->error = $this->db->error();
3161 return -1;
3162 }
3163 } else {
3164 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3165 $this->error = 'Status of the object is incompatible '.$this->status;
3166 return -2;
3167 }
3168 }
3169
3176 public function setTransportMode($id)
3177 {
3178 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3179 if ($this->status >= 0 || $this->element == 'societe') {
3180 $fieldname = 'fk_transport_mode';
3181 if ($this->element == 'societe') {
3182 $fieldname = 'transport_mode';
3183 }
3184 if (get_class($this) == 'Fournisseur') {
3185 $fieldname = 'transport_mode_supplier';
3186 }
3187
3188 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3189 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3190 $sql .= ' WHERE rowid='.((int) $this->id);
3191
3192 if ($this->db->query($sql)) {
3193 $this->transport_mode_id = $id;
3194 // for supplier
3195 if (get_class($this) == 'Fournisseur') {
3196 $this->transport_mode_supplier_id = $id;
3197 }
3198 return 1;
3199 } else {
3200 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3201 $this->error = $this->db->error();
3202 return -1;
3203 }
3204 } else {
3205 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3206 $this->error = 'Status of the object is incompatible '.$this->status;
3207 return -2;
3208 }
3209 }
3210
3218 {
3219 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
3220 if ($this->status >= 0 || $this->element == 'societe') {
3221 $fieldname = 'retained_warranty_fk_cond_reglement';
3222
3223 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3224 $sql .= " SET ".$fieldname." = ".((int) $id);
3225 $sql .= ' WHERE rowid='.((int) $this->id);
3226
3227 if ($this->db->query($sql)) {
3228 $this->retained_warranty_fk_cond_reglement = $id;
3229 return 1;
3230 } else {
3231 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
3232 $this->error = $this->db->error();
3233 return -1;
3234 }
3235 } else {
3236 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3237 $this->error = 'Status of the object is incompatible '.$this->status;
3238 return -2;
3239 }
3240 }
3241
3249 public function setDeliveryAddress($id)
3250 {
3251 $fieldname = 'fk_delivery_address';
3252 if ($this->element == 'delivery' || $this->element == 'shipping') {
3253 $fieldname = 'fk_address';
3254 }
3255
3256 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$fieldname." = ".((int) $id);
3257 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3258
3259 if ($this->db->query($sql)) {
3260 $this->fk_delivery_address = $id;
3261 return 1;
3262 } else {
3263 $this->error = $this->db->error();
3264 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3265 return -1;
3266 }
3267 }
3268
3269
3278 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3279 {
3280 global $user;
3281
3282 if (empty($userused)) {
3283 $userused = $user;
3284 }
3285
3286 $error = 0;
3287
3288 if (!$this->table_element) {
3289 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3290 return -1;
3291 }
3292
3293 $this->db->begin();
3294
3295 if ($shipping_method_id < 0) {
3296 $shipping_method_id = 'NULL';
3297 }
3298 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3299
3300 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3301 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3302 $sql .= " WHERE rowid=".((int) $this->id);
3303 $resql = $this->db->query($sql);
3304 if (!$resql) {
3305 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3306 $this->error = $this->db->lasterror();
3307 $error++;
3308 } else {
3309 if (!$notrigger) {
3310 // Call trigger
3311 $this->context = array('shippingmethodupdate' => 1);
3312 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3313 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3314 if ($result < 0) {
3315 $error++;
3316 }
3317 // End call trigger
3318 }
3319 }
3320 if ($error) {
3321 $this->db->rollback();
3322 return -1;
3323 } else {
3324 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3325 $this->db->commit();
3326 return 1;
3327 }
3328 }
3329
3330
3337 public function setWarehouse($warehouse_id)
3338 {
3339 if (!$this->table_element) {
3340 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3341 return -1;
3342 }
3343 if ($warehouse_id < 0) {
3344 $warehouse_id = 'NULL';
3345 }
3346 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3347
3348 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3349 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3350 $sql .= " WHERE rowid=".((int) $this->id);
3351
3352 if ($this->db->query($sql)) {
3353 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3354 return 1;
3355 } else {
3356 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3357 $this->error = $this->db->error();
3358 return 0;
3359 }
3360 }
3361
3362
3370 public function setDocModel($user, $modelpdf)
3371 {
3372 if (!$this->table_element) {
3373 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3374 return -1;
3375 }
3376
3377 $newmodelpdf = dol_trunc($modelpdf, 255);
3378
3379 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3380 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3381 $sql .= " WHERE rowid = ".((int) $this->id);
3382
3383 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3384 $resql = $this->db->query($sql);
3385 if ($resql) {
3386 $this->model_pdf = $modelpdf;
3387 return 1;
3388 } else {
3389 dol_print_error($this->db);
3390 return 0;
3391 }
3392 }
3393
3394
3403 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3404 {
3405 global $user;
3406
3407 if (empty($userused)) {
3408 $userused = $user;
3409 }
3410
3411 $error = 0;
3412
3413 if (!$this->table_element) {
3414 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3415 return -1;
3416 }
3417 $this->db->begin();
3418
3419 if ($fk_account < 0) {
3420 $fk_account = 'NULL';
3421 }
3422 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3423
3424 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3425 $sql .= " SET fk_account = ".((int) $fk_account);
3426 $sql .= " WHERE rowid=".((int) $this->id);
3427
3428 $resql = $this->db->query($sql);
3429 if (!$resql) {
3430 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3431 $this->error = $this->db->lasterror();
3432 $error++;
3433 } else {
3434 if (!$notrigger) {
3435 // Call trigger
3436 $this->context['bankaccountupdate'] = 1;
3437
3438 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3439 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3440 if ($result < 0) {
3441 $error++;
3442 }
3443 // End call trigger
3444 }
3445 }
3446 if ($error) {
3447 $this->db->rollback();
3448 return -1;
3449 } else {
3450 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3451 $this->db->commit();
3452 return 1;
3453 }
3454 }
3455
3456
3457 // TODO: Move line related operations to CommonObjectLine?
3458
3459 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3469 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3470 {
3471 // phpcs:enable
3472 if (!$this->table_element_line) {
3473 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3474 return -1;
3475 }
3476 if (!$this->fk_element) {
3477 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3478 return -1;
3479 }
3480
3481 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3482 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3483 $fieldposition = 'position';
3484 }
3485
3486 // Count number of lines to reorder (according to choice $renum)
3487 $nl = 0;
3488 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->table_element_line;
3489 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3490 if (!$renum) {
3491 $sql .= " AND " . $fieldposition . " = 0";
3492 }
3493 if ($renum) {
3494 $sql .= " AND " . $fieldposition . " <> 0";
3495 }
3496
3497 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3498 $resql = $this->db->query($sql);
3499 if ($resql) {
3500 $row = $this->db->fetch_row($resql);
3501 $nl = $row[0];
3502 } else {
3503 dol_print_error($this->db);
3504 }
3505 if ($nl > 0) {
3506 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3507 $rows = array();
3508
3509 // We first search all lines that are parent lines (for multilevel details lines)
3510 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3511 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3512 if ($fk_parent_line) {
3513 $sql .= ' AND fk_parent_line IS NULL';
3514 }
3515 $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3516
3517 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3518 $resql = $this->db->query($sql);
3519 if ($resql) {
3520 $i = 0;
3521 $num = $this->db->num_rows($resql);
3522 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3523 while ($i < $num) {
3524 $row = $this->db->fetch_row($resql);
3525 $rows[] = $row[0]; // Add parent line into array rows
3526 if ($fk_parent_line) {
3527 $children = $this->getChildrenOfLine($row[0], $grandchild);
3528 }
3529 if (!empty($children)) {
3530 foreach ($children as $child) {
3531 array_push($rows, $child);
3532 }
3533 }
3534 $i++;
3535 }
3536
3537 // Now we set a new number for each lines (parent and children with children included into parent tree)
3538 if (!empty($rows)) {
3539 foreach ($rows as $key => $row) {
3540 $this->updateRangOfLine($row, ($key + 1));
3541 }
3542 }
3543 } else {
3544 dol_print_error($this->db);
3545 }
3546 }
3547 return 1;
3548 }
3549
3557 public function getChildrenOfLine($id, $includealltree = 0)
3558 {
3559 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3560 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3561 $fieldposition = 'position';
3562 }
3563
3564 $rows = array();
3565
3566 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3567 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3568 $sql .= ' AND fk_parent_line = '.((int) $id);
3569 $sql .= " ORDER BY " . $fieldposition . " ASC";
3570
3571 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3572
3573 $resql = $this->db->query($sql);
3574 if (!$resql || $this->db->num_rows($resql) <= 0) {
3575 return array();
3576 }
3577
3578 while ($row = $this->db->fetch_row($resql)) {
3579 $rows[] = $row[0];
3580 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3581 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3582 }
3583 }
3584 return $rows;
3585 }
3586
3587 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3595 public function line_up($rowid, $fk_parent_line = true)
3596 {
3597 // phpcs:enable
3598 $this->line_order(false, 'ASC', $fk_parent_line);
3599
3600 // Get rang of line
3601 $rang = $this->getRangOfLine($rowid);
3602
3603 // Update position of line
3604 $this->updateLineUp($rowid, $rang);
3605 }
3606
3607 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3615 public function line_down($rowid, $fk_parent_line = true)
3616 {
3617 // phpcs:enable
3618 $this->line_order(false, 'ASC', $fk_parent_line);
3619
3620 // Get rang of line
3621 $rang = $this->getRangOfLine($rowid);
3622
3623 // Get max value for rang
3624 $max = $this->line_max();
3625
3626 // Update position of line
3627 $this->updateLineDown($rowid, $rang, $max);
3628 }
3629
3637 public function updateRangOfLine($rowid, $rang)
3638 {
3639 global $hookmanager;
3640 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3641 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3642 $fieldposition = 'position';
3643 }
3644
3645 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3646 $sql .= ' WHERE rowid = '.((int) $rowid);
3647
3648 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3649 if (!$this->db->query($sql)) {
3650 dol_print_error($this->db);
3651 return -1;
3652 }
3653
3654 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3655 $action = '';
3656 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3657 return 1;
3658 }
3659
3660 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3667 public function line_ajaxorder($rows)
3668 {
3669 // phpcs:enable
3670 $num = count($rows);
3671 for ($i = 0; $i < $num; $i++) {
3672 $this->updateRangOfLine($rows[$i], ($i + 1));
3673 }
3674 }
3675
3683 public function updateLineUp($rowid, $rang)
3684 {
3685 if ($rang <= 1) {
3686 return -1;
3687 }
3688
3689 $fieldposition = 'rang';
3690 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3691 $fieldposition = 'position';
3692 }
3693
3694 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3695 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3696 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3697 if (!$this->db->query($sql)) {
3698 $this->error = $this->db->lasterror();
3699 return -1;
3700 }
3701
3702 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3703 $sql .= ' WHERE rowid = '.((int) $rowid);
3704 if (!$this->db->query($sql)) {
3705 $this->error = $this->db->lasterror();
3706 return -1;
3707 }
3708
3709 return 1;
3710 }
3711
3720 public function updateLineDown($rowid, $rang, $max)
3721 {
3722 if ($rang >= $max) {
3723 return -1;
3724 }
3725
3726 $fieldposition = 'rang';
3727 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3728 $fieldposition = 'position';
3729 }
3730
3731 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3732 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3733 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3734 if (!$this->db->query($sql)) {
3735 $this->error = $this->db->lasterror();
3736 return -1;
3737 }
3738
3739 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3740 $sql .= ' WHERE rowid = '.((int) $rowid);
3741 if (!$this->db->query($sql)) {
3742 $this->error = $this->db->lasterror();
3743 return -1;
3744 }
3745
3746 return 1;
3747 }
3748
3755 public function getRangOfLine($rowid)
3756 {
3757 $fieldposition = 'rang';
3758 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3759 $fieldposition = 'position';
3760 }
3761
3762 $sql = "SELECT " . $fieldposition . " FROM ".$this->db->prefix().$this->table_element_line;
3763 $sql .= " WHERE rowid = ".((int) $rowid);
3764
3765 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3766 $resql = $this->db->query($sql);
3767 if (!$resql) {
3768 return 0;
3769 }
3770
3771 $row = $this->db->fetch_row($resql);
3772 return $row[0];
3773 }
3774
3781 public function getIdOfLine($rang)
3782 {
3783 $fieldposition = 'rang';
3784 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3785 $fieldposition = 'position';
3786 }
3787
3788 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3789 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3790 $sql .= " AND " . $fieldposition . " = ".((int) $rang);
3791 $resql = $this->db->query($sql);
3792 if (!$resql) {
3793 return 0;
3794 }
3795
3796 $row = $this->db->fetch_row($resql);
3797 return $row[0];
3798 }
3799
3800 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3807 public function line_max($fk_parent_line = 0)
3808 {
3809 // phpcs:enable
3810 $positionfield = 'rang';
3811 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3812 $positionfield = 'position';
3813 }
3814
3815 $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3816 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3817
3818 // Search the last rang with fk_parent_line
3819 if ($fk_parent_line) {
3820 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3821 }
3822
3823 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3824 $resql = $this->db->query($sql);
3825 if ($resql) {
3826 $row = $this->db->fetch_row($resql);
3827
3828 if ($fk_parent_line) {
3829 if (!empty($row[0])) {
3830 return $row[0];
3831 } else {
3832 return $this->getRangOfLine($fk_parent_line);
3833 }
3834 } else {
3835 return $row[0];
3836 }
3837 }
3838
3839 return 0;
3840 }
3841
3842 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3849 public function update_ref_ext($ref_ext)
3850 {
3851 // phpcs:enable
3852 if (!$this->table_element) {
3853 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3854 return -1;
3855 }
3856
3857 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3858 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3859 // @phan-suppress-next-line PhanUndeclaredProperty
3860 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3861
3862 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3863 if ($this->db->query($sql)) {
3864 $this->ref_ext = $ref_ext;
3865 return 1;
3866 } else {
3867 $this->error = $this->db->error();
3868 return -1;
3869 }
3870 }
3871
3872 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3881 public function update_note($note, $suffix = '', $notrigger = 0)
3882 {
3883 // phpcs:enable
3884 global $user;
3885
3886 if (!$this->table_element) {
3887 $this->error = 'update_note was called on object with property table_element not defined';
3888 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3889 return -1;
3890 }
3891 if (!in_array($suffix, array('', '_public', '_private'))) {
3892 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3893 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3894 return -2;
3895 }
3896
3897 $newsuffix = $suffix;
3898
3899 // Special case
3900 if ($this->table_element == 'product' && $newsuffix == '_private') {
3901 $newsuffix = '';
3902 }
3903 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3904 $fieldusermod = "fk_user_mod";
3905 } elseif ($this->table_element == 'ecm_files') {
3906 $fieldusermod = "fk_user_m";
3907 } else {
3908 $fieldusermod = "fk_user_modif";
3909 }
3910 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3911 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3912 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3913 $sql .= " WHERE rowid = ".((int) $this->id);
3914
3915 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3916 if ($this->db->query($sql)) {
3917 if ($suffix == '_public') {
3918 $this->note_public = $note;
3919 } elseif ($suffix == '_private') {
3920 $this->note_private = $note;
3921 } else {
3922 $this->note = $note; // deprecated
3923 $this->note_private = $note;
3924 }
3925 if (empty($notrigger)) {
3926 // TODO Use the $this->TRIGGER_PREFIX when implemented
3927 switch ($this->element) {
3928 case 'societe':
3929 $triggerName = 'COMPANY_MODIFY';
3930 break;
3931 case 'facture':
3932 $triggerName = 'BILL_MODIFY';
3933 break;
3934 case 'invoice_supplier':
3935 $triggerName = 'BILL_SUPPLIER_MODIFY';
3936 break;
3937 case 'facturerec':
3938 $triggerName = 'BILLREC_MODIFIY';
3939 break;
3940 case 'expensereport':
3941 $triggerName = 'EXPENSE_REPORT_MODIFY';
3942 break;
3943 default:
3944 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
3945 }
3946 $ret = $this->call_trigger($triggerName, $user);
3947 if ($ret < 0) {
3948 return -1;
3949 }
3950 }
3951 return 1;
3952 } else {
3953 $this->error = $this->db->lasterror();
3954 return -1;
3955 }
3956 }
3957
3958 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3967 public function update_note_public($note)
3968 {
3969 // phpcs:enable
3970 return $this->update_note($note, '_public');
3971 }
3972
3973 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3984 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
3985 {
3986 // phpcs:enable
3987 global $conf, $hookmanager, $action;
3988
3989 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3990 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3991 if ($reshook > 0) {
3992 return 1; // replacement code
3993 } elseif ($reshook < 0) {
3994 return -1; // failure
3995 } // reshook = 0 => execute normal code
3996
3997 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3998 $isElementForSupplier = false;
3999 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4000 $MODULE = "";
4001 if ($this->element == 'propal') {
4002 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4003 } elseif ($this->element == 'commande' || $this->element == 'order') {
4004 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4005 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4006 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4007 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4008 $isElementForSupplier = true;
4009 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4010 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4011 $isElementForSupplier = true;
4012 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4013 } elseif ($this->element == 'supplier_proposal') {
4014 $isElementForSupplier = true;
4015 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4016 }
4017 if ($isElementForSupplier) {
4018 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4019 }
4020
4021 if (!empty($MODULE)) {
4022 if (getDolGlobalString($MODULE)) {
4023 $modsactivated = explode(',', getDolGlobalString($MODULE));
4024 foreach ($modsactivated as $mod) {
4025 if (isModEnabled($mod)) {
4026 return 1; // update was disabled by specific setup
4027 }
4028 }
4029 }
4030 }
4031
4032 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4033
4034 $forcedroundingmode = $roundingadjust;
4035 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4036 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4037 } elseif ($forcedroundingmode == 'auto') {
4038 $forcedroundingmode = '0';
4039 }
4040
4041 $error = 0;
4042
4043 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4044
4045 // Define constants to find lines to sum (field name int the table_element_line not into table_element)
4046 $fieldtva = 'total_tva';
4047 $fieldlocaltax1 = 'total_localtax1';
4048 $fieldlocaltax2 = 'total_localtax2';
4049 $fieldup = 'subprice';
4050 $base_price_type = 'HT';
4051 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4052 $fieldtva = 'tva';
4053 $fieldup = 'pu_ht';
4054 }
4055 if ($this->element == 'invoice_supplier_rec') {
4056 $fieldup = 'pu_ht';
4057 }
4058 if ($this->element == 'expensereport') {
4059 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4060 // 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
4061 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4062 $forcedroundingmode = '0';
4063 $fieldup = 'value_unit';
4064 $base_price_type = 'TTC';
4065 }
4066
4067 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4068 $sql .= " total_ht, ".$this->db->sanitize($fieldtva)." as total_tva, total_ttc, ".$this->db->sanitize($fieldlocaltax1)." as total_localtax1, ".$this->db->sanitize($fieldlocaltax2)." as total_localtax2,";
4069 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4070 $sql .= ' info_bits, product_type,';
4071 if ($this->element == 'expensereport') {
4072 $sql .= ' comments as description';
4073 } else {
4074 $sql .= ' description';
4075 }
4076 if ($this->table_element_line == 'facturedet') {
4077 $sql .= ', situation_percent';
4078 }
4079 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4080 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
4081 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4082 if ($exclspec) {
4083 $product_field = 'product_type';
4084 if ($this->table_element_line == 'contratdet') {
4085 $product_field = ''; // contratdet table has no product_type field
4086 }
4087 if ($product_field) {
4088 $sql .= " AND ".$product_field." <> 9";
4089 }
4090 }
4091 $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
4092
4093 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4094
4095 $resql = $this->db->query($sql);
4096 if ($resql) {
4097 $this->total_ht = 0;
4098 $this->total_tva = 0;
4099 $this->total_localtax1 = 0;
4100 $this->total_localtax2 = 0;
4101 $this->total_ttc = 0;
4102 $total_ht_by_vats = array();
4103 $total_tva_by_vats = array();
4104 $total_ttc_by_vats = array();
4105 $this->multicurrency_total_ht = 0;
4106 $this->multicurrency_total_tva = 0;
4107 $this->multicurrency_total_ttc = 0;
4108
4109 $this->db->begin();
4110
4111 $num = $this->db->num_rows($resql);
4112 $i = 0;
4113 while ($i < $num) {
4114 $obj = $this->db->fetch_object($resql);
4115
4116 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4117 $parameters = array('fk_element' => $obj->rowid);
4118 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4119
4120 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'
4121 // This part of code is to fix data. We should not call it too often.
4122 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4123 $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);
4124
4125 $diff_when_using_price_ht = price2num((float) $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.
4126 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4127 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4128 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4129
4130 if ($diff_on_current_total) {
4131 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4132 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4133 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4134 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4135 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4136 dol_syslog('Warn1: 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);
4137 $resqlfix = $this->db->query($sqlfix);
4138 if (!$resqlfix) {
4139 dol_print_error($this->db, 'Failed to update line');
4140 }
4141 $obj->total_tva = $tmpcal[1];
4142 $obj->total_ttc = $tmpcal[2];
4143 $obj->multicurrency_total_tva = $tmpcal[17];
4144 $obj->multicurrency_total_ttc = $tmpcal[18];
4145 } elseif ($diff_when_using_price_ht) {
4146 // If total_ht calculated from unit price is different than the one in database, we do nothing, this may be a regular case to have also a different VAT, that can be explained
4147 // because price was entered included tax and we round the unit price without tax to store it in database (so recalculation will give different results),
4148 // so we continue only if price HT are same.
4149 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4150 // In rare cases, we can have the unit price that was not saved as original (like when adding a line from a discount of down payment). In this case, recalculation from unit price
4151 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4152 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4153 // After calculation from HT, totals sum of all part is consistent ($diff_on_current_total) + total_ht is same + we are not on a line with a bad unit amount + we have found a difference between VAT part calculated from unit price and the VAT part into database,
4154 // and we ask to force the use of rounding on line (like what we did on initial calculation) so this should not happen, so we force the update of line to fix this.
4155 // This part of code must be called only to fix corrupted data due to the use of the old feature to round total instead of rounding lines.
4156 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4157 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4158 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4159 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4160
4161 dol_syslog('Warn2: 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);
4162
4163 $resqlfix = $this->db->query($sqlfix);
4164 if (!$resqlfix) {
4165 dol_print_error($this->db, 'Failed to update line');
4166 }
4167 $obj->total_tva = $tmpcal[1];
4168 $obj->total_ttc = $tmpcal[2];
4169 $obj->multicurrency_total_tva = $tmpcal[17];
4170 $obj->multicurrency_total_ttc = $tmpcal[18];
4171 }
4172 }
4173 }
4174 }
4175
4176 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4177 $this->total_tva += $obj->total_tva;
4178 $this->total_localtax1 += $obj->total_localtax1;
4179 $this->total_localtax2 += $obj->total_localtax2;
4180 $this->total_ttc += $obj->total_ttc;
4181 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4182 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4183 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4184
4185 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4186 $total_ht_by_vats[$obj->vatrate] = 0;
4187 }
4188 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4189 $total_tva_by_vats[$obj->vatrate] = 0;
4190 }
4191 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4192 $total_ttc_by_vats[$obj->vatrate] = 0;
4193 }
4194 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4195 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4196 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4197
4198 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4199 if ($base_price_type == 'TTC') {
4200 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4201 } else {
4202 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4203 }
4204 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4205 //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";
4206 if ($diff) {
4207 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4208 if (abs((float) $diff) > $maxdiff) {
4209 // If error is more than 10 times the accuracy of rounding. This should not happen.
4210 $errmsg = 'We found a rounding difference after line '.($obj->rowid).' between HT*VAT='.$tmpvat.' and total in database='.$total_tva_by_vats[$obj->vatrate].' (calculated with UP*qty) but diff='.$diff.' is too high (> '.$maxdiff.') to be corrected. Some data in your lines may be corrupted. Try to edit each line manually to fix this before restarting.';
4211 dol_syslog($errmsg, LOG_WARNING);
4212 $this->error = $errmsg;
4213 $error++;
4214 break;
4215 }
4216
4217 if ($base_price_type == 'TTC') {
4218 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldtva)." = ".price2num($obj->total_tva - (float) $diff).", total_ht = ".price2num($obj->total_ht + (float) $diff)." WHERE rowid = ".((int) $obj->rowid);
4219 dol_syslog('We found a difference of '.$diff.' for line rowid = '.$obj->rowid.' between TotalHT('.$total_ht_by_vats[$obj->vatrate].')*VATrate('.$obj->vatrate.')='.$tmpvat.' and total in database='.$total_tva_by_vats[$obj->vatrate]." (calculated with UP*qty). We fix the total_vat and total_ht of line by running sqlfix = ".$sqlfix);
4220 } else {
4221 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldtva)." = ".price2num($obj->total_tva - (float) $diff).", total_ttc = ".price2num($obj->total_ttc - (float) $diff)." WHERE rowid = ".((int) $obj->rowid);
4222 dol_syslog('We found a difference of '.$diff.' for line rowid = '.$obj->rowid.' between TotalHT('.$total_ht_by_vats[$obj->vatrate].')*VATrate('.$obj->vatrate.')='.$tmpvat.' and total in database='.$total_tva_by_vats[$obj->vatrate]." (calculated with UP*qty). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix);
4223 }
4224
4225 $resqlfix = $this->db->query($sqlfix);
4226
4227 if (!$resqlfix) {
4228 dol_print_error($this->db, 'Failed to update line');
4229 }
4230
4231 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4232 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4233 if ($base_price_type == 'TTC') {
4234 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4235 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4236 } else {
4237 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4238 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4239 }
4240 }
4241 }
4242
4243 $i++;
4244 }
4245
4246 // Add revenue stamp to total
4247 // @phan-suppress-next-line PhanUndeclaredProperty
4248 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4249 // @phan-suppress-next-line PhanUndeclaredProperty
4250 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4251
4252 // Situations totals
4253 if (!empty($this->situation_cycle_ref) && !empty($this->situation_counter) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits')) { // @phan-suppress-current-line PhanUndeclaredProperty
4254 '@phan-var-force Facture $this';
4255 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; // Note: possibly useless as $this is normally already Facture, so the class file should be loaded
4256 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4257 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4258 $prev_sits = $this->get_prev_sits();
4259
4260 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4261 $this->total_ht -= $sit->total_ht;
4262 $this->total_tva -= $sit->total_tva;
4263 $this->total_localtax1 -= $sit->total_localtax1;
4264 $this->total_localtax2 -= $sit->total_localtax2;
4265 $this->total_ttc -= $sit->total_ttc;
4266 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4267 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4268 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4269 }
4270 }
4271 }
4272 }
4273
4274 // Clean total
4275 $this->total_ht = (float) price2num($this->total_ht);
4276 $this->total_tva = (float) price2num($this->total_tva);
4277 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4278 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4279 $this->total_ttc = (float) price2num($this->total_ttc);
4280
4281 $this->db->free($resql);
4282
4283 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4284 $fieldht = 'total_ht';
4285 $fieldtva = 'tva';
4286 $fieldlocaltax1 = 'localtax1';
4287 $fieldlocaltax2 = 'localtax2';
4288 $fieldttc = 'total_ttc';
4289 // Specific code for backward compatibility with old field names
4290 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4291 $fieldtva = 'total_tva';
4292 }
4293
4294 if (!$error && empty($nodatabaseupdate)) {
4295 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4296 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4297 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4298 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4299 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4300 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4301 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4302 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4303 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4304 $sql .= " WHERE rowid = ".((int) $this->id);
4305
4306 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4307 $resql = $this->db->query($sql);
4308
4309 if (!$resql) {
4310 $error++;
4311 $this->error = $this->db->lasterror();
4312 $this->errors[] = $this->db->lasterror();
4313 }
4314 }
4315
4316 if (!$error) {
4317 $this->db->commit();
4318 return 1;
4319 } else {
4320 $this->db->rollback();
4321 return -1;
4322 }
4323 } else {
4324 dol_print_error($this->db, 'Bad request in update_price');
4325 return -1;
4326 }
4327 }
4328
4329 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4340 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4341 {
4342 // phpcs:enable
4343 global $user, $hookmanager, $action;
4344
4345 if (empty($this->origin_type) && !empty($this->origin)) {
4346 $this->origin_type = $this->origin;
4347 }
4348 $origin = (!empty($origin) ? $origin : $this->origin_type);
4349 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4350 $f_user = isset($f_user) ? $f_user : $user;
4351
4352 // Special case
4353 if ($origin == 'order') {
4354 $origin = 'commande';
4355 }
4356 if ($origin == 'invoice') {
4357 $origin = 'facture';
4358 }
4359 if ($origin == 'invoice_template') {
4360 $origin = 'facturerec';
4361 }
4362 if ($origin == 'supplierorder') {
4363 $origin = 'order_supplier';
4364 }
4365
4366 // Add module part to target type
4367 $targettype = $this->getElementType();
4368
4369 $parameters = array('targettype' => $targettype);
4370 // Hook for explicitly set the targettype if it must be different than $this->element
4371 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4372 if ($reshook > 0) {
4373 if (!empty($hookmanager->resArray['targettype'])) {
4374 $targettype = $hookmanager->resArray['targettype'];
4375 }
4376 }
4377
4378 $this->db->begin();
4379 $error = 0;
4380
4381 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4382 $sql .= "fk_source";
4383 $sql .= ", sourcetype";
4384 $sql .= ", fk_target";
4385 $sql .= ", targettype";
4386 $sql .= ") VALUES (";
4387 $sql .= ((int) $origin_id);
4388 $sql .= ", '" . $this->db->escape($origin) . "'";
4389 $sql .= ", " . ((int) $this->id);
4390 $sql .= ", '" . $this->db->escape($targettype) . "'";
4391 $sql .= ")";
4392
4393 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4394 if ($this->db->query($sql)) {
4395 if (!$notrigger) {
4396 // Call trigger
4397 $this->context['link_origin'] = $origin;
4398 $this->context['link_origin_id'] = $origin_id;
4399
4400 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4401 if ($result < 0) {
4402 $error++;
4403 }
4404 // End call triggers
4405 }
4406 } else {
4407 $this->error = $this->db->lasterror();
4408 $error++;
4409 }
4410
4411 if (!$error) {
4412 $this->db->commit();
4413 return 1;
4414 } else {
4415 $this->db->rollback();
4416 return 0;
4417 }
4418 }
4419
4425 public function getElementType()
4426 {
4427 // 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.
4428 // 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).
4429 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4430 // Add module part to target type if object has $module property and isn't in core modules.
4431 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4432 }
4433
4434
4457 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4458 {
4459 global $hookmanager, $action;
4460
4461 // Important for pdf generation time reduction
4462 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4463 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4464 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4465 return 1;
4466 }
4467
4468 $this->linkedObjectsIds = array();
4469 $this->linkedObjects = array();
4470
4471 // Hook for allowing modules to completely alter the behavior of the method
4472 $parameters = array(
4473 'sourceid' => $sourceid,
4474 'sourcetype' => $sourcetype,
4475 'targetid' => $targetid,
4476 'targettype' => $targettype,
4477 'clause' => $clause,
4478 'alsosametype' => $alsosametype,
4479 'orderby' => $orderby,
4480 'loadalsoobjects' => $loadalsoobjects
4481 );
4482 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4483 if ($reshook > 0) {
4484 return $reshook;
4485 }
4486
4487 $justsource = false;
4488 $justtarget = false;
4489 $withtargettype = false;
4490 $withsourcetype = false;
4491
4492 // Hook for explicitly set the targettype if it must be differtent than $this->element
4493 if (is_object($hookmanager)) {
4494 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4495 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4496 if ($reshook > 0) {
4497 if (!empty($hookmanager->resArray['sourcetype'])) {
4498 $sourcetype = $hookmanager->resArray['sourcetype'];
4499 }
4500 if (!empty($hookmanager->resArray['sourceid'])) {
4501 $sourceid = $hookmanager->resArray['sourceid'];
4502 }
4503 if (!empty($hookmanager->resArray['targettype'])) {
4504 $targettype = $hookmanager->resArray['targettype'];
4505 }
4506 if (!empty($hookmanager->resArray['targetid'])) {
4507 $targetid = $hookmanager->resArray['targetid'];
4508 }
4509 }
4510 }
4511
4512 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4513 $justsource = true; // the source (id and type) is a search criteria
4514 if (!empty($targettype)) {
4515 $withtargettype = true;
4516 }
4517 }
4518 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4519 $justtarget = true; // the target (id and type) is a search criteria
4520 if (!empty($sourcetype)) {
4521 $withsourcetype = true;
4522 }
4523 }
4524
4525 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4526 $targetid = (!empty($targetid) ? $targetid : $this->id);
4527 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4528 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4529
4530 /*if (empty($sourceid) && empty($targetid))
4531 {
4532 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4533 return -1;
4534 }*/
4535
4536 // Links between objects are stored in table element_element
4537 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4538 $sql .= " FROM ".$this->db->prefix()."element_element";
4539 $sql .= " WHERE ";
4540 if ($justsource || $justtarget) {
4541 if ($justsource) {
4542 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4543 if ($withtargettype) {
4544 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4545 }
4546 } elseif ($justtarget) {
4547 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4548 if ($withsourcetype) {
4549 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4550 }
4551 }
4552 } else {
4553 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4554 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4555 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4556 $this->linkedObjectsFullLoaded[$this->id] = true;
4557 }
4558 }
4559 $sql .= " ORDER BY ".$orderby;
4560
4561 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4562 $resql = $this->db->query($sql);
4563 if ($resql) {
4564 $num = $this->db->num_rows($resql);
4565 $i = 0;
4566 while ($i < $num) {
4567 $obj = $this->db->fetch_object($resql);
4568 if ($justsource || $justtarget) {
4569 if ($justsource) {
4570 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4571 } elseif ($justtarget) {
4572 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4573 }
4574 } else {
4575 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4576 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4577 }
4578 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4579 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4580 }
4581 }
4582 $i++;
4583 }
4584
4585 if (!empty($this->linkedObjectsIds)) {
4586 $tmparray = $this->linkedObjectsIds;
4587 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4588 $element_properties = getElementProperties($objecttype);
4589 $element = $element_properties['element'];
4590 $classPath = $element_properties['classpath'];
4591 $classFile = $element_properties['classfile'];
4592 $className = $element_properties['classname'];
4593 $module = $element_properties['module'];
4594
4595 // Here $module, $classFile and $className are set, we can use them.
4596 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4597 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4598 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4599 if (class_exists($className)) {
4600 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4601 $object = new $className($this->db);
4602 '@phan-var-force CommonObject $object';
4603 $ret = $object->fetch($objectid);
4604 if ($ret >= 0) {
4605 $this->linkedObjects[$objecttype][$i] = $object;
4606 }
4607 }
4608 }
4609 }
4610 } else {
4611 unset($this->linkedObjectsIds[$objecttype]);
4612 }
4613 }
4614 }
4615 return 1;
4616 } else {
4617 dol_print_error($this->db);
4618 return -1;
4619 }
4620 }
4621
4628 public function clearObjectLinkedCache()
4629 {
4630 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4631 unset($this->linkedObjectsFullLoaded[$this->id]);
4632 }
4633
4634 return 1;
4635 }
4636
4649 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4650 {
4651 global $user;
4652 $updatesource = false;
4653 $updatetarget = false;
4654 $f_user = isset($f_user) ? $f_user : $user;
4655
4656 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4657 $updatesource = true;
4658 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4659 $updatetarget = true;
4660 }
4661
4662 $this->db->begin();
4663 $error = 0;
4664
4665 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4666 if ($updatesource) {
4667 $sql .= "fk_source = " . ((int) $sourceid);
4668 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4669 $sql .= " WHERE fk_target = " . ((int) $this->id);
4670 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4671 } elseif ($updatetarget) {
4672 $sql .= "fk_target = " . ((int) $targetid);
4673 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4674 $sql .= " WHERE fk_source = " . ((int) $this->id);
4675 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4676 }
4677
4678 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4679 if ($this->db->query($sql)) {
4680 if (!$notrigger) {
4681 // Call trigger
4682 $this->context['link_source_id'] = $sourceid;
4683 $this->context['link_source_type'] = $sourcetype;
4684 $this->context['link_target_id'] = $targetid;
4685 $this->context['link_target_type'] = $targettype;
4686
4687 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4688 if ($result < 0) {
4689 $error++;
4690 }
4691 // End call triggers
4692 }
4693 } else {
4694 $this->error = $this->db->lasterror();
4695 $error++;
4696 }
4697
4698 if (!$error) {
4699 $this->db->commit();
4700 return 1;
4701 } else {
4702 $this->db->rollback();
4703 return -1;
4704 }
4705 }
4706
4720 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4721 {
4722 global $user;
4723 $deletesource = false;
4724 $deletetarget = false;
4725 $f_user = isset($f_user) ? $f_user : $user;
4726
4727 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4728 $deletesource = true;
4729 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4730 $deletetarget = true;
4731 }
4732
4733 $element = $this->getElementType();
4734 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4735 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4736 $targetid = (!empty($targetid) ? $targetid : $this->id);
4737 $targettype = (!empty($targettype) ? $targettype : $element);
4738 $this->db->begin();
4739 $error = 0;
4740
4741 if (!$notrigger) {
4742 // Call trigger
4743 $this->context['link_id'] = $rowid;
4744 $this->context['link_source_id'] = $sourceid;
4745 $this->context['link_source_type'] = $sourcetype;
4746 $this->context['link_target_id'] = $targetid;
4747 $this->context['link_target_type'] = $targettype;
4748
4749 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4750 if ($result < 0) {
4751 $error++;
4752 }
4753 // End call triggers
4754 }
4755
4756 if (!$error) {
4757 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4758 $sql .= " WHERE";
4759 if ($rowid > 0) {
4760 $sql .= " rowid = " . ((int) $rowid);
4761 } else {
4762 if ($deletesource) {
4763 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4764 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4765 } elseif ($deletetarget) {
4766 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4767 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4768 } else {
4769 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4770 $sql .= " OR";
4771 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4772 }
4773 }
4774
4775 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4776 if (!$this->db->query($sql)) {
4777 $this->error = $this->db->lasterror();
4778 $this->errors[] = $this->error;
4779 $error++;
4780 }
4781 }
4782
4783 if (!$error) {
4784 $this->db->commit();
4785 return 1;
4786 } else {
4787 $this->db->rollback();
4788 return 0;
4789 }
4790 }
4791
4801 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4802 {
4803 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4804 return -1;
4805 }
4806 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4807 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4808 }
4809
4810 global $db;
4811
4812 $sql = "SELECT ".$field_select." FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4813 $resql = $db->query($sql);
4814
4815 $TRes = array();
4816 if (!empty($resql)) {
4817 while ($res = $db->fetch_object($resql)) {
4818 $TRes[] = $res->{$field_select};
4819 }
4820 }
4821
4822 return $TRes;
4823 }
4824
4833 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4834 {
4835 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4836 return -1;
4837 }
4838
4839 global $db;
4840
4841 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4842 $resql = $db->query($sql);
4843 $n = 0;
4844 if ($resql) {
4845 $res = $db->fetch_object($resql);
4846 if ($res) {
4847 $n = $res->nb;
4848 }
4849 }
4850
4851 return $n;
4852 }
4853
4862 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4863 {
4864 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4865 return -1;
4866 }
4867
4868 global $db;
4869
4870 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4871 $resql = $db->query($sql);
4872
4873 if (empty($resql)) {
4874 return 0;
4875 }
4876
4877 return 1;
4878 }
4879
4891 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
4892 {
4893 global $user;
4894
4895 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4896
4897 $elementId = (!empty($elementId) ? $elementId : $this->id);
4898 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4899
4900 $this->db->begin();
4901
4902 if (empty($fieldstatus)) {
4903 if ($elementTable == 'facture_rec') {
4904 $fieldstatus = "suspended";
4905 }
4906 if ($elementTable == 'mailing') {
4907 $fieldstatus = "statut";
4908 }
4909 if ($elementTable == 'cronjob') {
4910 $fieldstatus = "status";
4911 }
4912 if ($elementTable == 'user') {
4913 $fieldstatus = "statut";
4914 }
4915 if ($elementTable == 'expensereport') {
4916 $fieldstatus = "fk_statut";
4917 }
4918 if ($elementTable == 'receptiondet_batch') {
4919 $fieldstatus = "status";
4920 }
4921 if ($elementTable == 'prelevement_bons') {
4922 $fieldstatus = "statut";
4923 }
4924 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4925 $fieldstatus = 'status';
4926 }
4927 // If still empty
4928 if (empty($fieldstatus)) {
4929 $fieldstatus = 'fk_statut';
4930 }
4931 }
4932
4933 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
4934 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
4935 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
4936 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
4937 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4938 $sql .= ", fk_user_valid = ".((int) $user->id);
4939 }
4940 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4941 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
4942 }
4943 if ($status == 1 && in_array($elementTable, array('inventory'))) {
4944 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
4945 }
4946 $sql .= " WHERE rowid = ".((int) $elementId);
4947 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
4948
4949 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4950 $resql = $this->db->query($sql);
4951 if ($resql) {
4952 $error = 0;
4953
4954 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
4955
4956 if ($nb_rows_affected > 0) {
4957 if (empty($trigkey)) {
4958 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
4959 if ($this->element == 'supplier_proposal' && $status == 2) {
4960 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4961 }
4962 if ($this->element == 'supplier_proposal' && $status == 3) {
4963 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4964 }
4965 if ($this->element == 'supplier_proposal' && $status == 4) {
4966 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4967 }
4968 if ($this->element == 'fichinter' && $status == 3) {
4969 $trigkey = 'FICHINTER_CLASSIFY_DONE';
4970 }
4971 if ($this->element == 'fichinter' && $status == 2) {
4972 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
4973 }
4974 if ($this->element == 'fichinter' && $status == 1) {
4975 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4976 }
4977 }
4978
4979 $this->context = array_merge($this->context, array('newstatus' => $status));
4980
4981 if ($trigkey && $trigkey != 'none') {
4982 $this->oldcopy = dol_clone($this);
4983
4984 // Call trigger
4985 $result = $this->call_trigger($trigkey, $user);
4986 if ($result < 0) {
4987 $error++;
4988 }
4989 // End call triggers
4990 }
4991 } else {
4992 // The status was probably already good. We do nothing more, no triggers.
4993 }
4994
4995 if (!$error) {
4996 $this->db->commit();
4997
4998 if (empty($savElementId)) {
4999 // If the element we update is $this (so $elementId was provided as null)
5000 if ($fieldstatus == 'dispute_status') {
5001 $this->dispute_status = $status;
5002 } elseif ($fieldstatus == 'tosell') {
5003 $this->status = $status;
5004 } elseif ($fieldstatus == 'tobuy') {
5005 $this->status_buy = $status; // @phpstan-ignore-line
5006 } elseif ($fieldstatus == 'tobatch') {
5007 $this->status_batch = $status; // @phpstan-ignore-line
5008 } else {
5009 $this->status = $status;
5010 }
5011 }
5012
5013 return 1;
5014 } else {
5015 $this->db->rollback();
5016 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5017 return -1;
5018 }
5019 } else {
5020 $this->error = $this->db->lasterror();
5021 $this->db->rollback();
5022 return -1;
5023 }
5024 }
5025
5026
5034 public function getCanvas($id = 0, $ref = '')
5035 {
5036 if (empty($id) && empty($ref)) {
5037 return 0;
5038 }
5039 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5040 return 0; // To increase speed. Not enabled by default.
5041 }
5042
5043 // Clean parameters
5044 $ref = trim($ref);
5045
5046 $sql = "SELECT rowid, canvas";
5047 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5048 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5049 if (!empty($id)) {
5050 $sql .= " AND rowid = ".((int) $id);
5051 }
5052 if (!empty($ref)) {
5053 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5054 }
5055
5056 $resql = $this->db->query($sql);
5057 if ($resql) {
5058 $obj = $this->db->fetch_object($resql);
5059 if ($obj) {
5060 $this->canvas = $obj->canvas;
5061 return 1;
5062 } else {
5063 return 0;
5064 }
5065 } else {
5066 dol_print_error($this->db);
5067 return -1;
5068 }
5069 }
5070
5071
5078 public function getSpecialCode($lineid)
5079 {
5080 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->table_element_line;
5081 $sql .= " WHERE rowid = ".((int) $lineid);
5082 $resql = $this->db->query($sql);
5083 if ($resql) {
5084 $row = $this->db->fetch_row($resql);
5085 return (!empty($row[0]) ? $row[0] : 0);
5086 }
5087
5088 return 0;
5089 }
5090
5099 public function isObjectUsed($id = 0, $entity = 0)
5100 {
5101 global $langs;
5102
5103 if (empty($id)) {
5104 $id = $this->id;
5105 }
5106
5107 // Check parameters
5108 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5109 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5110 return -1;
5111 }
5112
5113 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5114 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5115 $tmparray = array_keys($this->childtables);
5116 if (is_numeric($tmparray[0])) {
5117 $arraytoscan = array_flip($this->childtables);
5118 }
5119
5120 // Test if child exists
5121 $haschild = 0;
5122 foreach ($arraytoscan as $table => $element) {
5123 //print $id.'-'.$table.'-'.$elementname.'<br>';
5124
5125 // Check if module is enabled (to avoid error if tables of module not created)
5126 if (isset($element['enabled']) && !empty($element['enabled'])) {
5127 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5128 if (empty($enabled)) {
5129 continue;
5130 }
5131 }
5132
5133 // Check if element can be deleted
5134 $sql = "SELECT COUNT(*) as nb";
5135 $sql .= " FROM ".$this->db->prefix().$table." as c";
5136 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5137 $sql .= ", ".$this->db->prefix().$element['parent']." as p";
5138 }
5139 if (!empty($element['fk_element'])) {
5140 $sql .= " WHERE c.".$element['fk_element']." = ".((int) $id);
5141 } else {
5142 $sql .= " WHERE c.".$this->fk_element." = ".((int) $id);
5143 }
5144 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5145 $sql .= " AND c.".$element['parentkey']." = p.rowid";
5146 }
5147 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5148 $sql .= " AND c.".$element['parenttypefield']." = '".$this->db->escape($element['parenttypevalue'])."'";
5149 }
5150 if (!empty($entity)) {
5151 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5152 $sql .= " AND p.entity = ".((int) $entity);
5153 } else {
5154 $sql .= " AND c.entity = ".((int) $entity);
5155 }
5156 }
5157
5158 $resql = $this->db->query($sql);
5159 if ($resql) {
5160 $obj = $this->db->fetch_object($resql);
5161 if ($obj->nb > 0) {
5162 $langs->load("errors");
5163 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5164 $haschild += $obj->nb;
5165 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5166 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5167 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5168 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5169 } else { // new usage: $element['name']=Translation key
5170 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5171 }
5172 break; // We found at least one, we stop here
5173 }
5174 } else {
5175 $this->errors[] = $this->db->lasterror();
5176 return -1;
5177 }
5178 }
5179 if ($haschild > 0) {
5180 $this->errors[] = "ErrorRecordHasChildren";
5181 return $haschild;
5182 } else {
5183 return 0;
5184 }
5185 }
5186
5193 public function hasProductsOrServices($predefined = -1)
5194 {
5195 $nb = 0;
5196
5197 foreach ($this->lines as $key => $val) {
5198 $qualified = 0;
5199 if ($predefined == -1) {
5200 $qualified = 1;
5201 }
5202 if ($predefined == 1 && $val->fk_product > 0) {
5203 $qualified = 1;
5204 }
5205 if ($predefined == 0 && $val->fk_product <= 0) {
5206 $qualified = 1;
5207 }
5208 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5209 $qualified = 1;
5210 }
5211 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5212 $qualified = 1;
5213 }
5214 if ($qualified) {
5215 $nb++;
5216 }
5217 }
5218 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5219 return $nb;
5220 }
5221
5227 public function getTotalDiscount()
5228 {
5229 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5230 $total_discount = 0.00;
5231
5232 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5233 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5234 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
5235
5236 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5237 $resql = $this->db->query($sql);
5238 if ($resql) {
5239 $num = $this->db->num_rows($resql);
5240 $i = 0;
5241 while ($i < $num) {
5242 $obj = $this->db->fetch_object($resql);
5243
5244 $pu_ht = $obj->pu_ht;
5245 $qty = $obj->qty;
5246 $total_ht = $obj->total_ht;
5247
5248 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5249 $total_discount += $total_discount_line;
5250
5251 $i++;
5252 }
5253 }
5254
5255 //print $total_discount; exit;
5256 return (float) price2num($total_discount);
5257 }
5258
5259 return null;
5260 }
5261
5262
5269 public function getTotalWeightVolume()
5270 {
5271 $totalWeight = 0;
5272 $totalVolume = 0;
5273 // defined for shipment only
5274 $totalOrdered = 0;
5275 // defined for shipment only
5276 $totalToShip = 0;
5277
5278 if (empty($this->lines)) {
5279 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5280 }
5281
5282 foreach ($this->lines as $line) {
5283 if (isset($line->qty_asked)) {
5284 $totalOrdered += $line->qty_asked; // defined for shipment only
5285 }
5286 if (isset($line->qty_shipped)) {
5287 $totalToShip += $line->qty_shipped; // defined for shipment only
5288 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5289 if (empty($totalToShip)) {
5290 $totalToShip = 0;
5291 }
5292 $totalToShip += $line->qty; // defined for reception only
5293 }
5294
5295 // Define qty, weight, volume, weight_units, volume_units
5296 if ($this->element == 'shipping') {
5297 // for shipments
5298 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5299 } else {
5300 $qty = $line->qty ? $line->qty : 0;
5301 }
5302
5303 $weight = !empty($line->weight) ? $line->weight : 0;
5304 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5305 $volume = !empty($line->volume) ? $line->volume : 0;
5306 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5307
5308 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5309 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5310 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5311 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5312
5313 $weightUnit = 0;
5314 $volumeUnit = 0;
5315 if (!empty($weight_units)) {
5316 $weightUnit = $weight_units;
5317 }
5318 if (!empty($volume_units)) {
5319 $volumeUnit = $volume_units;
5320 }
5321
5322 if (empty($totalWeight)) {
5323 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5324 }
5325 if (empty($totalVolume)) {
5326 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5327 }
5328
5329 //var_dump($line->volume_units);
5330 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5331 $trueWeightUnit = pow(10, $weightUnit);
5332 $totalWeight += $weight * $qty * $trueWeightUnit;
5333 } else {
5334 if ($weight_units == 99) {
5335 // conversion 1 Pound = 0.45359237 KG
5336 $trueWeightUnit = 0.45359237;
5337 $totalWeight += $weight * $qty * $trueWeightUnit;
5338 } elseif ($weight_units == 98) {
5339 // conversion 1 Ounce = 0.0283495 KG
5340 $trueWeightUnit = 0.0283495;
5341 $totalWeight += $weight * $qty * $trueWeightUnit;
5342 } else {
5343 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5344 }
5345 }
5346 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5347 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5348 $trueVolumeUnit = pow(10, $volumeUnit);
5349 //print $line->volume;
5350 $totalVolume += $volume * $qty * $trueVolumeUnit;
5351 } else {
5352 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5353 }
5354 }
5355
5356 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5357 }
5358
5359
5365 public function setExtraParameters()
5366 {
5367 $this->db->begin();
5368
5369 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5370 $extraparams = dol_trunc($extraparams, 250);
5371
5372 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5373 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5374 $sql .= " WHERE rowid = ".((int) $this->id);
5375
5376 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5377 $resql = $this->db->query($sql);
5378 if (!$resql) {
5379 $this->error = $this->db->lasterror();
5380 $this->db->rollback();
5381 return -1;
5382 } else {
5383 $this->db->commit();
5384 return 1;
5385 }
5386 }
5387
5388
5389 // --------------------
5390 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5391 // --------------------
5392
5393 /* This is to show add lines */
5394
5404 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5405 {
5406 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5407
5408 // Line extrafield
5409 if (!is_object($extrafields)) {
5410 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5411 $extrafields = new ExtraFields($this->db);
5412 }
5413 $extrafields->fetch_name_optionals_label($this->table_element_line);
5414
5415 // Output template part (modules that overwrite templates must declare this into descriptor)
5416 // Use global variables + $dateSelector + $seller and $buyer
5417 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5418 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5419 foreach ($dirtpls as $module => $reldir) {
5420 if (!empty($module)) {
5421 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5422 } else {
5423 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5424 }
5425
5426 if (empty($conf->file->strict_mode)) {
5427 $res = @include $tpl;
5428 } else {
5429 $res = include $tpl; // for debug
5430 }
5431 if ($res) {
5432 break;
5433 }
5434 }
5435 }
5436
5437
5438
5439 /* This is to show array of line of details */
5440
5441
5456 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5457 {
5458 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5459 // TODO We should not use global var for this
5460 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5461
5462 // Define $usemargins (used by objectline_xxx.tpl.php files)
5463 $usemargins = 0;
5464 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5465 $usemargins = 1;
5466 }
5467
5468 $num = count($this->lines);
5469
5470 // Line extrafield
5471 if (!is_object($extrafields)) {
5472 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5473 $extrafields = new ExtraFields($this->db);
5474 }
5475 $extrafields->fetch_name_optionals_label($this->table_element_line);
5476
5477 if (method_exists($this, 'loadExpeditions')) {
5478 // TODO No reason to have this here. This fill an array ->expeditions not used here. This can be called before going here of by the code that need it.
5479 $this->loadExpeditions();
5480 }
5481
5482 $parameters = array();
5483 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5484 if (empty($reshook)) {
5485 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5486 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5487 if (empty($reshook)) {
5488 // Output template part (modules that overwrite templates must declare this into descriptor)
5489 // Use global variables + $dateSelector + $seller and $buyer
5490 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5491 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5492 foreach ($dirtpls as $module => $reldir) {
5493 $res = 0;
5494 if (!empty($module)) {
5495 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5496 } else {
5497 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5498 }
5499 if (file_exists($tpl)) {
5500 if (empty($conf->file->strict_mode)) {
5501 $res = @include $tpl;
5502 } else {
5503 $res = include $tpl; // for debug
5504 }
5505 }
5506 if ($res) {
5507 break;
5508 }
5509 }
5510 }
5511
5512 $i = 0;
5513
5514 print "<!-- begin printObjectLines() -->\n";
5515 foreach ($this->lines as $line) {
5516 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5517 //$line->fetch_optionals();
5518
5519 if (is_object($hookmanager)) {
5520 if (empty($line->fk_parent_line)) {
5521 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5522 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5523 } else {
5524 $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);
5525 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5526 }
5527 }
5528 if (empty($reshook)) {
5529 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5530 }
5531
5532 $i++;
5533 }
5534 print "<!-- end printObjectLines() -->\n";
5535 }
5536 }
5537
5555 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5556 {
5557 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5558 global $form;
5559 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5560
5561 // var used into tpl
5562 $text = '';
5563 $description = '';
5564
5565 // Line in view mode
5566 if ($action != 'editline' || $selected != $line->id) {
5567 // Product
5568 if (!empty($line->fk_product) && $line->fk_product > 0) {
5569 $product_static = new Product($this->db);
5570 $product_static->fetch($line->fk_product);
5571
5572 $product_static->ref = $line->ref; //can change ref in hook
5573 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5574
5575 $text = $product_static->getNomUrl(1);
5576
5577 // Define output language and label
5578 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5579 // @phan-suppress-next-line PhanUndeclaredProperty
5580 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5581 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5582 return;
5583 }
5584
5585 $prod = new Product($this->db);
5586 $prod->fetch($line->fk_product);
5587
5588 $outputlangs = $langs;
5589 $newlang = '';
5590 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5591 $newlang = GETPOST('lang_id', 'aZ09');
5592 }
5593 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5594 $newlang = $this->thirdparty->default_lang; // To use language of customer
5595 }
5596 if (!empty($newlang)) {
5597 $outputlangs = new Translate("", $conf);
5598 $outputlangs->setDefaultLang($newlang);
5599 }
5600
5601 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5602 } else {
5603 $label = $line->product_label;
5604 }
5605
5606 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5607 $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.
5608 }
5609
5610 // Recalculate unit price with tax if not defined
5611 if (empty((float) $line->subprice_ttc) && $line->qty) { // subprice_ttc may be not stored on old version or not defined for lines with no unit price (like a discount)
5612 // So we calculate an estimated value just to show something on screen
5613 if ($line->remise_percent != 100) {
5614 $line->subprice_ttc = (float) price2num($line->total_ttc / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5615 } else {
5616 // Other method is less accurate
5617 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5618 }
5619 }
5620 $line->pu_ttc = $line->subprice_ttc; // deprecated
5621
5622 // Output template part (modules that overwrite templates must declare this into descriptor)
5623 // Use global variables + $dateSelector + $seller and $buyer
5624 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5625
5626 $qty_shipped = 0;
5627 if (isset($this->expeditions[$line->id])) {
5628 $qty_shipped = $this->expeditions[$line->id];
5629 }
5630 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5631
5632 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5633 foreach ($dirtpls as $module => $reldir) {
5634 $res = 0;
5635 if (!empty($module)) {
5636 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5637 } else {
5638 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5639 }
5640 //var_dump($tpl);
5641 if (file_exists($tpl)) {
5642 if (empty($conf->file->strict_mode)) {
5643 $res = @include $tpl;
5644 } else {
5645 $res = include $tpl; // for debug
5646 }
5647 }
5648 if ($res) {
5649 break;
5650 }
5651 }
5652 }
5653
5654 // Line in update mode
5655 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5656 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5657
5658 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5659 $line->pu_ttc = $line->subprice_ttc; // deprecated
5660
5661 // Output template part (modules that overwrite templates must declare this into descriptor)
5662 // Use global variables + $dateSelector + $seller and $buyer
5663 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5664 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5665 foreach ($dirtpls as $module => $reldir) {
5666 if (!empty($module)) {
5667 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5668 } else {
5669 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5670 }
5671
5672 if (empty($conf->file->strict_mode)) {
5673 $res = @include $tpl;
5674 } else {
5675 $res = include $tpl; // for debug
5676 }
5677 if ($res) {
5678 break;
5679 }
5680 }
5681 }
5682 }
5683
5684
5685 /* This is to show array of line of details of source object */
5686
5687
5698 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5699 {
5700 global $langs, $hookmanager, $form, $action;
5701
5702 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5703 print '<tr class="liste_titre">';
5704 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5705 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5706 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5707 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5708 if (isModEnabled("multicurrency")) {
5709 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5710 }
5711 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5712 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5713 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5714 }
5715 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5716 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5717 print '<td class="center">';
5718 print $form->showCheckAddButtons('checkforselect', 1);
5719 print '</td>';
5720 print '</tr>';
5721 $i = 0;
5722
5723 if (!empty($this->lines)) {
5724 foreach ($this->lines as $line) {
5725 $reshook = 0;
5726 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5727 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5728 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5729 if (!empty($line->fk_parent_line)) {
5730 $parameters['fk_parent_line'] = $line->fk_parent_line;
5731 }
5732 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5733 }
5734 if (empty($reshook)) {
5735 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5736 }
5737
5738 $i++;
5739 }
5740 }
5741 }
5742
5756 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5757 {
5758 global $langs, $conf;
5759
5760 //var_dump($line);
5761 if (!empty($line->date_start)) {
5762 // @phan-suppress-next-line PhanUndeclaredProperty
5763 $date_start = $line->date_start;
5764 } else {
5765 $date_start = $line->date_debut_prevue;
5766 if ($line->date_debut_reel) {
5767 $date_start = $line->date_debut_reel;
5768 }
5769 }
5770 if (!empty($line->date_end)) {
5771 // @phan-suppress-next-line PhanUndeclaredProperty
5772 $date_end = $line->date_end;
5773 } else {
5774 $date_end = $line->date_fin_prevue;
5775 if ($line->date_fin_reel) {
5776 $date_end = $line->date_fin_reel;
5777 }
5778 }
5779
5780 // Set thevalue into ->tpl[] array.
5781 $this->tpl['id'] = $line->id;
5782
5783 $this->tpl['label'] = '';
5784 if (!empty($line->fk_parent_line)) {
5785 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5786 }
5787
5788 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5789 $discount = new DiscountAbsolute($this->db);
5790 if (property_exists($this, 'socid')) {
5791 $discount->fk_soc = $this->socid;
5792 $discount->socid = $this->socid;
5793 }
5794 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5795 } elseif (!empty($line->fk_product)) {
5796 $productstatic = new Product($this->db);
5797 $productstatic->id = $line->fk_product;
5798 $productstatic->ref = $line->ref;
5799 $productstatic->type = $line->fk_product_type;
5800 if (empty($productstatic->ref)) {
5801 $line->fetch_product();
5802 $productstatic = $line->product;
5803 }
5804
5805 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5806 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5807 // Dates
5808 if ($line->product_type == 1 && ($date_start || $date_end)) {
5809 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5810 }
5811 } else {
5812 $this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans('Service'), 'service', 'class="pictofixedwidth"') : img_object($langs->trans('Product'), 'product', 'class="pictofixedwidth"')));
5813 if (!empty($line->desc)) {
5814 $this->tpl['label'] .= $line->desc;
5815 } else {
5816 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5817 }
5818
5819 // Dates
5820 if ($line->product_type == 1 && ($date_start || $date_end)) {
5821 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5822 }
5823 }
5824
5825 if (!empty($line->desc)) {
5826 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5827 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5828 $discount = new DiscountAbsolute($this->db);
5829 $discount->fetch($line->fk_remise_except);
5830 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5831 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5832 $discount = new DiscountAbsolute($this->db);
5833 $discount->fetch($line->fk_remise_except);
5834 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5835 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5836 $discount = new DiscountAbsolute($this->db);
5837 $discount->fetch($line->fk_remise_except);
5838 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5839 } elseif ($line->desc == '(EXCESS PAID)') {
5840 $discount = new DiscountAbsolute($this->db);
5841 $discount->fetch($line->fk_remise_except);
5842 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5843 } else {
5844 $this->tpl['description'] = dol_trunc($line->desc, 60);
5845 }
5846 } else {
5847 $this->tpl['description'] = '&nbsp;';
5848 }
5849
5850 // VAT Rate
5851 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5852 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
5853 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5854 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5855 }
5856
5857 $this->tpl['price'] = price($line->subprice);
5858 $this->tpl['total_ht'] = price($line->total_ht);
5859 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5860 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5861 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5862 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
5863 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
5864 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
5865 }
5866 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
5867
5868 // Is the line strike or not
5869 $this->tpl['strike'] = 0;
5870 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5871 $this->tpl['strike'] = 1;
5872 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
5873 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
5874 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
5875 // Without the guard, printing origin lines from a PO into a vendor
5876 // invoice fatals (#37663). Other tpl files already follow this pattern.
5877 $this->tpl['strike'] = 1;
5878 }
5879
5880 // Output template part (modules that overwrite templates must declare this into descriptor)
5881 // Use global variables + $dateSelector + $seller and $buyer
5882 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5883 foreach ($dirtpls as $module => $reldir) {
5884 if (!empty($module)) {
5885 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5886 } else {
5887 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5888 }
5889
5890 if (empty($conf->file->strict_mode)) {
5891 $res = @include $tpl;
5892 } else {
5893 $res = include $tpl; // for debug
5894 }
5895 if ($res) {
5896 break;
5897 }
5898 }
5899 }
5900
5901
5902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5914 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
5915 {
5916 // phpcs:enable
5917 global $user;
5918 $this->db->begin();
5919
5920 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5921 $sql .= "resource_id";
5922 $sql .= ", resource_type";
5923 $sql .= ", element_id";
5924 $sql .= ", element_type";
5925 $sql .= ", busy";
5926 $sql .= ", mandatory";
5927 $sql .= ") VALUES (";
5928 $sql .= ((int) $resource_id);
5929 $sql .= ", '".$this->db->escape($resource_type)."'";
5930 $sql .= ", '".$this->db->escape((string) $this->id)."'";
5931 $sql .= ", '".$this->db->escape($this->element)."'";
5932 $sql .= ", '".$this->db->escape((string) $busy)."'";
5933 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
5934 $sql .= ")";
5935
5936 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5937 if ($this->db->query($sql)) {
5938 if (!$notrigger) {
5939 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
5940 if ($result < 0) {
5941 $this->db->rollback();
5942 return -1;
5943 }
5944 }
5945 $this->db->commit();
5946 return 1;
5947 } else {
5948 $this->error = $this->db->lasterror();
5949 $this->db->rollback();
5950 return 0;
5951 }
5952 }
5953
5954 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5963 public function delete_resource($rowid, $element = '', $notrigger = 0)
5964 {
5965 // phpcs:enable
5966 global $user;
5967
5968 $this->db->begin();
5969
5970 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
5971 $sql .= " WHERE rowid = ".((int) $rowid);
5972
5973 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5974
5975 $resql = $this->db->query($sql);
5976 if (!$resql) {
5977 $this->error = $this->db->lasterror();
5978 $this->db->rollback();
5979 return -1;
5980 } else {
5981 if (!$notrigger) {
5982 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
5983 if ($result < 0) {
5984 $this->db->rollback();
5985 return -1;
5986 }
5987 }
5988 $this->db->commit();
5989 return 1;
5990 }
5991 }
5992
5993
5999 public function __clone()
6000 {
6001 // Force a copy of this->lines, otherwise it will point to same object.
6002 if (isset($this->lines) && is_array($this->lines)) {
6003 $nboflines = count($this->lines);
6004 for ($i = 0; $i < $nboflines; $i++) {
6005 if (is_object($this->lines[$i])) {
6006 $this->lines[$i] = clone $this->lines[$i];
6007 }
6008 }
6009 }
6010 }
6011
6025 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6026 {
6027 global $conf, $langs, $hookmanager, $action;
6028
6029 $srctemplatepath = '';
6030
6031 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6032 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6033
6034 if (!empty($reshook)) {
6035 return $reshook;
6036 }
6037
6038 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6039
6040 if (empty($modele)) {
6041 $this->error = 'BadValueForParameterModele';
6042 return -1;
6043 }
6044
6045 // Increase limit for PDF build
6046 $err = error_reporting();
6047 error_reporting(0);
6048 @set_time_limit(120);
6049 error_reporting($err);
6050
6051 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6052 $tmp = explode(':', $modele, 2);
6053 $saved_model = $modele;
6054 if (!empty($tmp[1])) {
6055 $modele = $tmp[0];
6056 $srctemplatepath = $tmp[1];
6057 }
6058
6059 // Search template files
6060 $file = '';
6061 $classname = '';
6062 $filefound = '';
6063 $dirmodels = array('/');
6064 if (is_array($conf->modules_parts['models'])) {
6065 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6066 }
6067 foreach ($dirmodels as $reldir) {
6068 foreach (array('doc', 'pdf') as $prefix) {
6069 if (in_array(get_class($this), array('Adherent'))) {
6070 // Member module use prefix_modele.class.php
6071 $file = $prefix."_".$modele.".class.php";
6072 } else {
6073 // Other module use prefix_modele.modules.php
6074 $file = $prefix."_".$modele.".modules.php";
6075 }
6076
6077 $file = dol_sanitizeFileName($file);
6078
6079 // We check if the file exists
6080 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6081 if (file_exists($file)) {
6082 $filefound = $file;
6083 $classname = $prefix.'_'.$modele;
6084 break;
6085 }
6086 }
6087 if ($filefound) {
6088 break;
6089 }
6090 }
6091
6092 if ($filefound === '' || $classname === '') {
6093 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6094 $this->errors[] = $this->error;
6095 dol_syslog($this->error, LOG_ERR);
6096 return -1;
6097 }
6098
6099 // Sanitize $filefound
6100 $filefound = dol_sanitizePathName($filefound);
6101
6102 // If generator was found
6103 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6104
6105 require_once $filefound;
6106
6107 $obj = new $classname($this->db);
6108
6109 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6110 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6111 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6112 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6113 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6114 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6115 // ModelePDFStock|ModelePDFStockTransfer|
6116 // ModeleDon|ModelePDFTask|
6117 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6118 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6119 '@phan-var-force ModelePDFMember $obj';
6120 // '@phan-var-force ModelePDFMember|ModeleBarCode|ModeleDon|ModeleExports|ModeleImports|ModelePDFAsset|ModelePDFContract|ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|ModelePDFRecruitmentJobPosition|ModelePDFStock|ModelePDFStockTransfer|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePDFTask|ModelePDFTicket|ModelePDFUser|ModelePDFUserGroup|ModelePdfExpedition|ModelePdfReception|ModeleThirdPartyDoc $obj';
6121
6122 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6123 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6124 $varfortemplatedir = $obj->scandir;
6125 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6126 $dirtoscan = getDolGlobalString($varfortemplatedir);
6127
6128 $listoffiles = array();
6129
6130 // Now we add first model found in directories scanned
6131 $listofdir = explode(',', $dirtoscan);
6132 foreach ($listofdir as $key => $tmpdir) {
6133 $tmpdir = trim($tmpdir);
6134 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6135 if (!$tmpdir) {
6136 unset($listofdir[$key]);
6137 continue;
6138 }
6139 if (is_dir($tmpdir)) {
6140 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6141 if (count($tmpfiles)) {
6142 $listoffiles = array_merge($listoffiles, $tmpfiles);
6143 }
6144 }
6145 }
6146
6147 if (count($listoffiles)) {
6148 foreach ($listoffiles as $record) {
6149 $srctemplatepath = $record['fullname'];
6150 break;
6151 }
6152 }
6153 }
6154
6155 if (empty($srctemplatepath)) {
6156 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6157 return -1;
6158 }
6159 }
6160
6161 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6162 if (!dol_is_file($srctemplatepath)) {
6163 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6164 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6165 return -1;
6166 }
6167 }
6168
6169 // We save charset_output to restore it because write_file can change it if needed for
6170 // output format that does not support UTF8.
6171 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6172
6173 // update model_pdf in object
6174 $this->model_pdf = $saved_model;
6175
6176 if ($obj instanceof ModelePDFMember) {
6177 if ($this instanceof Adherent) {
6178 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6179 } else {
6180 $resultwritefile = -1;
6181 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6182 }
6183 } elseif ($obj instanceof ModeleDon) {
6184 // Only 3 arguments
6185 if ($this instanceof Don) {
6186 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6187 } else {
6188 $resultwritefile = -1;
6189 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6190 }
6191 } else {
6192 // TODO: Try to set type above again
6193 '@phan-var-force ModeleBarCode|ModeleExports|ModeleImports|ModelePDFAsset|ModelePDFContract|ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|ModelePDFRecruitmentJobPosition|ModelePDFStock|ModelePDFStockTransfer|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePDFTask|ModelePDFTicket|ModelePDFUser|ModelePDFUserGroup|ModelePdfExpedition|ModelePdfReception|ModeleThirdPartyDoc $obj';
6194 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams); // @phan-suppress-line-PhanTypeMismatchArgument
6195 }
6196 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6197
6198
6199 if ($resultwritefile > 0) {
6200 $outputlangs->charset_output = $sav_charset_output;
6201
6202 // We delete old preview
6203 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6204 dol_delete_preview($this);
6205
6206 // Index file in database
6207 if (!empty($obj->result['fullpath'])) {
6208 $destfull = $obj->result['fullpath'];
6209
6210 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6211 $update_main_doc_field = 0;
6212 if (!empty($obj->update_main_doc_field)) {
6213 $update_main_doc_field = 1;
6214 }
6215
6216 // Check that the file exists, before indexing it.
6217 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6218 if (dol_is_file($destfull)) {
6219 $this->indexFile($destfull, $update_main_doc_field);
6220 }
6221 } else {
6222 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);
6223 }
6224
6225 // Success in building document. We build meta file.
6226 dol_meta_create($this);
6227
6228 $this->warnings = $obj->warnings;
6229
6230 return 1;
6231 } else {
6232 $outputlangs->charset_output = $sav_charset_output;
6233 $this->error = $obj->error;
6234 $this->errors = $obj->errors;
6235 $this->warnings = $obj->warnings;
6236 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
6237 return -1;
6238 }
6239 }
6240
6250 public function indexFile($destfull, $update_main_doc_field)
6251 {
6252 global $conf, $user;
6253
6254 $upload_dir = dirname($destfull);
6255 $destfile = basename($destfull);
6256 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6257
6258 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6259 $filename = basename($destfile);
6260 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6261 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6262
6263 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6264 $ecmfile = new EcmFiles($this->db);
6265 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6266
6267 // Set the public "share" key
6268 $setsharekey = false;
6269 if (
6270 !empty($this->TRIGGER_PREFIX)
6271 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6272 ) {
6273 $setsharekey = true;
6274 }
6275 // TODO Remove case covered by trigger prefix
6276 if ($this->element == 'propal' || $this->element == 'proposal') {
6277 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6278 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6279 }
6280 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6281 $setsharekey = true;
6282 }
6283 }
6284 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6285 $setsharekey = true;
6286 }
6287 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6288 $setsharekey = true;
6289 }
6290 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6291 $setsharekey = true;
6292 }
6293 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6294 $setsharekey = true;
6295 }
6296 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6297 $setsharekey = true;
6298 }
6299
6300 if ($setsharekey) {
6301 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6302 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6303 $ecmfile->share = getRandomPassword(true);
6304 }
6305 }
6306
6307 if ($result > 0) {
6308 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6309 $ecmfile->fullpath_orig = '';
6310 $ecmfile->gen_or_uploaded = 'generated';
6311 $ecmfile->description = ''; // indexed content
6312 $ecmfile->keywords = ''; // keyword content
6313 $ecmfile->src_object_type = $this->table_element;
6314 $ecmfile->src_object_id = $this->id;
6315 $result = $ecmfile->update($user);
6316 if ($result < 0) {
6317 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6318 return -1;
6319 }
6320 } else {
6321 $ecmfile->entity = $conf->entity;
6322 $ecmfile->filepath = $rel_dir;
6323 $ecmfile->filename = $filename;
6324 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6325 $ecmfile->fullpath_orig = '';
6326 $ecmfile->gen_or_uploaded = 'generated';
6327 $ecmfile->description = ''; // indexed content
6328 $ecmfile->keywords = ''; // keyword content
6329 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6330 $ecmfile->src_object_id = $this->id;
6331
6332 $result = $ecmfile->create($user);
6333 if ($result < 0) {
6334 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6335 return -1;
6336 }
6337 }
6338
6339 /*$this->result['fullname']=$destfull;
6340 $this->result['filepath']=$ecmfile->filepath;
6341 $this->result['filename']=$ecmfile->filename;*/
6342 //var_dump($obj->update_main_doc_field);exit;
6343
6344 if ($update_main_doc_field && !empty($this->table_element)) {
6345 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6346 $sql .= " WHERE rowid = ".((int) $this->id);
6347
6348 $resql = $this->db->query($sql);
6349 if (!$resql) {
6350 dol_print_error($this->db);
6351 return -1;
6352 } else {
6353 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6354 }
6355 }
6356 }
6357
6358 return 1;
6359 }
6360
6369 public function addThumbs($file, $quality = 50)
6370 {
6371 $file_osencoded = dol_osencode($file);
6372
6373 if (file_exists($file_osencoded)) {
6374 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6375
6376 $tmparraysize = getDefaultImageSizes();
6377 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6378 $maxheightsmall = $tmparraysize['maxheightsmall'];
6379 $maxwidthmini = $tmparraysize['maxwidthmini'];
6380 $maxheightmini = $tmparraysize['maxheightmini'];
6381 //$quality = $tmparraysize['quality'];
6382
6383 // Create small thumbs for company (Ratio is near 16/9)
6384 // Used on logon for example
6385 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6386
6387 // Create mini thumbs for company (Ratio is near 16/9)
6388 // Used on menu or for setup page for example
6389 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6390 }
6391 }
6392
6400 public function delThumbs($file)
6401 {
6402 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6403 dol_delete_file($imgThumbName);
6404 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6405 dol_delete_file($imgThumbName);
6406 }
6407
6408
6409 /* Functions common to commonobject and commonobjectline */
6410
6411 /* For default values */
6412
6426 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6427 {
6428 /* TODO Remove this. Must use now something like:
6429 $note_private = GETPOST('note_private', 'restricthtml');
6430 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6431 $note_private = $objectsrc->note_private;
6432 }
6433 */
6434
6435 // If param here has been posted, we use this value first.
6436 if (GETPOSTISSET($fieldname)) {
6437 return GETPOST($fieldname, $type, 3);
6438 }
6439
6440 if (isset($alternatevalue)) {
6441 return $alternatevalue;
6442 }
6443
6444 $newelement = $this->element;
6445 if ($newelement == 'facture') {
6446 $newelement = 'invoice';
6447 }
6448 if ($newelement == 'commande') {
6449 $newelement = 'order';
6450 }
6451 if (empty($newelement)) {
6452 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6453 return '';
6454 }
6455
6456 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6457 //var_dump($keyforfieldname);
6458 if (getDolGlobalString($keyforfieldname)) {
6459 return getDolGlobalString($keyforfieldname);
6460 }
6461
6462 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6463 // store content into $conf->cache['overwrite_default']
6464
6465 return '';
6466 }
6467
6468
6469 /* Functions for data in other language */
6470
6471
6481 {
6482 // To avoid SQL errors. Probably not the better solution though
6483 if (!$this->element) {
6484 return 0;
6485 }
6486 if (!($this->id > 0)) {
6487 return 0;
6488 }
6489 if (is_array($this->array_languages)) {
6490 return 1;
6491 }
6492
6493 $this->array_languages = array();
6494
6495 $element = $this->element;
6496 if ($element == 'categorie') {
6497 $element = 'categories'; // For compatibility
6498 }
6499
6500 // Request to get translation values for object
6501 $sql = "SELECT rowid, property, lang , value";
6502 $sql .= " FROM ".$this->db->prefix()."object_lang";
6503 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6504 $sql .= " AND fk_object = ".((int) $this->id);
6505
6506 $resql = $this->db->query($sql);
6507 if ($resql) {
6508 $numrows = $this->db->num_rows($resql);
6509 if ($numrows) {
6510 $i = 0;
6511 while ($i < $numrows) {
6512 $obj = $this->db->fetch_object($resql);
6513 $key = $obj->property;
6514 $value = $obj->value;
6515 $codelang = $obj->lang;
6516 $type = $this->fields[$key]['type'];
6517
6518 // we can add this attribute to object
6519 if (preg_match('/date/', $type)) {
6520 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6521 } else {
6522 $this->array_languages[$key][$codelang] = $value;
6523 }
6524
6525 $i++;
6526 }
6527 }
6528
6529 $this->db->free($resql);
6530
6531 if ($numrows) {
6532 return $numrows;
6533 } else {
6534 return 0;
6535 }
6536 } else {
6537 dol_print_error($this->db);
6538 return -1;
6539 }
6540 }
6541
6549 public function setValuesForExtraLanguages($onlykey = '')
6550 {
6551 // Get extra fields
6552 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6553 $tmparray = explode('-', $postfieldkey);
6554 if ($tmparray[0] != 'field') {
6555 continue;
6556 }
6557
6558 $element = $tmparray[1];
6559 $key = $tmparray[2];
6560 $codelang = $tmparray[3];
6561 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6562
6563 if (!empty($onlykey) && $key != $onlykey) {
6564 continue;
6565 }
6566 if ($element != $this->element) {
6567 continue;
6568 }
6569
6570 $key_type = $this->fields[$key]['type'];
6571
6572 $enabled = 1;
6573 if (isset($this->fields[$key]['enabled'])) {
6574 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6575 }
6576 /*$perms = 1;
6577 if (isset($this->fields[$key]['perms']))
6578 {
6579 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6580 }*/
6581 if (empty($enabled)) {
6582 continue;
6583 }
6584 //if (empty($perms)) continue;
6585
6586 if (in_array($key_type, array('date'))) {
6587 // Clean parameters
6588 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6589 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6590 } elseif (in_array($key_type, array('datetime'))) {
6591 // Clean parameters
6592 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6593 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6594 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6595 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6596 if (!empty($value_arr)) {
6597 $value_key = implode(',', $value_arr);
6598 } else {
6599 $value_key = '';
6600 }
6601 } elseif (in_array($key_type, array('price', 'double'))) {
6602 $value_arr = GETPOST($postfieldkey, 'alpha');
6603 $value_key = price2num($value_arr);
6604 } else {
6605 $value_key = GETPOST($postfieldkey);
6606 if (in_array($key_type, array('link')) && $value_key == '-1') {
6607 $value_key = '';
6608 }
6609 }
6610
6611 $this->array_languages[$key][$codelang] = $value_key;
6612
6613 /*if ($nofillrequired) {
6614 $langs->load('errors');
6615 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6616 return -1;
6617 }*/
6618 }
6619
6620 return 1;
6621 }
6622
6623
6624 /* Functions for extrafields */
6625
6632 public function fetchNoCompute($id)
6633 {
6634 global $conf;
6635
6636 $savDisableCompute = $conf->disable_compute;
6637 $conf->disable_compute = 1;
6638
6639 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6640
6641 $conf->disable_compute = $savDisableCompute;
6642
6643 return $ret;
6644 }
6645
6646 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6656 public function fetch_optionals($rowid = null, $optionsArray = null)
6657 {
6658 // phpcs:enable
6659 global $conf, $extrafields;
6660
6661 if (empty($rowid)) {
6662 $rowid = $this->id;
6663 }
6664 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6665 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6666 }
6667
6668 // To avoid SQL errors. Probably not the better solution though
6669 if (!$this->table_element) {
6670 return 0;
6671 }
6672
6673 $this->array_options = array();
6674
6675 if (!is_array($optionsArray)) {
6676 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6677 if (!isset($extrafields) || !is_object($extrafields)) {
6678 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6679 $extrafields = new ExtraFields($this->db);
6680 }
6681
6682 // Load array of extrafields for elementype = $this->table_element
6683 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6684 $extrafields->fetch_name_optionals_label($this->table_element);
6685 }
6686 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6687 } else {
6688 global $extrafields;
6689 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6690 }
6691
6692 $table_element = $this->table_element;
6693 if ($table_element == 'categorie') {
6694 $table_element = 'categories'; // For compatibility
6695 }
6696
6697 // Request to get complementary values
6698 if (is_array($optionsArray) && count($optionsArray) > 0) {
6699 $sql = "SELECT rowid";
6700 foreach ($optionsArray as $name => $label) {
6701 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6702 $sql .= ", ".$this->db->sanitize($name);
6703 }
6704 // use geo sql fonction to read as text
6705 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6706 // TODO Add an abstraction method in the database driver
6707 $sql .= ", ST_AsWKT(".$name.") as ".$this->db->sanitize($name);
6708 }
6709 }
6710 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6711 $sql .= " WHERE fk_object = ".((int) $rowid);
6712
6713 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6714 $resql = $this->db->query($sql);
6715 if ($resql) {
6716 $numrows = $this->db->num_rows($resql);
6717 if ($numrows) {
6718 $tab = $this->db->fetch_array($resql);
6719
6720 foreach ($tab as $key => $value) {
6721 // 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)
6722 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6723 // we can add this attribute to object
6724 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6725 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6726 $this->array_options["options_".$key] = $this->db->jdate($value);
6727 } else {
6728 $this->array_options["options_".$key] = $value;
6729 }
6730
6731 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6732 }
6733 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6734 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6735 $this->array_options["options_".$key] = dolDecrypt($value);
6736 }
6737 }
6738 }
6739 } else {
6744 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6745 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6746 $this->array_options['options_' . $key] = null;
6747 }
6748 }
6749 }
6750
6751 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6752 // in the element's extrafields table)
6753 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6754 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6755 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6756 //var_dump($conf->disable_compute);
6757 if (empty($conf->disable_compute)) {
6758 global $objectoffield; // We set a global variable to $objectoffield so
6759 $objectoffield = $this; // we can use it inside computed formula
6760 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6761 }
6762 }
6763 }
6764 }
6765
6766 $this->db->free($resql);
6767
6768 if ($numrows) {
6769 return $numrows;
6770 } else {
6771 return 0;
6772 }
6773 } else {
6774 $this->errors[] = $this->db->lasterror;
6775 return -1;
6776 }
6777 }
6778 return 0;
6779 }
6780
6787 public function deleteExtraFields()
6788 {
6789 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6790 return 0;
6791 }
6792
6793 $this->db->begin();
6794
6795 $table_element = $this->table_element;
6796 if ($table_element == 'categorie') {
6797 $table_element = 'categories'; // For compatibility
6798 }
6799
6800 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6801
6802 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6803
6804 $resql = $this->db->query($sql_del);
6805 if (!$resql) {
6806 $this->error = $this->db->lasterror();
6807 $this->db->rollback();
6808 return -1;
6809 } else {
6810 $this->db->commit();
6811 return 1;
6812 }
6813 }
6814
6825 public function insertExtraFields($trigger = '', $userused = null)
6826 {
6827 global $langs, $user;
6828
6829 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6830 return 0;
6831 }
6832
6833 if (empty($userused)) {
6834 $userused = $user;
6835 }
6836
6837 $error = 0;
6838
6839 // Check parameters
6840 $langs->load('admin');
6841 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6842 $extrafields = new ExtraFields($this->db);
6843 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6844
6845 // Eliminate copied source object extra fields that do not exist in target object
6846 $new_array_options = array();
6847 foreach ($this->array_options as $key => $value) {
6848 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6849 $new_array_options[$key] = $value;
6850 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6851 $new_array_options['options_'.$key] = $value;
6852 }
6853 }
6854
6855 foreach ($new_array_options as $key => $value) {
6856 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6857 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6858 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6859 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6860 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6861 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6862 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6863 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
6864
6865 // If we clone, we have to clean unique extrafields to prevent duplicates.
6866 // If we clone, we have to clean extrafields having "empty on clone" option on.
6867 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6868 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
6869 $new_array_options[$key] = null;
6870 continue;
6871 }
6872
6873 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6874 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6875 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6876 $new_array_options[$key] = null;
6877 continue;
6878 }
6879
6880 // Similar code than into insertExtraFields
6881 if ($attributeRequired) {
6882 $v = $this->array_options[$key];
6883 if (ExtraFields::isEmptyValue($v, $attributeType)) {
6884 $langs->load("errors");
6885 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6886 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6887 return -1;
6888 }
6889 }
6890
6891 if (!empty($attrfieldcomputed)) {
6892 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6893 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
6894 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
6895 $new_array_options[$key] = $value;
6896 } else {
6897 $new_array_options[$key] = null;
6898 }
6899 continue;
6900 }
6901
6902 switch ($attributeType) {
6903 case 'int':
6904 case 'duration':
6905 case 'stars':
6906 if (!is_numeric($value) && $value != '') {
6907 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6908 return -1;
6909 } elseif ($value === '') {
6910 $new_array_options[$key] = null;
6911 }
6912 break;
6913 case 'boolean':
6914 if ($value === '' || $value === false || $value === null) {
6915 $new_array_options[$key] = null;
6916 }
6917 break;
6918 case 'price':
6919 case 'double':
6920 $value = price2num($value);
6921 if (!is_numeric($value) && $value != '') {
6922 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6923 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6924 return -1;
6925 } elseif ($value == '') {
6926 $value = null;
6927 }
6928 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6929 $new_array_options[$key] = $value;
6930 break;
6931 /*case 'select': // Not required, we chose value='0' for undefined values
6932 if ($value=='-1')
6933 {
6934 $this->array_options[$key] = null;
6935 }
6936 break;*/
6937 case 'password':
6938 $algo = '';
6939 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6940 // If there is an encryption choice, we use it to encrypt data before insert
6941 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6942 $algo = reset($tmparrays);
6943 if ($algo != '') {
6944 //global $action; // $action may be 'create', 'update', 'update_extras'...
6945 //var_dump($action);
6946 //var_dump($this->oldcopy);exit;
6947 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
6948 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6949 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6950 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6951 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6952 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6953 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6954 } else {
6955 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6956 }
6957 } else {
6958 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6959 }
6960 } else {
6961 // If value has changed
6962 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6963 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6964 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6965 } else {
6966 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6967 }
6968 } else {
6969 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
6970 }
6971 }
6972 } else {
6973 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6974 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6975 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
6976 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6977 } else {
6978 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6979 }
6980 }
6981 } else {
6982 // No encryption
6983 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6984 }
6985 } else { // Common usage
6986 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6987 }
6988 break;
6989 case 'date':
6990 case 'datetime':
6991 // If data is a string instead of a timestamp, we convert it
6992 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6993 $this->array_options[$key] = strtotime($this->array_options[$key]);
6994 }
6995 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6996 break;
6997 case 'datetimegmt':
6998 // If data is a string instead of a timestamp, we convert it
6999 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7000 $this->array_options[$key] = strtotime($this->array_options[$key]);
7001 }
7002 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7003 break;
7004 case 'link':
7005 $param_list = array_keys($attributeParam['options']);
7006 // 0 : ObjectName
7007 // 1 : classPath
7008 $InfoFieldList = explode(":", $param_list[0]);
7009 dol_include_once($InfoFieldList[1]);
7010 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7011 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7012 $new_array_options[$key] = '';
7013 } elseif ($value) {
7014 $object = new $InfoFieldList[0]($this->db);
7015 '@phan-var-force CommonObject $object';
7016
7017 $objectId = 0;
7018
7019 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7020 if (is_numeric($value)) {
7021 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7022 } else {
7023 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7024 }
7025
7026 $obj = $this->db->getRow($sqlFetchObject);
7027
7028 if ($obj !== false) {
7029 $objectId = $obj->rowid;
7030 $res = 1;
7031 } else {
7032 $res = -1;
7033 }
7034
7035 if ($res > 0) {
7036 $new_array_options[$key] = $objectId;
7037 } else {
7038 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7039 return -1;
7040 }
7041 }
7042 } else {
7043 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7044 }
7045 break;
7046 case 'checkbox':
7047 case 'chkbxlst':
7048 if (is_array($this->array_options[$key])) {
7049 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7050 } else {
7051 $new_array_options[$key] = $this->array_options[$key];
7052 }
7053 break;
7054 }
7055 }
7056
7057 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7058 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7059
7060 foreach ($new_array_options as $key => $newValue) {
7061 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7062 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7063 if ($attributeType === 'separate') {
7064 // separator extrafields don't have data per object so they don't have a comlumn in the database
7065 continue;
7066 }
7067 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7068 // Add field of attribute
7069 if (! $geoDataType) {
7070 // not a geodata type
7071 if ($newValue != '') {
7072 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7073 } else {
7074 $sqlColumnValues[$attributeKey] = 'null';
7075 }
7076 continue;
7077 }
7078 if (empty($newValue)) {
7079 $sqlColumnValues[$attributeKey] = 'null';
7080 continue;
7081 }
7082 if (preg_match('/error/i', $newValue)) {
7083 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7084 $sqlColumnValues[$attributeKey] = 'null';
7085 continue;
7086 }
7087
7088 // Geodata type: Text must be a WKT string. Examples:
7089 // - point => "POINT(15 20)"
7090 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7091 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7092 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7093 $sqlColumnValues[$key] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')";
7094 }
7095
7096 $this->db->begin();
7097
7098 $table_element = $this->table_element;
7099 if ($table_element == 'categorie') {
7100 $table_element = 'categories'; // For compatibility
7101 }
7102 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7103
7104 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7105
7106 $linealreadyfound = 0;
7107 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7108 $resql = $this->db->query($sql);
7109 if ($resql) {
7110 $tmpobj = $this->db->fetch_object($resql);
7111 if ($tmpobj) {
7112 $linealreadyfound = $tmpobj->nb;
7113 }
7114 }
7115
7116 // if the extrafields row already exists for the object, we update it
7117 if ($linealreadyfound) {
7118 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7119 $sqlColumnValueString = implode(
7120 ',',
7125 array_map(function ($key) use ($sqlColumnValues) {
7126 return "{$key} = {$sqlColumnValues[$key]}";
7127 }, array_keys($sqlColumnValues))
7128 );
7129 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7130 } else {
7131 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7132 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7133 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7134 if (isset($sqlColumnValues[$key])) {
7135 // extrafield value already provided: no need to add it
7136 continue;
7137 }
7138 // default value: empty string, except for 'int', 'double' and 'price'
7139 $sqlColumnValues[$key] = "''";
7140 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7141 $sqlColumnValues[$key] = 0;
7142 }
7143 }
7144 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7145 $sqlValues = implode(',', array_values($sqlColumnValues));
7146 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7147 }
7148
7149 $resql = $this->db->query($sql);
7150 if (!$resql) {
7151 $this->error = $this->db->lasterror();
7152 $error++;
7153 }
7154
7155 // Update also the user of last modification in parent table
7156 if (!$error && !empty($this->fields['fk_user_modif'])) {
7157 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7158 $sql .= " SET fk_user_modif = ".(int) $user->id;
7159 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7160 $this->db->query($sql);
7161 }
7162
7163 if (!$error && $trigger) {
7164 // Call trigger
7165 $this->context = array('extrafieldaddupdate' => 1);
7166 $result = $this->call_trigger($trigger, $userused);
7167 if ($result < 0) {
7168 $error++;
7169 }
7170 // End call trigger
7171 }
7172
7173 if ($error) {
7174 $this->db->rollback();
7175 return -1;
7176 } else {
7177 $this->db->commit();
7178 return 1;
7179 }
7180 }
7181
7193 public function insertExtraLanguages($trigger = '', $userused = null)
7194 {
7195 global $langs, $user;
7196
7197 if (empty($userused)) {
7198 $userused = $user;
7199 }
7200
7201 $error = 0;
7202
7203 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7204 return 0; // For avoid conflicts if trigger used
7205 }
7206
7207 if (is_array($this->array_languages)) {
7208 $new_array_languages = $this->array_languages;
7209
7210 foreach ($new_array_languages as $key => $value) {
7211 $attributeKey = $key;
7212 $attributeType = $this->fields[$attributeKey]['type'];
7213 $attributeLabel = $this->fields[$attributeKey]['label'];
7214
7215 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7216 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7217
7218 switch ($attributeType) {
7219 case 'int':
7220 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7221 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7222 return -1;
7223 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7224 $new_array_languages[$key] = null;
7225 }
7226 break;
7227 case 'double':
7228 $value = price2num((string) $value);
7229 if (!is_numeric($value) && $value != '') {
7230 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7231 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7232 return -1;
7233 } elseif ($value == '') {
7234 $new_array_languages[$key] = null;
7235 } else {
7236 $new_array_languages[$key] = $value;
7237 }
7238 break;
7239 /*case 'select': // Not required, we chose value='0' for undefined values
7240 if ($value=='-1')
7241 {
7242 $this->array_options[$key] = null;
7243 }
7244 break;*/
7245 }
7246 }
7247
7248 $this->db->begin();
7249
7250 $table_element = $this->table_element;
7251 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7252 $table_element = 'categories'; // For compatibility
7253 }
7254
7255 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7256
7257 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7258 foreach ($langcodearray as $langcode => $value) {
7259 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7260 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7261 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7262 $this->db->query($sql_del);
7263
7264 if ($value !== '') {
7265 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7266 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7267 $sql .= ")";
7268
7269 $resql = $this->db->query($sql);
7270 if (!$resql) {
7271 $this->error = $this->db->lasterror();
7272 $error++;
7273 break;
7274 }
7275 }
7276 }
7277 }
7278
7279 if (!$error && $trigger) {
7280 // Call trigger
7281 $this->context = array('extralanguagesaddupdate' => 1);
7282 $result = $this->call_trigger($trigger, $userused);
7283 if ($result < 0) {
7284 $error++;
7285 }
7286 // End call trigger
7287 }
7288
7289 if ($error) {
7290 $this->db->rollback();
7291 return -1;
7292 } else {
7293 $this->db->commit();
7294 return 1;
7295 }
7296 } else {
7297 return 0;
7298 }
7299 }
7300
7311 public function updateExtraField($key, $trigger = null, $userused = null)
7312 {
7313 global $langs, $user, $hookmanager;
7314
7315 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7316 return 0;
7317 }
7318
7319 if (empty($userused)) {
7320 $userused = $user;
7321 }
7322
7323 $error = 0;
7324
7325 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7326 // Check parameters
7327 $langs->load('admin');
7328 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7329 $extrafields = new ExtraFields($this->db);
7330 $extrafields->fetch_name_optionals_label($this->table_element);
7331
7332 $value = $this->array_options["options_".$key];
7333
7334 $attributeKey = $key;
7335 $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
7336 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
7337 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
7338 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
7339 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7340 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
7341
7342 // Similar code than into insertExtraFields
7343 if ($attributeRequired) {
7344 $mandatorypb = false;
7345 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7346 $mandatorypb = true;
7347 }
7348 if ($this->array_options["options_".$key] === '') {
7349 $mandatorypb = true;
7350 }
7351 if ($mandatorypb) {
7352 $langs->load("errors");
7353 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7354 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7355 return -1;
7356 }
7357 }
7358
7359 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7360 $new_array_options = $this->array_options;
7361
7362 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7363 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7364 if (!empty($attrfieldcomputed)) {
7365 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7366 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7367 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7368
7369 $new_array_options["options_".$key] = $value;
7370
7371 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7372 } else {
7373 $new_array_options["options_".$key] = null;
7374
7375 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7376 }
7377 }
7378
7379 switch ($attributeType) {
7380 case 'int':
7381 if (!is_numeric($value) && $value != '') {
7382 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7383 return -1;
7384 } elseif ($value === '') {
7385 $new_array_options["options_".$key] = null;
7386
7387 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7388 }
7389 break;
7390 case 'price':
7391 case 'double':
7392 $value = price2num($value);
7393 if (!is_numeric($value) && $value != '') {
7394 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7395 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7396 return -1;
7397 } elseif ($value === '') {
7398 $value = null;
7399 }
7400 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7401 $new_array_options["options_".$key] = $value;
7402
7403 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7404 break;
7405 /*case 'select': // Not required, we chose value='0' for undefined values
7406 if ($value=='-1')
7407 {
7408 $new_array_options["options_".$key] = $value;
7409
7410 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7411 }
7412 break;*/
7413 case 'password':
7414 $algo = '';
7415 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7416 // If there is an encryption choice, we use it to encrypt data before insert
7417 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7418 $algo = reset($tmparrays);
7419 if ($algo != '') {
7420 //global $action; // $action may be 'create', 'update', 'update_extras'...
7421 //var_dump($action);
7422 //var_dump($this->oldcopy);exit;
7423 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7424 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
7425 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7426 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.
7427 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7428 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7429 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7430 } else {
7431 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7432 }
7433 } else {
7434 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7435 }
7436 } else {
7437 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7438 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7439 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7440 } else {
7441 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7442 }
7443 } else {
7444 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7445 }
7446 }
7447 } else {
7448 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7449 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7450 } else {
7451 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7452 }
7453 }
7454 } else {
7455 // No encryption
7456 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7457 }
7458 } else { // Common usage
7459 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7460 }
7461
7462 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7463 break;
7464 case 'date':
7465 case 'datetime':
7466 if (empty($this->array_options["options_".$key])) {
7467 $new_array_options["options_".$key] = null;
7468
7469 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7470 } else {
7471 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7472 }
7473 break;
7474 case 'datetimegmt':
7475 if (empty($this->array_options["options_".$key])) {
7476 $new_array_options["options_".$key] = null;
7477
7478 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7479 } else {
7480 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7481 }
7482 break;
7483 case 'boolean':
7484 if (empty($this->array_options["options_".$key])) {
7485 $new_array_options["options_".$key] = null;
7486
7487 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7488 }
7489 break;
7490 case 'link':
7491 if ($this->array_options["options_".$key] === '') {
7492 $new_array_options["options_".$key] = null;
7493
7494 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7495 }
7496 break;
7497 /*
7498 case 'link':
7499 $param_list = array_keys($attributeParam['options']);
7500 // 0 : ObjectName
7501 // 1 : classPath
7502 $InfoFieldList = explode(":", $param_list[0]);
7503 dol_include_once($InfoFieldList[1]);
7504 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7505 {
7506 if ($value == '-1') // -1 is key for no defined in combo list of objects
7507 {
7508 $new_array_options[$key] = '';
7509 } elseif ($value) {
7510 $object = new $InfoFieldList[0]($this->db);
7511 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7512 else $res = $object->fetch(0, $value); // For compatibility
7513
7514 if ($res > 0) $new_array_options[$key] = $object->id;
7515 else {
7516 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7517 $this->db->rollback();
7518 return -1;
7519 }
7520 }
7521 } else {
7522 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7523 }
7524 break;
7525 */
7526 case 'checkbox':
7527 case 'chkbxlst':
7528 $new_array_options = array();
7529 if (is_array($this->array_options["options_".$key])) {
7530 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7531 } else {
7532 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7533 }
7534
7535 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7536 break;
7537 }
7538
7539 $this->db->begin();
7540
7541 $linealreadyfound = 0;
7542
7543 // 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)
7544 $table_element = $this->table_element;
7545 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7546 $table_element = 'categories'; // For compatibility
7547 }
7548
7549 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7550 $resql = $this->db->query($sql);
7551 if ($resql) {
7552 $tmpobj = $this->db->fetch_object($resql);
7553 if ($tmpobj) {
7554 $linealreadyfound = $tmpobj->nb;
7555 }
7556 }
7557
7558 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7559 if ($linealreadyfound) {
7560 if ($this->array_options["options_".$key] === null) {
7561 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
7562 } else {
7563 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7564 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7565 }
7566 $sql .= " WHERE fk_object = ".((int) $this->id);
7567
7568 $resql = $this->db->query($sql);
7569 if (!$resql) {
7570 $error++;
7571 $this->error = $this->db->lasterror();
7572 }
7573 } else {
7574 $result = $this->insertExtraFields('', $user);
7575 if ($result < 0) {
7576 $error++;
7577 }
7578 }
7579
7580 // Update also the user of last modification in parent table
7581 if (!$error && !empty($this->fields['fk_user_modif'])) {
7582 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7583 $sql .= " SET fk_user_modif = ".(int) $user->id;
7584 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7585 $this->db->query($sql);
7586 }
7587
7588 if (!$error) {
7589 $parameters = array('key' => $key);
7590 global $action;
7591 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7592 if ($reshook < 0) {
7593 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7594 }
7595 }
7596
7597 if (!$error && $trigger) {
7598 // Call trigger
7599 $this->context = array('extrafieldupdate' => 1);
7600 $result = $this->call_trigger($trigger, $userused);
7601 if ($result < 0) {
7602 $error++;
7603 }
7604 // End call trigger
7605 }
7606
7607 if ($error) {
7608 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7609 $this->db->rollback();
7610 return -1;
7611 } else {
7612 $this->db->commit();
7613 return 1;
7614 }
7615 } else {
7616 return 0;
7617 }
7618 }
7619
7626 public function getExtraField($key)
7627 {
7628 return $this->array_options['options_'.$key] ?? null;
7629 }
7630
7638 public function setExtraField($key, $value)
7639 {
7640 $this->array_options['options_'.$key] = $value;
7641 }
7642
7653 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7654 {
7655 global $user;
7656
7657 if (empty($userused)) {
7658 $userused = $user;
7659 }
7660
7661 //$error = 0;
7662
7663 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7664 return 0; // For avoid conflicts if trigger used
7665 }
7666
7667 return 0;
7668 }
7669
7670
7685 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7686 {
7687 global $conf, $langs, $form;
7688
7689
7690 // TODO pass the current object as a parameter to give more flexibility (like disable showing input for extra fields when canAlwaysBeEdited is false and $object->status is not draft...)
7691
7692 if (!is_object($form)) {
7693 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7694 $form = new Form($this->db);
7695 }
7696
7697 if (!empty($this->fields)) {
7698 $val = $this->fields[$key];
7699 }
7700
7701 // Validation tests and output
7702 $fieldValidationErrorMsg = '';
7703 $validationClass = '';
7704 $fieldValidationErrorMsg = $this->getFieldError($key);
7705 if (!empty($fieldValidationErrorMsg)) {
7706 $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
7707 } else {
7708 $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
7709 }
7710
7711 //$valuemultiselectinput = array();
7712 $out = '';
7713 $type = '';
7714 $isDependList = 0;
7715 $param = array();
7716 $param['options'] = array();
7717 $reg = array();
7718 // @phan-suppress-next-line PhanTypeMismatchProperty
7719 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7720 // Because we work on extrafields
7721 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7722 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7723 $type = 'link';
7724 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7725 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7726 $type = 'link';
7727 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7728 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7729 $type = 'link';
7730 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7731 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7732 $type = 'sellist';
7733 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7734 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7735 $type = 'sellist';
7736 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7737 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7738 $type = 'sellist';
7739 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7740 $param['options'] = array($reg[1] => 'N');
7741 $type = 'chkbxlst';
7742 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7743 $param['options'] = array();
7744 $type = 'varchar';
7745 $size = $reg[1];
7746 } elseif (preg_match('/varchar/', $val['type'])) {
7747 $param['options'] = array();
7748 $type = 'varchar';
7749 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7750 $param['options'] = array();
7751 $type = 'stars';
7752 $size = $reg[1];
7753 } else {
7754 $param['options'] = array();
7755 $type = $this->fields[$key]['type'];
7756 }
7757 //var_dump($type); var_dump($param['options']);
7758
7759 // Special case that force options and type ($type can be integer, varchar, ...)
7760 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7761 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7762 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7763 if (empty($this->fields[$key]['multiinput'])) {
7764 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7765 }
7766 }
7767
7768 $label = $this->fields[$key]['label'];
7769 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7770 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7771 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7772 // @phan-suppress-next-line PhanTypeMismatchProperty
7773 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7774 // @phan-suppress-next-line PhanTypeMismatchProperty
7775 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7776 // @phan-suppress-next-line PhanTypeMismatchProperty
7777 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7778 // @phan-suppress-next-line PhanTypeMismatchProperty
7779 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7780 // @phan-suppress-next-line PhanTypeMismatchProperty
7781 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7782
7783 // @phan-suppress-next-line PhanTypeMismatchProperty
7784 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7785 // @phan-suppress-next-line PhanTypeMismatchProperty
7786 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7787 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7788
7789 $objectid = $this->id;
7790
7791 if ($computed) {
7792 if (!preg_match('/^search_/', $keyprefix)) {
7793 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7794 } else {
7795 return '';
7796 }
7797 }
7798
7799 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7800 if (empty($morecss) && !empty($val['css'])) {
7801 $morecss = $val['css'];
7802 } elseif (empty($morecss)) {
7803 if ($type == 'date') {
7804 $morecss = 'minwidth100imp';
7805 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7806 $morecss = 'minwidth200imp';
7807 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7808 $morecss = 'maxwidth75';
7809 } elseif ($type == 'url') {
7810 $morecss = 'minwidth400';
7811 } elseif ($type == 'boolean') {
7812 $morecss = '';
7813 } else {
7814 if (is_numeric($size) && round((float) $size) < 12) {
7815 $morecss = 'minwidth100';
7816 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7817 $morecss = 'minwidth200';
7818 } else {
7819 $morecss = 'minwidth400';
7820 }
7821 }
7822 }
7823
7824 // Add validation state class
7825 if (!empty($validationClass)) {
7826 $morecss .= $validationClass;
7827 }
7828
7829 if (in_array($type, array('date'))) {
7830 $tmp = explode(',', $size);
7831 $newsize = $tmp[0];
7832 $showtime = 0;
7833
7834 // Do not show current date when field not required (see selectDate() method)
7835 if (!$required && $value == '') {
7836 $value = '-1';
7837 }
7838
7839 // TODO Must also support $moreparam
7840 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7841 } elseif (in_array($type, array('datetime'))) {
7842 $tmp = explode(',', $size);
7843 $newsize = $tmp[0];
7844 $showtime = 1;
7845
7846 // Do not show current date when field not required (see selectDate() method)
7847 if (!$required && $value == '') {
7848 $value = '-1';
7849 }
7850
7851 // TODO Must also support $moreparam
7852 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7853 } elseif (in_array($type, array('duration'))) {
7854 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7855 } elseif (in_array($type, array('int', 'integer'))) {
7856 $tmp = explode(',', $size);
7857 $newsize = $tmp[0];
7858 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"'.($newsize > 0 ? ' maxlength="'.$newsize.'"' : '').' value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7859 } elseif (in_array($type, array('real'))) {
7860 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7861 } elseif (preg_match('/varchar/', (string) $type)) {
7862 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"'.($size > 0 ? ' maxlength="'.$size.'"' : '').' value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($placeholder ? ' placeholder="'.dolPrintHTMLForAttribute($placeholder).'"' : '').($autofocusoncreate ? ' autofocus' : '').'>';
7863 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7864 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7865 } elseif (preg_match('/^text/', (string) $type)) {
7866 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7867 if (!empty($param['options'])) {
7868 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7869 //var_dump($param['options']);
7870 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7871 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7872 $out .= "<script>";
7873 $out .= '
7874 function handlemultiinputdisabling(htmlname){
7875 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
7876 multiinput = $("#"+htmlname+"_multiinput");
7877 multiinput.find("option").each(function(){
7878 tmpval = $("#"+htmlname).val();
7879 tmpvalarray = tmpval.split("\n");
7880 valtotest = $(this).val();
7881 if(tmpvalarray.includes(valtotest)){
7882 $(this).prop("disabled",true);
7883 } else {
7884 if($(this).prop("disabled") == true){
7885 console.log(valtotest)
7886 $(this).prop("disabled", false);
7887 }
7888 }
7889 });
7890 }
7891
7892 $(document).ready(function () {
7893 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
7894 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
7895 tmpvalarray = tmpval.split(",");
7896 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
7897 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
7898 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
7899 if(tmpval == ""){
7900 tmpval = valtotest;
7901 } else {
7902 tmpval = tmpval + "\n" + valtotest;
7903 }
7904 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
7905 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7906 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
7907 } else {
7908 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
7909 }
7910 });
7911 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
7912 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7913 });
7914 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7915 })';
7916 $out .= "</script>";
7917 $value = str_replace(',', "\n", $value);
7918 }
7919 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7920 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
7921 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
7922 } else {
7923 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7924 }
7925 } elseif (preg_match('/^html/', (string) $type)) {
7926 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7927 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7928 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
7929 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
7930 } else {
7931 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7932 }
7933 } elseif ($type == 'boolean') {
7934 $checked = '';
7935 if (!empty($value)) {
7936 $checked = ' checked value="1" ';
7937 } else {
7938 $checked = ' value="1" ';
7939 }
7940 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
7941 } elseif ($type == 'price') {
7942 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7943 $value = price($value);
7944 }
7945 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
7946 } elseif ($type == 'stars') {
7947 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7948 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
7949 $i = 1;
7950 while ($i <= $size) {
7951 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
7952 $i++;
7953 }
7954 $out .= '</div>';
7955 $out .= '<script>
7956 jQuery(function($) { /* commonobject.class.php 1 */
7957 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
7958 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
7959 container.find(".star").each(function() {
7960 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7961 });
7962 container.find(".star").on("mouseover", function() {
7963 let selectedStar = $(this).data("value");
7964 container.find(".star").each(function() {
7965 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
7966 });
7967 });
7968 container.on("mouseout", function() {
7969 container.find(".star").each(function() {
7970 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7971 });
7972 });
7973 container.find(".star").off("click").on("click", function() {
7974 selectedStars = $(this).data("value");
7975 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
7976 selectedStars = 0;
7977 }
7978 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
7979 container.find(".star").each(function() {
7980 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7981 });
7982 });
7983 });
7984 </script>';
7985 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7986 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7987 $value = price($value);
7988 }
7989 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
7990 } elseif ($type == 'select') { // combo list
7991 $out = '';
7992 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7993 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7994 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7995 }
7996
7997 $tmpselect = '';
7998 $nbchoice = 0;
7999
8000 foreach ($param['options'] as $keyb => $valb) {
8001 if ((string) $keyb == '') {
8002 continue;
8003 }
8004 if (strpos($valb, "|") !== false) {
8005 list($valb, $parent) = explode('|', $valb);
8006 }
8007 $nbchoice++;
8008 $tmpselect .= '<option value="'.$keyb.'"';
8009 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8010 if (!empty($parent)) {
8011 $isDependList = 1;
8012 }
8013 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8014 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8015 }
8016
8017 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8018 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8019 $out .= '<option value="0">&nbsp;</option>';
8020 }
8021 $out .= $tmpselect;
8022 $out .= '</select>';
8023 } elseif ($type == 'sellist') {
8024 $out = '';
8025 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8026 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8027 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8028 }
8029
8030 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8031 if (is_array($param['options'])) {
8032 $tmpparamoptions = array_keys($param['options']);
8033 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8034
8035 $InfoFieldList = explode(":", $paramoptions[0], 5);
8036 // 0 : tableName
8037 // 1 : label field name
8038 // 2 : key fields name (if different of rowid)
8039 // optional parameters...
8040 // 3 : key field parent (for dependent lists). How this is used ?
8041 // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value. Or use USF on a second line separated by "\n".
8042 // 5 : string category type. This replace the filter.
8043 // 6 : ids categories list separated by comma for category root. This replace the filter.
8044 // 7 : sort field
8045
8046 // If there is filter
8047 if (! empty($InfoFieldList[4])) {
8048 $pos = 0;
8049 $parenthesisopen = 0;
8050 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8051 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8052 $parenthesisopen++;
8053 }
8054 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8055 $parenthesisopen--;
8056 }
8057 $pos++;
8058 }
8059 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8060 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8061 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8062 $InfoFieldList[4] = $tmpbefore;
8063 if ($tmpafter !== '') {
8064 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8065 }
8066 //var_dump($InfoFieldList);
8067
8068 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8069 $reg = array();
8070 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8071 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8072 }
8073
8074 //var_dump($InfoFieldList);
8075 }
8076
8077 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8078
8079 $parentName = '';
8080 $parentField = '';
8081
8082 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8083
8084 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8085 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8086 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8087 } else {
8088 $keyList = $InfoFieldList[2].' as rowid';
8089 }
8090 }
8091 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8092 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8093 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8094 $keyList .= ', main.'.$parentField;
8095 } else {
8096 $keyList .= ', '.$parentField;
8097 }
8098 }
8099
8100 $filter_categorie = false;
8101 if (count($InfoFieldList) > 5) {
8102 if ($InfoFieldList[0] == 'categorie') {
8103 $filter_categorie = true;
8104 }
8105 }
8106
8107 if (!$filter_categorie) {
8108 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8109 if (!empty($fields_label)) {
8110 $keyList .= ', ';
8111 $keyList .= implode(', ', $fields_label);
8112 }
8113
8114 $sqlwhere = '';
8115 $sql = "SELECT " . $keyList;
8116 $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
8117
8118 if (!empty($InfoFieldList[4])) {
8119 // can use SELECT request
8120 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8121 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8122 }
8123
8124 // current object id can be use into filter
8125 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8126 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8127 } else {
8128 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8129 }
8130
8131 // We have to join on extrafield table
8132 $errstr = '';
8133 if (strpos($InfoFieldList[4], 'extra') !== false) {
8134 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8135 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8136 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8137 } else {
8138 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8139 }
8140 } else {
8141 $sqlwhere .= ' WHERE 1=1';
8142 }
8143
8144 // Add Usf filter on second line
8145 /*
8146 if ($Usf) {
8147 $errorstr = '';
8148 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8149 if (!$errorstr) {
8150 $sqlwhere .= $sqlusf;
8151 } else {
8152 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8153 }
8154 }
8155 */
8156
8157 // Some tables may have field, some other not. For the moment we disable it.
8158 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8159 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8160 }
8161 $sql .= $sqlwhere;
8162
8163 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8164 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8165 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8166 } else {
8167 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8168 }
8169 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8170 // print $sql;
8171
8172 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8173 $resql = $this->db->query($sql);
8174 if ($resql) {
8175 $out .= '<option value="0">&nbsp;</option>';
8176 $num = $this->db->num_rows($resql);
8177 $i = 0;
8178 while ($i < $num) {
8179 $labeltoshow = '';
8180 $obj = $this->db->fetch_object($resql);
8181
8182 // Several field into label (eq table:code|libelle:rowid)
8183 $notrans = false;
8184 $fields_label = explode('|', $InfoFieldList[1]);
8185 if (count($fields_label) > 1) {
8186 $notrans = true;
8187 foreach ($fields_label as $field_toshow) {
8188 $labeltoshow .= $obj->$field_toshow . ' ';
8189 }
8190 } else {
8191 $labeltoshow = $obj->{$InfoFieldList[1]};
8192 }
8193 $labeltoshow = dol_trunc($labeltoshow, 45);
8194
8195 if ($value == $obj->rowid) {
8196 foreach ($fields_label as $field_toshow) {
8197 $translabel = $langs->trans($obj->$field_toshow);
8198 if ($translabel != $obj->$field_toshow) {
8199 $labeltoshow = dol_trunc($translabel) . ' ';
8200 } else {
8201 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8202 }
8203 }
8204 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8205 } else {
8206 if (!$notrans) {
8207 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8208 if ($translabel != $obj->{$InfoFieldList[1]}) {
8209 $labeltoshow = dol_trunc($translabel, 18);
8210 } else {
8211 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8212 }
8213 }
8214 if (empty($labeltoshow)) {
8215 $labeltoshow = '(not defined)';
8216 }
8217 if ($value == $obj->rowid) {
8218 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8219 }
8220
8221 if (!empty($InfoFieldList[3]) && $parentField) {
8222 $parent = $parentName . ':' . $obj->{$parentField};
8223 $isDependList = 1;
8224 }
8225
8226 $out .= '<option value="' . $obj->rowid . '"';
8227 $out .= ($value == $obj->rowid ? ' selected' : '');
8228 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8229 $out .= '>' . $labeltoshow . '</option>';
8230 }
8231
8232 $i++;
8233 }
8234 $this->db->free($resql);
8235 } else {
8236 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8237 }
8238 } else {
8239 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8240 $categcode = $InfoFieldList[5];
8241 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8242 $tmpcategory = new Categorie($this->db);
8243 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8244 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8245 }
8246
8247 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8248 $out .= '<option value="0">&nbsp;</option>';
8249 foreach ($data as $data_key => $data_value) {
8250 $out .= '<option value="' . $data_key . '"';
8251 $out .= ($value == $data_key ? ' selected' : '');
8252 $out .= '>' . $data_value . '</option>';
8253 }
8254 }
8255 }
8256 $out .= '</select>';
8257 } elseif ($type == 'checkbox') {
8258 $value_arr = explode(',', $value);
8259 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8260 } elseif ($type == 'radio') {
8261 $out = '';
8262 foreach ($param['options'] as $keyopt => $valopt) {
8263 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8264 $out .= ' value="'.$keyopt.'"';
8265 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8266 $out .= ($value == $keyopt ? 'checked' : '');
8267 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8268 }
8269 } elseif ($type == 'chkbxlst') {
8270 if (is_array($value)) {
8271 $value_arr = $value;
8272 } else {
8273 $value_arr = explode(',', $value);
8274 }
8275
8276 if (is_array($param['options'])) {
8277 $tmpparamoptions = array_keys($param['options']);
8278 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8279
8280 $InfoFieldList = explode(":", $paramoptions[0], 5);
8281 // 0 : tableName
8282 // 1 : label field name
8283 // 2 : key fields name (if different of rowid)
8284 // optional parameters...
8285 // 3 : key field parent (for dependent lists). How this is used ?
8286 // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value. Or use USF on a second line separated by "\n".
8287 // 5 : string category type. This replace the filter.
8288 // 6 : ids categories list separated by comma for category root. This replace the filter.
8289 // 7 : sort field
8290
8291 // If there is a filter
8292 if (! empty($InfoFieldList[4])) {
8293 $pos = 0;
8294 $parenthesisopen = 0;
8295 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8296 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8297 $parenthesisopen++;
8298 }
8299 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8300 $parenthesisopen--;
8301 }
8302 $pos++;
8303 }
8304 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8305 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8306 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8307 $InfoFieldList[4] = $tmpbefore;
8308 if ($tmpafter !== '') {
8309 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8310 }
8311
8312 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8313 $reg = array();
8314 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8315 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8316 }
8317
8318 //var_dump($InfoFieldList);
8319 }
8320
8321 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8322
8323 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8324
8325 $parentName = '';
8326 $parentField = '';
8327
8328 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8329
8330 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8331 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8332 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8333 } else {
8334 $keyList = $InfoFieldList[2].' as rowid';
8335 }
8336 }
8337 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8338 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8339 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8340 $keyList .= ', main.'.$parentField;
8341 } else {
8342 $keyList .= ', '.$parentField;
8343 }
8344 }
8345
8346 $filter_categorie = false;
8347 if (count($InfoFieldList) > 5) {
8348 if ($InfoFieldList[0] == 'categorie') {
8349 $filter_categorie = true;
8350 }
8351 }
8352
8353 // Common filter
8354 if (!$filter_categorie) {
8355 $fields_label = explode('|', $InfoFieldList[1]);
8356 if (is_array($fields_label)) {
8357 $keyList .= ', ';
8358 $keyList .= implode(', ', $fields_label);
8359 }
8360
8361 $sqlwhere = '';
8362 $sql = "SELECT " . $keyList;
8363 $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8364
8365 if (!empty($InfoFieldList[4])) {
8366 // can use SELECT request
8367 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8368 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8369 }
8370
8371 // current object id can be use into filter
8372 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8373 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8374 } else {
8375 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8376 }
8377
8378 // We have to join on extrafield table
8379 $errstr = '';
8380 if (strpos($InfoFieldList[4], 'extra') !== false) {
8381 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8382 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8383 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8384 } else {
8385 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8386 }
8387 } else {
8388 $sqlwhere .= ' WHERE 1=1';
8389 }
8390
8391 // Add Usf filter on second line
8392 /*
8393 if ($Usf) {
8394 $errorstr = '';
8395 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8396 if (!$errorstr) {
8397 $sqlwhere .= $sqlusf;
8398 } else {
8399 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8400 }
8401 }
8402 */
8403
8404 // Some tables may have field, some other not. For the moment we disable it.
8405 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8406 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8407 }
8408 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8409 // print $sql;
8410
8411 $sql .= $sqlwhere;
8412
8413 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8414
8415 $resql = $this->db->query($sql);
8416 if ($resql) {
8417 $num = $this->db->num_rows($resql);
8418 $i = 0;
8419
8420 $data = array();
8421
8422 while ($i < $num) {
8423 $labeltoshow = '';
8424 $obj = $this->db->fetch_object($resql);
8425
8426 $notrans = false;
8427 // Several field into label (eq table:code|libelle:rowid)
8428 $fields_label = explode('|', $InfoFieldList[1]);
8429 if (count($fields_label) > 1) {
8430 $notrans = true;
8431 foreach ($fields_label as $field_toshow) {
8432 $labeltoshow .= $obj->$field_toshow . ' ';
8433 }
8434 } else {
8435 $labeltoshow = $obj->{$InfoFieldList[1]};
8436 }
8437 $labeltoshow = dol_trunc($labeltoshow, 45);
8438
8439 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8440 foreach ($fields_label as $field_toshow) {
8441 $translabel = $langs->trans($obj->$field_toshow);
8442 if ($translabel != $obj->$field_toshow) {
8443 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8444 } else {
8445 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8446 }
8447 }
8448
8449 $data[$obj->rowid] = $labeltoshow;
8450 } else {
8451 if (!$notrans) {
8452 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8453 if ($translabel != $obj->{$InfoFieldList[1]}) {
8454 $labeltoshow = dol_trunc($translabel, 18);
8455 } else {
8456 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8457 }
8458 }
8459 if (empty($labeltoshow)) {
8460 $labeltoshow = '(not defined)';
8461 }
8462
8463 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8464 $data[$obj->rowid] = $labeltoshow;
8465 }
8466
8467 if (!empty($InfoFieldList[3]) && $parentField) {
8468 $parent = $parentName . ':' . $obj->{$parentField};
8469 $isDependList = 1;
8470 }
8471
8472 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8473 }
8474
8475 $i++;
8476 }
8477 $this->db->free($resql);
8478
8479 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8480 } else {
8481 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8482 }
8483 } else {
8484 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8485 $categcode = $InfoFieldList[5];
8486 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8487 $tmpcategory = new Categorie($this->db);
8488 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8489 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8490 }
8491
8492 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8493 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8494 }
8495 }
8496 } elseif ($type == 'link') {
8497 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8498 // Filter can contains some ':' inside.
8499 $param_list = array_keys($param['options']);
8500 $param_list_array = explode(':', $param_list[0], 4);
8501
8502 $showempty = (($required && $default != '') ? 0 : 1);
8503
8504 if (!preg_match('/search_/', $keyprefix)) {
8505 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8506 // @phan-suppress-next-line PhanTypeMismatchProperty
8507 if (!empty($this->fields[$key]['picto'])) {
8508 $morecss .= ' widthcentpercentminusxx';
8509 } else {
8510 $morecss .= ' widthcentpercentminusx';
8511 }
8512 } else {
8513 // @phan-suppress-next-line PhanTypeMismatchProperty
8514 if (!empty($this->fields[$key]['picto'])) {
8515 $morecss .= ' widthcentpercentminusx';
8516 }
8517 }
8518 }
8519
8520 // $param_list_array[0] can be the name of object (Example 'User' the field is linked to). Not as taking the information from the record in ->fields found from $objectfield.
8521
8522 // $val is already the record field found at same place than found by $valparent but already loaded and may have been modified by parent caller.
8523
8524 $objectfield = $val;
8525
8526 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8527 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8528
8529 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8530 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8531 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8532 list($class, $classfile) = explode(':', $param_list[0]);
8533 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8534 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8535 } else {
8536 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8537 }
8538 $paramforthenewlink = '';
8539 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8540 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8541 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8542 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8543 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8544 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8545 $out .= '<a class="butActionNew" title="'.$langs->trans("New").'" href="'.$url_path.'?action=create&backtopage='.urlencode($_SERVER['PHP_SELF'].($paramforthenewlink ? '?'.$paramforthenewlink : '')).'"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8546 }
8547 }
8548 } elseif ($type == 'password') {
8549 // If prefix is 'search_', field is used as a filter, we use a common text field.
8550 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8551 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8552 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8553 } else {
8554 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8555 }
8556 } elseif ($type == 'array') {
8557 $newval = $val;
8558 $newval['type'] = 'varchar(256)';
8559
8560 $out = '';
8561 if (!empty($value)) {
8562 foreach ($value as $option) {
8563 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8564 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8565 }
8566 }
8567 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8568
8569 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8570 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8571
8572 if (!empty($conf->use_javascript_ajax)) {
8573 $out .= '
8574 <script nonce="'.getNonce().'">
8575 $(document).ready(function() {
8576 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8577 $("'.dol_escape_js($newInput).'").insertBefore(this);
8578 });
8579
8580 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8581 $(this).parent().remove();
8582 });
8583 });
8584 </script>';
8585 }
8586 }
8587 if (!empty($hidden)) {
8588 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8589 }
8590
8591 if ($isDependList == 1) {
8592 $out .= $this->getJSListDependancies('_common');
8593 }
8594 /* Add comments
8595 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8596 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8597 */
8598
8599 // Display error message for field
8600 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8601 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8602 }
8603
8604 return $out;
8605 }
8606
8620 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8621 {
8622 global $conf, $langs, $form;
8623
8624 // TODO pass the current object as a parameter to give more flexibility (like disable ajax update when canAlwaysBeEdited is false and $object->status is not draft...)
8625
8626 if (!is_object($form)) {
8627 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8628 $form = new Form($this->db);
8629 }
8630
8631 //$label = empty($val['label']) ? '' : $val['label'];
8632 $type = empty($val['type']) ? '' : $val['type'];
8633 $size = empty($val['css']) ? '' : $val['css'];
8634 $reg = array();
8635
8636 // Convert var to be able to share same code than showOutputField of extrafields
8637 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8638 $type = 'varchar'; // convert varchar(xx) int varchar
8639 $size = $reg[1];
8640 } elseif (preg_match('/varchar/', $type)) {
8641 $type = 'varchar'; // convert varchar(xx) int varchar
8642 }
8643 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8644 // @phan-suppress-next-line PhanTypeMismatchProperty
8645 if (empty($this->fields[$key]['multiinput'])) {
8646 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8647 }
8648 }
8649 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8650 $type = 'link';
8651 }
8652
8653 $default = empty($val['default']) ? '' : $val['default'];
8654 $computed = empty($val['computed']) ? '' : $val['computed'];
8655 $unique = empty($val['unique']) ? '' : $val['unique'];
8656 $required = empty($val['required']) ? '' : $val['required'];
8657 $param = array();
8658 $param['options'] = array();
8659
8660 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8661 $param['options'] = $val['arrayofkeyval'];
8662 }
8663 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8664 $type = 'link';
8665 $stringforoptions = $reg[1].':'.$reg[2];
8666 // Special case: Force addition of getnomurlparam1 to -1 for users
8667 if ($reg[1] == 'User') {
8668 $stringforoptions .= ':#getnomurlparam1=-1';
8669 }
8670 $param['options'] = array($stringforoptions => $stringforoptions);
8671 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8672 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8673 $type = 'sellist';
8674 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8675 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8676 $type = 'sellist';
8677 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8678 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8679 $type = 'sellist';
8680 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8681 $param['options'] = array($reg[1] => 'N');
8682 $type = 'chkbxlst';
8683 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8684 $param['options'] = array();
8685 $type = 'stars';
8686 $size = $reg[1];
8687 }
8688
8689 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8690 $list = (empty($val['list']) ? '' : $val['list']);
8691 $help = (empty($val['help']) ? '' : $val['help']);
8692 $hidden = (($val['visible'] == 0) ? 1 : 0); // If zero, we are sure it is hidden, otherwise we show. If it depends on mode (view/create/edit form or list, this must be filtered by caller)
8693
8694 if ($hidden) {
8695 return '';
8696 }
8697
8698 // If field is a computed field, value must become result of compute
8699 if ($computed) {
8700 // Make the eval of compute string
8701 //var_dump($computed);
8702 $value = dol_eval((string) $computed, 1, 0, '2');
8703 }
8704
8705 if (empty($morecss)) {
8706 if ($type == 'date') {
8707 $morecss = 'minwidth100imp';
8708 } elseif ($type == 'datetime' || $type == 'timestamp') {
8709 $morecss = 'minwidth200imp';
8710 } elseif (in_array($type, array('int', 'double', 'price'))) {
8711 $morecss = 'maxwidth75';
8712 } elseif ($type == 'url') {
8713 $morecss = 'minwidth400';
8714 } elseif ($type == 'boolean') {
8715 $morecss = '';
8716 } else {
8717 if (is_numeric($size) && round((float) $size) < 12) {
8718 $morecss = 'minwidth100';
8719 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8720 $morecss = 'minwidth200';
8721 } else {
8722 $morecss = 'minwidth400';
8723 }
8724 }
8725 }
8726
8727 // Format output value differently according to properties of field
8728 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8729 // @phan-suppress-next-line PhanTypeMismatchProperty
8730 if ($key != 'rowid' || empty($this->fields['ref'])) { // If we want ref field or if we want ID and there is no ref field, we show the link.
8731 $value = $this->getNomUrl(1, '', 0, '', 1);
8732 }
8733 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8734 $value = $this->getLibStatut(3);
8735 } elseif ($type == 'date') {
8736 if (!empty($value)) {
8737 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8738 } else {
8739 $value = '';
8740 }
8741 } elseif ($type == 'datetime' || $type == 'timestamp') {
8742 if (!empty($value)) {
8743 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8744 } else {
8745 $value = '';
8746 }
8747 } elseif ($type == 'duration') {
8748 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8749 if (!is_null($value) && $value !== '') {
8750 $value = convertSecondToTime((int) $value, 'allhourmin');
8751 }
8752 } elseif ($type == 'double' || $type == 'real') {
8753 if (!is_null($value) && $value !== '') {
8754 $value = price($value);
8755 }
8756 } elseif ($type == 'boolean') {
8757 $checked = '';
8758 if (!empty($value)) {
8759 $checked = ' checked ';
8760 }
8761 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8762 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8763 } else {
8764 $value = yn($value ? 1 : 0);
8765 }
8766 } elseif ($type == 'mail' || $type == 'email') {
8767 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8768 } elseif ($type == 'url') {
8769 $value = dol_print_url((string) $value, '_blank', 32, 1);
8770 } elseif ($type == 'phone') {
8771 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8772 } elseif ($type == 'ip') {
8773 $value = dol_print_ip((string) $value, 0);
8774 } elseif ($type == 'stars') {
8775 $value = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.$this->id.'" value="'.dol_escape_htmltag((string) $value).'"'.($moreparam ? $moreparam : '').'>';
8776 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8777 $i = 1;
8778 while ($i <= $size) {
8779 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8780 $i++;
8781 }
8782 $value .= '</div>';
8783 $value .= '<script>
8784 $(document).ready(function() { /* commonobject.class.php 2 */
8785 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8786 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8787 container.find(".star").each(function() {
8788 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8789 });
8790 container.find(".star").on("mouseover", function() {
8791 let selectedStar = $(this).data("value");
8792 container.find(".star").each(function() {
8793 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8794 });
8795 });
8796 container.on("mouseout", function() {
8797 container.find(".star").each(function() {
8798 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8799 });
8800 });
8801 container.find(".star").off("click").on("click", function() {
8802 selectedStars = $(this).data("value");
8803 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8804 selectedStars = 0;
8805 }
8806 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8807 container.find(".star").each(function() {
8808 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8809 });
8810 $.ajax({
8811 url: "ajax/'.$this->element.'.php",
8812 method: "POST",
8813 data: {
8814 objectId: "'.$this->id.'",
8815 field: "'.$keyprefix.$key.$keysuffix.'",
8816 value: selectedStars,
8817 token: "'.newToken().'"
8818 },
8819 success: function(response) {
8820 var res = JSON.parse(response);
8821 console[res.status === "success" ? "log" : "error"](res.message);
8822 },
8823 error: function(xhr, status, error) {
8824 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8825 }
8826 });
8827 });
8828 });
8829 </script>';
8830 } elseif ($type == 'price') {
8831 if (!is_null($value) && $value !== '') {
8832 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8833 }
8834 } elseif ($type == 'select') {
8835 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8836 if (strpos($value, "|") !== false) {
8837 $value = $langs->trans(explode('|', $value)[0]);
8838 } elseif (! is_numeric($value)) {
8839 $value = $langs->trans($value);
8840 }
8841 } elseif ($type == 'sellist') {
8842 $param_list = array_keys($param['options']);
8843 $InfoFieldList = explode(":", $param_list[0]);
8844
8845 $selectkey = "rowid";
8846 $keyList = 'rowid';
8847
8848 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8849 $selectkey = $InfoFieldList[2];
8850 $keyList = $InfoFieldList[2].' as rowid';
8851 }
8852
8853 $fields_label = explode('|', $InfoFieldList[1]);
8854 if (is_array($fields_label)) {
8855 $keyList .= ', ';
8856 $keyList .= implode(', ', $fields_label);
8857 }
8858
8859 $filter_categorie = false;
8860 if (count($InfoFieldList) > 5) {
8861 if ($InfoFieldList[0] == 'categorie') {
8862 $filter_categorie = true;
8863 }
8864 }
8865
8866 $sql = "SELECT ".$keyList;
8867 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8868 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8869 $sql .= ' as main';
8870 }
8871 if ($selectkey == 'rowid' && empty($value)) {
8872 $sql .= " WHERE ".$selectkey." = 0";
8873 } elseif ($selectkey == 'rowid') {
8874 $sql .= " WHERE ".$selectkey." = ".((int) $value);
8875 } else {
8876 $sql .= " WHERE ".$selectkey." = '".$this->db->escape((string) $value)."'";
8877 }
8878
8879 //$sql.= ' AND entity = '.$conf->entity;
8880
8881 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
8882 $resql = $this->db->query($sql);
8883 if ($resql) {
8884 if (!$filter_categorie) {
8885 $value = ''; // value was used, so now we reset it to use it to build final output
8886 $numrows = $this->db->num_rows($resql);
8887 if ($numrows) {
8888 $obj = $this->db->fetch_object($resql);
8889
8890 // Several field into label (eq table:code|libelle:rowid)
8891 $fields_label = explode('|', $InfoFieldList[1]);
8892
8893 if (is_array($fields_label) && count($fields_label) > 1) {
8894 foreach ($fields_label as $field_toshow) {
8895 $translabel = '';
8896 if (!empty($obj->$field_toshow)) {
8897 $translabel = $langs->trans($obj->$field_toshow);
8898 }
8899 if ($translabel != $field_toshow) {
8900 $value .= dol_trunc($translabel, 18) . ' ';
8901 } else {
8902 $value .= $obj->$field_toshow . ' ';
8903 }
8904 }
8905 } else {
8906 $translabel = '';
8907 if (!empty($obj->{$InfoFieldList[1]})) {
8908 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8909 }
8910 if ($translabel != $obj->{$InfoFieldList[1]}) {
8911 $value = dol_trunc($translabel, 18);
8912 } else {
8913 $value = $obj->{$InfoFieldList[1]};
8914 }
8915 }
8916 }
8917 } else {
8918 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8919
8920 $toprint = array();
8921 $obj = $this->db->fetch_object($resql);
8922 $c = new Categorie($this->db);
8923 $c->fetch($obj->rowid);
8924 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8925 foreach ($ways as $way) {
8926 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8927 }
8928 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8929 }
8930 } else {
8931 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8932 }
8933 } elseif ($type == 'radio') {
8934 $value = $param['options'][(string) $value];
8935 } elseif ($type == 'checkbox') {
8936 $value_arr = explode(',', (string) $value);
8937 $value = '';
8938 if (is_array($value_arr) && count($value_arr) > 0) {
8939 $toprint = array();
8940 foreach ($value_arr as $keyval => $valueval) {
8941 if (!empty($valueval)) {
8942 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
8943 }
8944 }
8945 if (!empty($toprint)) {
8946 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8947 }
8948 }
8949 } elseif ($type == 'chkbxlst') {
8950 $value_arr = (isset($value) ? explode(',', $value) : array());
8951
8952 $param_list = array_keys($param['options']);
8953 $InfoFieldList = explode(":", $param_list[0]);
8954
8955 $selectkey = "rowid";
8956 $keyList = 'rowid';
8957
8958 if (count($InfoFieldList) >= 3) {
8959 $selectkey = $InfoFieldList[2];
8960 $keyList = $InfoFieldList[2].' as rowid';
8961 }
8962
8963 $fields_label = explode('|', $InfoFieldList[1]);
8964 if (is_array($fields_label)) {
8965 $keyList .= ', ';
8966 $keyList .= implode(', ', $fields_label);
8967 }
8968
8969 $filter_categorie = false;
8970 if (count($InfoFieldList) > 5) {
8971 if ($InfoFieldList[0] == 'categorie') {
8972 $filter_categorie = true;
8973 }
8974 }
8975
8976 $sql = "SELECT ".$keyList;
8977 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8978 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8979 $sql .= ' as main';
8980 }
8981 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
8982 // $sql.= ' AND entity = '.$conf->entity;
8983
8984 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
8985 $resql = $this->db->query($sql);
8986 if ($resql) {
8987 if (!$filter_categorie) {
8988 $value = ''; // value was used, so now we reset it to use it to build final output
8989 $toprint = array();
8990 while ($obj = $this->db->fetch_object($resql)) {
8991 // Several field into label (eq table:code|libelle:rowid)
8992 $fields_label = explode('|', $InfoFieldList[1]);
8993 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8994 if (is_array($fields_label) && count($fields_label) > 1) {
8995 foreach ($fields_label as $field_toshow) {
8996 $translabel = '';
8997 if (!empty($obj->$field_toshow)) {
8998 $translabel = $langs->trans($obj->$field_toshow);
8999 }
9000 if ($translabel != $field_toshow) {
9001 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9002 } else {
9003 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9004 }
9005 }
9006 } else {
9007 $translabel = '';
9008 if (!empty($obj->{$InfoFieldList[1]})) {
9009 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9010 }
9011 if ($translabel != $obj->{$InfoFieldList[1]}) {
9012 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9013 } else {
9014 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9015 }
9016 }
9017 }
9018 }
9019 } else {
9020 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9021
9022 $toprint = array();
9023 while ($obj = $this->db->fetch_object($resql)) {
9024 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9025 $c = new Categorie($this->db);
9026 $c->fetch($obj->rowid);
9027 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9028 foreach ($ways as $way) {
9029 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9030 }
9031 }
9032 }
9033 }
9034 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9035 } else {
9036 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9037 }
9038 } elseif ($type == 'link') {
9039 $out = '';
9040
9041 // only if something to display (perf)
9042 if ($value) {
9043 $param_list = array_keys($param['options']);
9044 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9045 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9046
9047 $InfoFieldList = explode(":", $param_list[0]);
9048
9049 $classname = $InfoFieldList[0];
9050 $classpath = $InfoFieldList[1];
9051
9052 // Set $getnomurlparam1 et getnomurlparam2
9053 $getnomurlparam = 3;
9054 $getnomurlparam2 = '';
9055 $regtmp = array();
9056 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9057 $getnomurlparam = $regtmp[1];
9058 }
9059 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9060 $getnomurlparam2 = $regtmp[1];
9061 }
9062
9063 if (!empty($classpath)) {
9064 dol_include_once($InfoFieldList[1]);
9065
9066 if ($classname && !class_exists($classname)) {
9067 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9068 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9069 $element_prop = getElementProperties($classname);
9070 if ($element_prop) {
9071 $classname = $element_prop['classname'];
9072 }
9073 }
9074
9075
9076 if ($classname && class_exists($classname)) {
9077 $object = new $classname($this->db);
9078 '@phan-var-force CommonObject $object';
9079 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9080 '@phan-var-force Product $object';
9081 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9082 } else {
9083 $result = $object->fetch($value);
9084 }
9085 if ($result > 0) {
9086 if ($object->element === 'product') {
9087 '@phan-var-force Product $object';
9088 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9089 if (isset($val['get_name_url_params'])) {
9090 $get_name_url_params = explode(':', $val['get_name_url_params']);
9091 if (!empty($get_name_url_params)) {
9092 $param_num_max = count($get_name_url_param_arr) - 1;
9093 foreach ($get_name_url_params as $param_num => $param_value) {
9094 if ($param_num > $param_num_max) {
9095 break;
9096 }
9097 $get_name_url_param_arr[$param_num] = $param_value;
9098 }
9099 }
9100 }
9101
9105 $value = $object->getNomUrl($get_name_url_param_arr[0], $get_name_url_param_arr[1], $get_name_url_param_arr[2], $get_name_url_param_arr[3], $get_name_url_param_arr[4], $get_name_url_param_arr[5], $get_name_url_param_arr[6]);
9106 } else {
9107 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9108 }
9109 } else {
9110 $value = '';
9111 }
9112 }
9113 } else {
9114 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9115 return 'Error bad setup of extrafield';
9116 }
9117 } else {
9118 $value = '';
9119 }
9120 } elseif ($type == 'password') {
9121 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9122 //$value = preg_replace('/./i', '*', $value);
9123 } elseif ($type == 'array') {
9124 if (is_array($value)) {
9125 $value = implode('<br>', $value);
9126 } else {
9127 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9128 return 'Error unexpected result from code evaluation';
9129 }
9130 } else { // text|html|varchar
9131 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9132 $value = str_replace(',', "\n", $value);
9133 }
9134 $value = dol_htmlentitiesbr((string) $value);
9135 }
9136
9137 //print $type.'-'.$size.'-'.$value;
9138 $out = $value;
9139
9140 return is_null($out) ? '' : $out;
9141 }
9142
9149 public function clearFieldError($fieldKey)
9150 {
9151 $this->error = '';
9152 unset($this->validateFieldsErrors[$fieldKey]);
9153 }
9154
9162 public function setFieldError($fieldKey, $msg = '')
9163 {
9164 global $langs;
9165 if (empty($msg)) {
9166 $msg = $langs->trans("UnknownError");
9167 }
9168
9169 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9170 }
9171
9178 public function getFieldError($fieldKey)
9179 {
9180 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9181 return $this->validateFieldsErrors[$fieldKey];
9182 }
9183 return '';
9184 }
9185
9194 public function validateField($fields, $fieldKey, $fieldValue)
9195 {
9196 global $langs;
9197
9198 if (!class_exists('Validate')) {
9199 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9200 }
9201
9202 $this->clearFieldError($fieldKey);
9203
9204 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9205 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9206 return false;
9207 }
9208
9209 $val = $fields[$fieldKey];
9210
9211 $param = array();
9212 $param['options'] = array();
9213 $type = $val['type'];
9214
9215 $required = false;
9216 if (isset($val['notnull']) && $val['notnull'] === 1) {
9217 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9218 $required = true;
9219 }
9220
9221 $maxSize = 0;
9222 $minSize = 0;
9223
9224 //
9225 // PREPARE Elements
9226 //
9227 $reg = array();
9228
9229 // Convert var to be able to share same code than showOutputField of extrafields
9230 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9231 $type = 'varchar'; // convert varchar(xx) int varchar
9232 $maxSize = (int) $reg[1];
9233 } elseif (preg_match('/varchar/', $type)) {
9234 $type = 'varchar'; // convert varchar(xx) int varchar
9235 }
9236
9237 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9238 $type = 'select';
9239 }
9240
9241 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9242 $type = 'link';
9243 }
9244
9245 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9246 $param['options'] = $val['arrayofkeyval'];
9247 }
9248
9249 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9250 $type = 'link';
9251 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9252 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9253 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9254 $type = 'sellist';
9255 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9256 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9257 $type = 'sellist';
9258 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9259 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9260 $type = 'sellist';
9261 }
9262
9263 //
9264 // TEST Value
9265 //
9266
9267 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9268 $validate = new Validate($this->db, $langs);
9269
9270
9271 // little trick : to perform tests with good performances sort tests by quick to low
9272
9273 //
9274 // COMMON TESTS
9275 //
9276
9277 // Required test and empty value
9278 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9279 $this->setFieldError($fieldKey, $validate->error);
9280 return false;
9281 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9282 // if no value sent and the field is not mandatory, no need to perform tests
9283 return true;
9284 }
9285
9286 // MAX Size test
9287 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9288 $this->setFieldError($fieldKey, $validate->error);
9289 return false;
9290 }
9291
9292 // MIN Size test
9293 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9294 $this->setFieldError($fieldKey, $validate->error);
9295 return false;
9296 }
9297
9298 //
9299 // TESTS for TYPE
9300 //
9301
9302 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9303 if (!$validate->isTimestamp($fieldValue)) {
9304 $this->setFieldError($fieldKey, $validate->error);
9305 return false;
9306 } else {
9307 return true;
9308 }
9309 } elseif ($type == 'duration') {
9310 if (!$validate->isDuration($fieldValue)) {
9311 $this->setFieldError($fieldKey, $validate->error);
9312 return false;
9313 } else {
9314 return true;
9315 }
9316 } elseif (in_array($type, array('double', 'real', 'price'))) {
9317 // is numeric
9318 if (!$validate->isNumeric($fieldValue)) {
9319 $this->setFieldError($fieldKey, $validate->error);
9320 return false;
9321 } else {
9322 return true;
9323 }
9324 } elseif ($type == 'boolean') {
9325 if (!$validate->isBool($fieldValue)) {
9326 $this->setFieldError($fieldKey, $validate->error);
9327 return false;
9328 } else {
9329 return true;
9330 }
9331 } elseif ($type == 'mail' || $type == 'email') {
9332 if (!$validate->isEmail($fieldValue)) {
9333 $this->setFieldError($fieldKey, $validate->error);
9334 return false;
9335 }
9336 } elseif ($type == 'url') {
9337 if (!$validate->isUrl($fieldValue)) {
9338 $this->setFieldError($fieldKey, $validate->error);
9339 return false;
9340 } else {
9341 return true;
9342 }
9343 } elseif ($type == 'phone') {
9344 if (!$validate->isPhone($fieldValue)) {
9345 $this->setFieldError($fieldKey, $validate->error);
9346 return false;
9347 } else {
9348 return true;
9349 }
9350 } elseif ($type == 'select' || $type == 'radio') {
9351 if (!isset($param['options'][$fieldValue])) {
9352 $this->error = $langs->trans('RequireValidValue');
9353 return false;
9354 } else {
9355 return true;
9356 }
9357 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9358 $param_list = array_keys($param['options']);
9359 $InfoFieldList = explode(":", $param_list[0]);
9360 $value_arr = explode(',', $fieldValue);
9361 $value_arr = array_map(array($this->db, 'escape'), $value_arr);
9362
9363 $selectkey = "rowid";
9364 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9365 $selectkey = $InfoFieldList[2];
9366 }
9367
9368 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9369 $this->setFieldError($fieldKey, $validate->error);
9370 return false;
9371 } else {
9372 return true;
9373 }
9374 } elseif ($type == 'link') {
9375 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9376 $InfoFieldList = explode(":", $param_list[0]);
9377 $classname = $InfoFieldList[0];
9378 $classpath = $InfoFieldList[1];
9379 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9380 $lastIsFetchableError = $validate->error;
9381
9382 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9383 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9384 return true;
9385 }
9386
9387 $this->setFieldError($fieldKey, $lastIsFetchableError);
9388 return false;
9389 } else {
9390 return true;
9391 }
9392 }
9393
9394 // if no test failed all is ok
9395 return true;
9396 }
9397
9411 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9412 {
9413 global $db, $conf, $langs, $action, $form, $hookmanager;
9414
9415 if (!is_object($form)) {
9416 $form = new Form($db);
9417 }
9418 if (!is_object($extrafields)) {
9419 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9420 return 'Bad parameter extrafields for showOptionals';
9421 }
9422 if (!is_array($extrafields->attributes[$this->table_element])) {
9423 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9424 }
9425
9426 $out = '';
9427
9428 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9429 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9430 $hookResPrint = $hookmanager->resPrint;
9431
9432 if (empty($reshook)) {
9433 if (is_array($extrafields->attributes[$this->table_element]) && array_key_exists('label', $extrafields->attributes[$this->table_element]) && is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label']) > 0) {
9434 $out .= "\n";
9435 $out .= '<!-- commonobject:showOptionals --> ';
9436 $out .= "\n";
9437
9438 $nbofextrafieldsshown = 0;
9439 $e = 0; // var to manage the modulo (odd/even)
9440
9441 $lastseparatorkeyfound = '';
9442 $extrafields_collapse_num = '';
9443 $extrafields_collapse_num_old = '';
9444 $i = 0;
9445
9446 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9447 $i++;
9448
9449 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9450 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9451 continue;
9452 }
9453
9454 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9455 $enabled = 1;
9456 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9457 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9458 }
9459 if (empty($enabled)) {
9460 continue;
9461 }
9462
9463 $visibility = 1;
9464 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9465 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9466 }
9467
9468 $perms = 1;
9469 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9470 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9471 }
9472
9473 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9474 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9475 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9476 // We need to make sure, that the values of hidden extrafields are also part of $_POST. Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9477 $ef_name = 'options_' . $key;
9478 $ef_value = $this->array_options[$ef_name]??'';
9479 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . $ef_value . '" />' . "\n";
9480 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9481 } elseif ($mode == 'view' && empty($visibility)) {
9482 continue;
9483 }
9484 if (empty($perms)) {
9485 continue;
9486 }
9487
9488 // Load language if required
9489 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9490 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9491 }
9492
9493 $colspan = 0;
9494 $value = null;
9495 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9496 if (array_key_exists('cols', $params)) {
9497 $colspan = $params['cols'];
9498 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9499 $reg = array();
9500 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9501 $colspan = $reg[1];
9502 } else {
9503 $colspan = $params['colspan'];
9504 }
9505 }
9506 }
9507 $colspan = intval($colspan);
9508
9509 switch ($mode) {
9510 case "view":
9511 $value = ((!empty($this->array_options) && array_key_exists("options_".$key.$keysuffix, $this->array_options)) ? $this->array_options["options_".$key.$keysuffix] : null); // Value may be cleaned or formatted later
9512 break;
9513 case "create":
9514 case "edit":
9515 // We get the value of property found with GETPOST so it takes into account:
9516 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9517 $check = 'alphanohtml';
9518 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9519 $check = 'restricthtml';
9520 }
9521 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9522 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9523 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9524 if (is_array($getposttemp)) {
9525 // $getposttemp is an array but following code expects a comma separated string
9526 $value = implode(",", $getposttemp);
9527 } else {
9528 $value = $getposttemp;
9529 }
9530 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9531 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9532 } else {
9533 $value = (!empty($this->array_options["options_".$key]) ? $this->array_options["options_".$key] : ''); // No GET, no POST, no default value, so we take value of object.
9534 }
9535 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9536 break;
9537 }
9538
9539 $nbofextrafieldsshown++;
9540
9541 // Output value of the current field
9542 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9543 $extrafields_collapse_num = $key;
9544 /*
9545 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9546 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9547 $extrafield_param_list = array_keys($extrafield_param['options']);
9548
9549 if (count($extrafield_param_list) > 0) {
9550 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9551
9552 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9553 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9554 $extrafields_collapse_num = $key;
9555 }
9556 }
9557 }
9558 */
9559
9560 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9561 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9562
9563 $lastseparatorkeyfound = $key;
9564 } else {
9565 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9566
9567 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9568 $csstyle = '';
9569 if (is_array($params) && count($params) > 0) {
9570 if (array_key_exists('class', $params)) {
9571 $class .= $params['class'].' ';
9572 }
9573 if (array_key_exists('style', $params)) {
9574 $csstyle = $params['style'];
9575 }
9576 }
9577
9578 // add html5 elements
9579 $domData = ' data-element="extrafield"';
9580 $domData .= ' data-targetelement="'.$this->element.'"';
9581 $domData .= ' data-targetid="'.$this->id.'"';
9582
9583 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9584 if ($display_type == 'card') {
9585 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9586 $colspan = 0;
9587 }
9588
9589 if ($action == 'selectlines') {
9590 $colspan++;
9591 }
9592 }
9593
9594 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9595 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9596 $force_values_on_change_company = (
9597 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9598 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9599 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9600 );
9601 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9602 $value = $this->array_options['options_'.$key] ?? $value;
9603 }
9604
9605 // Convert date into timestamp format (value in memory must be a timestamp)
9606 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9607 $datenotinstring = null;
9608 if (array_key_exists('options_'.$key, $this->array_options)) {
9609 $datenotinstring = $this->array_options['options_'.$key];
9610 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9611 $datenotinstring = $this->db->jdate($datenotinstring);
9612 }
9613 }
9614 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9615 $value = (GETPOSTISSET($datekey) && $this->id == GETPOST('elrowid', 'int')) ? dol_mktime(12, 0, 0, GETPOSTINT($datekey.'month', 3), GETPOSTINT($datekey.'day', 3), GETPOSTINT($datekey.'year', 3)) : $datenotinstring;
9616 }
9617 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9618 $datenotinstring = null;
9619 if (array_key_exists('options_'.$key, $this->array_options)) {
9620 $datenotinstring = $this->array_options['options_'.$key];
9621 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9622 $datenotinstring = $this->db->jdate($datenotinstring);
9623 }
9624 }
9625 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9626 $value = (GETPOSTISSET($timekey)) ? dol_mktime(GETPOSTINT($timekey.'hour', 3), GETPOSTINT($timekey.'min', 3), GETPOSTINT($timekey.'sec', 3), GETPOSTINT($timekey.'month', 3), GETPOSTINT($timekey.'day', 3), GETPOSTINT($timekey.'year', 3), 'tzuserrel') : $datenotinstring;
9627 }
9628 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9629 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9630 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9631 $value = price2num($value);
9632 } elseif (isset($this->array_options['options_'.$key])) {
9633 $value = $this->array_options['options_'.$key];
9634 }
9635 }
9636
9637 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9638 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9639 if ($action == 'create' || $mode == 'create') {
9640 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9641 }
9642 }
9643
9644 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9645 if ($action == 'create') {
9646 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9647 }
9648 }
9649
9650
9651 $labeltoshow = $langs->trans($label);
9652 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9653 if ($display_type == 'card') {
9654 $out .= '<tr '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="field_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields'.($extrafields_collapse_num ? ' trextrafieldsgroup' : '').' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9655 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9656 $out .= '<td></td>';
9657 }
9658 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9659 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9660 $out .= ' tdtop';
9661 }
9662 } elseif ($display_type == 'line') {
9663 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9664 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9665 }
9666 //$out .= "titlefield";
9667 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9668 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9669 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9670 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9671 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9672 $out .= ' fieldrequired';
9673 }
9674 }
9675 $out .= '">';
9676 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9677 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9678 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9679 } else {
9680 $out .= $labeltoshow;
9681 }
9682 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9683 $out .= '&nbsp;<span style="color: red">*</span>';
9684 }
9685 } else {
9686 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9687 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9688 } else {
9689 $out .= $labeltoshow;
9690 }
9691 }
9692
9693 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9694
9695 // Second column
9696 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9697 if ($display_type == 'card') {
9698 // a first td column was already output (and may be another on before if MAIN_VIEW_LINE_NUMBER set), so this td is the next one
9699 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9700 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9701 $out .= '>';
9702 } elseif ($display_type == 'line') {
9703 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').' style="display: inline-block" class="valuefieldcreate '.$this->element.'_extras_'.$key.' extra_inline_'.$extrafields->attributes[$this->table_element]['type'][$key].'">';
9704 }
9705
9706 switch ($mode) {
9707 case "view":
9708 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9709 break;
9710 case "create":
9711 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9712 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9713 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9714 }
9715 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9716 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9717 break;
9718 case "edit":
9719 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9720 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9721 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9722 }
9723 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9724 break;
9725 }
9726
9727 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9728 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9729 $e++;
9730 }
9731 }
9732 $out .= "\n";
9733 // Add code to manage list depending on others
9734 if (!empty($conf->use_javascript_ajax)) {
9735 $out .= $this->getJSListDependancies();
9736 }
9737
9738 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9739
9740 if (empty($nbofextrafieldsshown)) {
9741 $out = '';
9742 }
9743 }
9744 }
9745
9746 $out .= $hookResPrint;
9747
9748 return $out;
9749 }
9750
9755 public function getJSListDependancies($type = '_extra')
9756 {
9757 $out = '
9758 <script nonce="'.getNonce().'">
9759 jQuery(document).ready(function() {
9760 function showOptions'.$type.'(child_list, parent_list, orig_select)
9761 {
9762 var val = $("select[name=\""+parent_list+"\"]").val();
9763 var parentVal = parent_list + ":" + val;
9764 if(typeof val == "string"){
9765 if(val != "") {
9766 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9767 $("select[name=\""+child_list+"\"] option[parent]").remove();
9768 $("select[name=\""+child_list+"\"]").append(options);
9769 } else {
9770 var options = orig_select.find("option[parent]").clone();
9771 $("select[name=\""+child_list+"\"] option[parent]").remove();
9772 $("select[name=\""+child_list+"\"]").append(options);
9773 }
9774 } else if(val > 0) {
9775 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9776 $("select[name=\""+child_list+"\"] option[parent]").remove();
9777 $("select[name=\""+child_list+"\"]").append(options);
9778 } else {
9779 var options = orig_select.find("option[parent]").clone();
9780 $("select[name=\""+child_list+"\"] option[parent]").remove();
9781 $("select[name=\""+child_list+"\"]").append(options);
9782 }
9783 }
9784 function setListDependencies'.$type.'() {
9785 jQuery("select option[parent]").parent().each(function() {
9786 var orig_select = {};
9787 var child_list = $(this).attr("name");
9788 orig_select[child_list] = $(this).clone();
9789 var parent = $(this).find("option[parent]:first").attr("parent");
9790 var infos = parent.split(":");
9791 var parent_list = infos[0];
9792
9793 //Hide daughters lists
9794 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9795 $("#"+child_list).hide();
9796 //Show mother lists
9797 } else if ($("#"+parent_list).val() != 0){
9798 $("#"+parent_list).show();
9799 }
9800 //Show the child list if the parent list value is selected
9801 $("select[name=\""+parent_list+"\"]").click(function() {
9802 if ($(this).val() != 0){
9803 $("#"+child_list).show()
9804 }
9805 });
9806
9807 //When we change parent list
9808 $("select[name=\""+parent_list+"\"]").change(function() {
9809 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9810 //Select the value 0 on child list after a change on the parent list
9811 $("#"+child_list).val(0).trigger("change");
9812 //Hide child lists if the parent value is set to 0
9813 if ($(this).val() == 0){
9814 $("#"+child_list).hide();
9815 }
9816 });
9817 });
9818 }
9819
9820 setListDependencies'.$type.'();
9821 });
9822 </script>'."\n";
9823 return $out;
9824 }
9825
9831 public function getRights()
9832 {
9833 global $user;
9834
9835 $module = empty($this->module) ? '' : $this->module;
9836 $element = $this->element;
9837
9838 if ($element == 'facturerec') {
9839 $element = 'facture';
9840 } elseif ($element == 'invoice_supplier_rec') {
9841 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9842 } elseif ($module && $user->hasRight($module, $element)) {
9843 // for modules built with ModuleBuilder
9844 return $user->hasRight($module, $element);
9845 }
9846
9847 return $user->rights->$element;
9848 }
9849
9862 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9863 {
9864 global $hookmanager;
9865
9866 $parameters = array(
9867 'origin_id' => $origin_id,
9868 'dest_id' => $dest_id,
9869 'tables' => $tables,
9870 );
9871 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
9872 if ($reshook) {
9873 return true; // replacement code
9874 } elseif ($reshook < 0) {
9875 return $ignoreerrors === 1; // failure
9876 } // reshook = 0 => execute normal code
9877
9878 foreach ($tables as $table) {
9879 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
9880
9881 if (!$dbs->query($sql)) {
9882 if ($ignoreerrors) {
9883 return true; // FIXME Not enough. If there is A-B on the kept thirdparty and B-C on the old one, we must get A-B-C after merge. Not A-B.
9884 }
9885 //$this->errors = $db->lasterror();
9886 return false;
9887 }
9888 }
9889
9890 return true;
9891 }
9892
9905 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9906 {
9907 foreach ($tables as $table) {
9908 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
9909
9910 if (!$dbs->query($sql)) {
9911 if ($ignoreerrors) {
9912 return true; // TODO Not enough. If there is A-B on kept product and B-C on old one, we must get A-B-C after merge. Not A-B.
9913 }
9914 //$this->errors = $db->lasterror();
9915 return false;
9916 }
9917 }
9918
9919 return true;
9920 }
9921
9934 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
9935 {
9936 global $conf;
9937
9938 $buyPrice = 0;
9939
9940 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
9941 // When ForceBuyingPriceIfNull is set
9942 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
9943 } else {
9944 // Get cost price for margin calculation
9945 if (!empty($fk_product) && $fk_product > 0) {
9946 $result = 0;
9947 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
9948 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9949 $product = new Product($this->db);
9950 $result = $product->fetch($fk_product);
9951 if ($result <= 0) {
9952 $this->errors[] = 'ErrorProductIdDoesNotExists';
9953 return -1;
9954 }
9955 if ($product->cost_price > 0) {
9956 $buyPrice = $product->cost_price;
9957 } elseif ($product->pmp > 0) {
9958 $buyPrice = $product->pmp;
9959 }
9960 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
9961 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9962 $product = new Product($this->db);
9963 $result = $product->fetch($fk_product);
9964 if ($result <= 0) {
9965 $this->errors[] = 'ErrorProductIdDoesNotExists';
9966 return -1;
9967 }
9968 if ($product->pmp > 0) {
9969 $buyPrice = $product->pmp;
9970 }
9971 }
9972
9973 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
9974 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
9975 $productFournisseur = new ProductFournisseur($this->db);
9976 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
9977 $buyPrice = $productFournisseur->fourn_unitprice;
9978 } elseif ($result < 0) {
9979 $this->errors[] = $productFournisseur->error;
9980 return -2;
9981 }
9982 }
9983 }
9984 }
9985
9986 return (float) $buyPrice;
9987 }
9988
9996 public function getDataToShowPhoto($modulepart, $imagesize)
9997 {
9998 // See getDataToShowPhoto() implemented by Product for example.
9999 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10000 }
10001
10002
10003 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10023 public function show_photos($modulepart, $sdir, $size = 0, $nbmax = 0, $nbbyrow = 5, $showfilename = 0, $showaction = 0, $maxHeight = 120, $maxWidth = 160, $nolink = 0, $overwritetitle = 0, $usesharelink = 0, $cache = '', $addphotorefcss = 'photoref')
10024 {
10025 // phpcs:enable
10026 global $user, $langs;
10027
10028 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10029 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10030
10031 $sortfield = 'position_name';
10032 $sortorder = 'asc';
10033
10034 $dir = $sdir.'/';
10035 $pdir = '/';
10036
10037 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10038 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10039
10040 // For backward compatibility
10041 if ($modulepart == 'product') {
10042 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10043 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10044 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10045 }
10046 }
10047 if ($modulepart == 'category') {
10048 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10049 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10050 }
10051
10052 // Defined relative dir to DOL_DATA_ROOT
10053 $relativedir = '';
10054 if ($dir) {
10055 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10056 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10057 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10058 }
10059
10060 $dirthumb = $dir.'thumbs/';
10061 $pdirthumb = $pdir.'thumbs/';
10062
10063 $return = '<!-- Photo -->'."\n";
10064 $nbphoto = 0;
10065
10066 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10067
10068 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10069 {
10070 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10071 $filearray=array_merge($filearray, $filearrayold);
10072 }*/
10073
10074 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10075 '@phan-var-force array<array{name:string,path:string,level1name:string,relativename:string,fullname:string,date:string,size:int,perm:int,type:string,position_name:string,cover:string,keywords:string,acl:string,rowid:int,label:string,share:string}> $filearray';
10076
10077 if (count($filearray)) {
10078 if ($sortfield && $sortorder) {
10079 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10080 }
10081
10082 foreach ($filearray as $key => $val) {
10083 $photo = '';
10084 $file = $val['name'];
10085
10086 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10087 if (image_format_supported($file) >= 0) {
10088 $nbphoto++;
10089 $photo = $file;
10090 $viewfilename = $file;
10091
10092 if ($size == 1 || $size == 'small') { // Format vignette
10093 // Find name of thumb file
10094 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10095 if (!dol_is_file($dirthumb.$photo_vignette)) {
10096 // The thumb does not exists, so we will use the original file
10097 $dirthumb = $dir;
10098 $pdirthumb = $pdir;
10099 $photo_vignette = basename($file);
10100 }
10101
10102 // Get filesize of original file
10103 $imgarray = dol_getImageSize($dir.$photo);
10104
10105 if ($nbbyrow > 0) {
10106 if ($nbphoto == 1) {
10107 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10108 }
10109
10110 if ($nbphoto % $nbbyrow == 1) {
10111 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10112 }
10113 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10114 } elseif ($nbbyrow < 0) {
10115 $return .= '<div class="inline-block">'."\n";
10116 }
10117
10118 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10119 if (empty($nolink)) {
10120 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10121 if ($urladvanced) {
10122 $return .= '<a href="'.$urladvanced.'">';
10123 } else {
10124 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10125 }
10126 }
10127
10128 // Show image (width height=$maxHeight)
10129 // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
10130 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10131 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10132 if ($overwritetitle) {
10133 if (is_numeric($overwritetitle)) {
10134 $alt = '';
10135 } else {
10136 $alt = $overwritetitle;
10137 }
10138 }
10139 if (empty($cache) && !empty($val['label'])) {
10140 // label is md5 of file
10141 // use it in url to say we want to cache this version of the file
10142 $cache = $val['label'];
10143 }
10144 if ($usesharelink) {
10145 if (array_key_exists('share', $val) && $val['share']) {
10146 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10147 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10148 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).($cache ? '&cache='.urlencode($cache) : '').'" title="'.dol_escape_htmltag($alt).'">';
10149 } else {
10150 $return .= '<!-- Show original file -->';
10151 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).($cache ? '&cache='.urlencode($cache) : '').'" title="'.dol_escape_htmltag($alt).'">';
10152 }
10153 } else {
10154 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10155 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10156 }
10157 } else {
10158 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10159 $return .= '<!-- Show thumb -->';
10160 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').' maxwidth150onsmartphone maxwidth200"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.($cache ? '&cache='.urlencode($cache) : '').'&file='.urlencode($pdirthumb.$photo_vignette).'" title="'.dol_escape_htmltag($alt).'">';
10161 } else {
10162 $return .= '<!-- Show original file -->';
10163 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.($cache ? '&cache='.urlencode($cache) : '').'&file='.urlencode($pdir.$photo).'" title="'.dol_escape_htmltag($alt).'">';
10164 }
10165 }
10166
10167 if (empty($nolink)) {
10168 $return .= '</a>';
10169 }
10170
10171 if ($showfilename) {
10172 $return .= '<br>'.$viewfilename;
10173 }
10174 if ($showaction) {
10175 $return .= '<br>';
10176 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10177 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10178 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=addthumb&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">'.img_picto($langs->trans('GenerateThumb'), 'refresh').'&nbsp;&nbsp;</a>';
10179 }
10180 // Special case for product
10181 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10182 // Link to resize
10183 $return .= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"), 'resize', '').'</a> &nbsp; ';
10184
10185 // Link to delete
10186 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10187 $return .= img_delete().'</a>';
10188 }
10189 }
10190 $return .= "\n";
10191
10192 if ($nbbyrow > 0) {
10193 $return .= '</td>';
10194 if (($nbphoto % $nbbyrow) == 0) {
10195 $return .= '</tr>';
10196 }
10197 } elseif ($nbbyrow < 0) {
10198 $return .= '</div>'."\n";
10199 }
10200 }
10201
10202 if (empty($size)) { // Format origine
10203 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10204
10205 if ($showfilename) {
10206 $return .= '<br>'.$viewfilename;
10207 }
10208 if ($showaction) {
10209 // Special case for product
10210 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10211 // Link to resize
10212 $return .= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"), 'resize', '').'</a> &nbsp; ';
10213
10214 // Link to delete
10215 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10216 $return .= img_delete().'</a>';
10217 }
10218 }
10219 }
10220
10221 // On continue ou on arrete de boucler ?
10222 if ($nbmax && $nbphoto >= $nbmax) {
10223 break;
10224 }
10225 }
10226 }
10227
10228 if ($size == 1 || $size == 'small') {
10229 if ($nbbyrow > 0) {
10230 // Ferme tableau
10231 while ($nbphoto % $nbbyrow) {
10232 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10233 $nbphoto++;
10234 }
10235
10236 if ($nbphoto) {
10237 $return .= '</table>';
10238 }
10239 }
10240 }
10241 }
10242
10243 $this->nbphoto = $nbphoto;
10244
10245 return $return;
10246 }
10247
10248
10255 protected function isArray($info)
10256 {
10257 if (is_array($info)) {
10258 if (isset($info['type']) && $info['type'] == 'array') {
10259 return true;
10260 } else {
10261 return false;
10262 }
10263 }
10264 return false;
10265 }
10266
10273 public function isDate($info)
10274 {
10275 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10276 return true;
10277 }
10278 return false;
10279 }
10280
10287 public function isDuration($info)
10288 {
10289 if (is_array($info)) {
10290 if (isset($info['type']) && ($info['type'] == 'duration')) {
10291 return true;
10292 } else {
10293 return false;
10294 }
10295 } else {
10296 return false;
10297 }
10298 }
10299
10306 public function isInt($info)
10307 {
10308 if (is_array($info)) {
10309 if (isset($info['type']) && (preg_match('/(^int|int$)/i', $info['type']))) {
10310 return true;
10311 } else {
10312 return false;
10313 }
10314 } else {
10315 return false;
10316 }
10317 }
10318
10325 public function isFloat($info)
10326 {
10327 if (is_array($info)) {
10328 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10329 return true;
10330 } else {
10331 return false;
10332 }
10333 }
10334 return false;
10335 }
10336
10343 public function isText($info)
10344 {
10345 if (is_array($info)) {
10346 if (isset($info['type']) && $info['type'] == 'text') {
10347 return true;
10348 } else {
10349 return false;
10350 }
10351 }
10352 return false;
10353 }
10354
10361 protected function canBeNull($info)
10362 {
10363 if (is_array($info)) {
10364 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10365 return true;
10366 } else {
10367 return false;
10368 }
10369 }
10370 return true;
10371 }
10372
10379 protected function isForcedToNullIfZero($info)
10380 {
10381 if (is_array($info)) {
10382 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10383 return true;
10384 } else {
10385 return false;
10386 }
10387 }
10388 return false;
10389 }
10390
10397 protected function isIndex($info)
10398 {
10399 if (is_array($info)) {
10400 if (array_key_exists('index', $info) && $info['index'] == true) {
10401 return true;
10402 } else {
10403 return false;
10404 }
10405 }
10406 return false;
10407 }
10408
10409
10418 protected function setSaveQuery()
10419 {
10420 global $conf;
10421
10422 $queryarray = array();
10423 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10424 // Depending on field type ('datetime', ...)
10425 if ($this->isDate($info)) {
10426 if (empty($this->{$field})) {
10427 $queryarray[$field] = null;
10428 } else {
10429 $queryarray[$field] = $this->db->idate($this->{$field});
10430 }
10431 } elseif ($this->isDuration($info)) {
10432 // $this->{$field} may be null, '', 0, '0', 123, '123'
10433 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10434 if (!isset($this->{$field})) {
10435 if (!empty($info['default'])) {
10436 $queryarray[$field] = $info['default'];
10437 } else {
10438 $queryarray[$field] = 0;
10439 }
10440 } else {
10441 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10442 }
10443 } else {
10444 $queryarray[$field] = null;
10445 }
10446 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10447 if ($field == 'entity' && is_null($this->{$field})) {
10448 $queryarray[$field] = ((int) $conf->entity);
10449 } else {
10450 // $this->{$field} may be null, '', 0, '0', 123, '123'
10451 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10452 if (!isset($this->{$field})) {
10453 $queryarray[$field] = 0;
10454 } elseif ($this->isInt($info)) {
10455 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10456 } elseif ($this->isFloat($info)) {
10457 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10458 }
10459 } else {
10460 $queryarray[$field] = null;
10461 }
10462 }
10463 } else {
10464 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10465 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10466 $queryarray[$field] = $this->{$field};
10467 }
10468
10469 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10470 unset($queryarray[$field]);
10471 }
10472 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10473 $queryarray[$field] = null; // May force 0 to null
10474 }
10475 }
10476
10477 return $queryarray;
10478 }
10479
10486 public function setVarsFromFetchObj(&$obj)
10487 {
10488 global $db;
10489
10490 foreach ($this->fields as $field => $info) {
10491 if ($this->isDate($info)) {
10492 if (!isset($obj->$field) || is_null($obj->$field) || $obj->$field === '' || $obj->$field === '0000-00-00 00:00:00' || $obj->$field === '1000-01-01 00:00:00') {
10493 $this->$field = '';
10494 } else {
10495 $this->$field = $db->jdate($obj->$field);
10496 }
10497 } elseif ($this->isInt($info)) {
10498 if ($field == 'rowid') {
10499 $this->id = (int) $obj->$field;
10500 } else {
10501 if ($this->isForcedToNullIfZero($info)) {
10502 if (empty($obj->$field)) {
10503 $this->$field = null;
10504 } else {
10505 $this->$field = (int) $obj->$field;
10506 }
10507 } else {
10508 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10509 $this->$field = (int) $obj->$field;
10510 } else {
10511 $this->$field = null;
10512 }
10513 }
10514 }
10515 } elseif ($this->isFloat($info)) {
10516 if ($this->isForcedToNullIfZero($info)) {
10517 if (empty($obj->$field)) {
10518 $this->$field = null;
10519 } else {
10520 $this->$field = (float) $obj->$field;
10521 }
10522 } else {
10523 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10524 $this->$field = (float) $obj->$field;
10525 } else {
10526 $this->$field = null;
10527 }
10528 }
10529 } else {
10530 $this->$field = isset($obj->$field) ? $obj->$field : null;
10531 }
10532 }
10533
10534 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10535 if (!isset($this->fields['ref']) && isset($this->id)) {
10536 $this->ref = (string) $this->id;
10537 }
10538 }
10539
10544 public function emtpyObjectVars()
10545 {
10546 foreach ($this->fields as $field => $arr) {
10547 $this->$field = null;
10548 }
10549 }
10550
10558 public function getFieldList($alias = '', $excludefields = array())
10559 {
10560 $keys = array_keys($this->fields);
10561 if (!empty($alias)) {
10562 $keys_with_alias = array();
10563 foreach ($keys as $fieldname) {
10564 if (!empty($excludefields)) {
10565 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10566 continue;
10567 }
10568 }
10569 $keys_with_alias[] = $alias . '.' . $fieldname;
10570 }
10571 return implode(', ', $keys_with_alias);
10572 } else {
10573 return implode(', ', $keys);
10574 }
10575 }
10576
10584 protected function quote($value, $fieldsentry)
10585 {
10586 if (is_null($value)) {
10587 return 'NULL';
10588 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10589 return price2num((string) $value);
10590 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10591 return (int) $value;
10592 } elseif ($fieldsentry['type'] == 'boolean') {
10593 if ($value) {
10594 return 'true';
10595 } else {
10596 return 'false';
10597 }
10598 } else {
10599 return "'".$this->db->escape($value)."'";
10600 }
10601 }
10602
10603
10611 public function createCommon(User $user, $notrigger = 0)
10612 {
10613 global $conf;
10614 //global $langs; // Should be able to work with $langs loaded
10615
10616 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10617
10618 $error = 0;
10619
10620 $now = dol_now();
10621
10622 $fieldvalues = $this->setSaveQuery();
10623
10624 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10625
10626 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10627 $fieldvalues['date_creation'] = $this->db->idate($now);
10628 $this->date_creation = $this->db->idate($now);
10629 }
10630 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10631 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10632 $fieldvalues['fk_user_creat'] = $user->id;
10633 $this->fk_user_creat = $user->id;
10634 }
10635 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10636 $fieldvalues['user_creation_id'] = $user->id;
10637 $this->user_creation_id = $user->id;
10638 }
10639 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10640 // @phan-suppress-next-line PhanUndeclaredProperty
10641 $tmparray = dol_hash($this->pass, '0', 0, 1);
10642 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10643 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10644 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10645 }
10646 }
10647 if (array_key_exists('ref', $fieldvalues)) {
10648 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10649 }
10650 if (getDolGlobalString('MAIN_DISABLE_AUTO_UPDATE_OF_TMS_FIELDS') && array_key_exists('tms', $fieldvalues)) { // If we want the explicit update of tms fields instead of deprecated update by database
10651 $fieldvalues['tms'] = $this->db->idate($now);
10652 }
10653
10654 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10655
10656 $keys = array();
10657 $values = array(); // Array to store string forged for SQL syntax
10658 foreach ($fieldvalues as $k => $v) {
10659 $keys[$k] = $k;
10660 $value = $this->fields[$k];
10661 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10662 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10663 }
10664
10665 // Clean and check mandatory
10666 foreach ($keys as $key) {
10667 if (!isset($this->fields[$key])) {
10668 continue;
10669 }
10670 $key_fields = $this->fields[$key];
10671
10672 // If field is an implicit foreign key field (so type = 'integer:...')
10673 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10674 $values[$key] = '';
10675 }
10676 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10677 $values[$key] = '';
10678 }
10679
10680 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10681 $error++;
10682 global $langs;
10683 if (empty($langs)) {
10684 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10685 $langs = new Translate('', $conf);
10686 $langs->setDefaultLang();
10687 $langs->load("errors");
10688 }
10689 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10690 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10691 }
10692
10693 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10694 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && array_key_exists('default', $key_fields) && !is_null($key_fields['default'])) {
10695 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10696 }
10697
10698 // If field is an implicit foreign key field (so type = 'integer:...')
10699 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10700 if (isset($key_fields['default'])) {
10701 $values[$key] = ((int) $key_fields['default']);
10702 } else {
10703 $values[$key] = 'null';
10704 }
10705 }
10706 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10707 $values[$key] = 'null';
10708 }
10709 }
10710
10711 if ($error) {
10712 return -1;
10713 }
10714
10715 $this->db->begin();
10716
10717 if (!$error) {
10718 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10719 $sql .= " (".implode(", ", $keys).')';
10720 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10721
10722 $res = $this->db->query($sql);
10723 if (!$res) {
10724 $error++;
10725 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10726 $this->errors[] = "ErrorRefAlreadyExists";
10727 } else {
10728 $this->errors[] = $this->db->lasterror();
10729 }
10730 }
10731 }
10732
10733 if (!$error) {
10734 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10735 }
10736
10737 // If we have a field ref with a default value of (PROV)
10738 if (!$error) {
10739 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10740 if (array_key_exists('ref', $this->fields) && array_key_exists('notnull', $this->fields['ref']) && $this->fields['ref']['notnull'] > 0 && array_key_exists('default', $this->fields['ref']) && $this->fields['ref']['default'] == '(PROV)') {
10741 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10742 $resqlupdate = $this->db->query($sql);
10743
10744 if ($resqlupdate === false) {
10745 $error++;
10746 $this->errors[] = $this->db->lasterror();
10747 } else {
10748 $this->ref = '(PROV'.$this->id.')';
10749 }
10750 }
10751 }
10752
10753 // Create extrafields
10754 if (!$error) {
10755 $result = $this->insertExtraFields();
10756 if ($result < 0) {
10757 $error++;
10758 }
10759 }
10760
10761 // Create lines
10762 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10763 foreach ($this->lines as $line) {
10764 $keyforparent = $this->fk_element;
10765 $line->$keyforparent = $this->id;
10766
10767 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10768 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10769 if (!is_object($line)) {
10770 $line = (object) $line;
10771 }
10772
10773 $result = 0;
10774 if (method_exists($line, 'insert')) {
10775 $result = $line->insert($user, 1);
10776 } elseif (method_exists($line, 'create')) {
10777 $result = $line->create($user, 1);
10778 }
10779 if ($result < 0) {
10780 $this->error = $line->error;
10781 $this->db->rollback();
10782 return -1;
10783 }
10784 }
10785 }
10786
10787 // Triggers
10788 if (!$error && !$notrigger) {
10789 // Call triggers
10790 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10791 if ($result < 0) {
10792 $error++;
10793 }
10794 // End call triggers
10795 }
10796
10797 // Commit or rollback
10798 if ($error) {
10799 $this->db->rollback();
10800 return -1;
10801 } else {
10802 $this->db->commit();
10803 return $this->id;
10804 }
10805 }
10806
10807
10817 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
10818 {
10819 if (empty($id) && empty($ref) && empty($morewhere)) {
10820 return -1;
10821 }
10822
10823 $fieldlist = $this->getFieldList('t');
10824 if (empty($fieldlist)) {
10825 return 0;
10826 }
10827
10828 $sql = "SELECT ".$fieldlist;
10829 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
10830
10831 if (!empty($id)) {
10832 $sql .= ' WHERE t.rowid = '.((int) $id);
10833 } elseif (!empty($ref)) {
10834 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
10835 } else {
10836 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
10837 }
10838 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
10839 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
10840 }
10841 if ($morewhere) {
10842 $sql .= $morewhere;
10843 }
10844 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
10845
10846 $res = $this->db->query($sql);
10847 if ($res) {
10848 $obj = $this->db->fetch_object($res);
10849 if ($obj) {
10850 $this->setVarsFromFetchObj($obj);
10851
10852 // Retrieve all extrafield
10853 // fetch optionals attributes and labels
10854 if (empty($noextrafields)) {
10855 $result = $this->fetch_optionals();
10856 if ($result < 0) {
10857 $this->error = $this->db->lasterror();
10858 $this->errors[] = $this->error;
10859 return -4;
10860 }
10861 }
10862
10863 return $this->id;
10864 } else {
10865 return 0;
10866 }
10867 } else {
10868 $this->error = $this->db->lasterror();
10869 $this->errors[] = $this->error;
10870 return -1;
10871 }
10872 }
10873
10881 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
10882 {
10883 $objectlineclassname = get_class($this).'Line';
10884 if (!class_exists($objectlineclassname)) {
10885 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
10886 return -1;
10887 }
10888
10889 $objectline = new $objectlineclassname($this->db);
10890 '@phan-var-force CommonObjectLine $objectline';
10891
10892 $sql = "SELECT ".$objectline->getFieldList('l');
10893 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
10894 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
10895 if ($morewhere) {
10896 $sql .= $morewhere;
10897 }
10898 if (isset($objectline->fields['position'])) {
10899 $sql .= $this->db->order('position', 'ASC');
10900 }
10901
10902 $resql = $this->db->query($sql);
10903 if ($resql) {
10904 $num_rows = $this->db->num_rows($resql);
10905 $i = 0;
10906 $this->lines = array();
10907 while ($i < $num_rows) {
10908 $obj = $this->db->fetch_object($resql);
10909 if ($obj) {
10910 $newline = new $objectlineclassname($this->db);
10911 '@phan-var-force CommonObjectLine $newline';
10912 $newline->setVarsFromFetchObj($obj);
10913
10914 // Load also extrafields for the line
10915 if (empty($noextrafields)) {
10916 $newline->fetch_optionals();
10917 }
10918
10919 $this->lines[] = $newline;
10920 }
10921 $i++;
10922 }
10923
10924 return 1;
10925 } else {
10926 $this->error = $this->db->lasterror();
10927 $this->errors[] = $this->error;
10928 return -1;
10929 }
10930 }
10931
10939 public function updateCommon(User $user, $notrigger = 0)
10940 {
10941 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
10942
10943 $error = 0;
10944
10945 $now = dol_now();
10946
10947 // $this->oldcopy should have been set by the caller of update
10948 //if (empty($this->oldcopy)) {
10949 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
10950 // $this->oldcopy = dol_clone($this, 2);
10951 //}
10952
10953 $fieldvalues = $this->setSaveQuery();
10954
10955 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10956
10957 if (array_key_exists('date_modification', $fieldvalues)) {
10958 $fieldvalues['date_modification'] = $this->db->idate($now);
10959 }
10960 if (getDolGlobalString('MAIN_DISABLE_AUTO_UPDATE_OF_TMS_FIELDS') && array_key_exists('tms', $fieldvalues)) { // If we want the explicit update of tms fields instead of deprecated update by database
10961 $fieldvalues['tms'] = $this->db->idate($now);
10962 }
10963 if (array_key_exists('fk_user_modif', $fieldvalues)) {
10964 $fieldvalues['fk_user_modif'] = $user->id;
10965 }
10966 if (array_key_exists('user_modification_id', $fieldvalues)) {
10967 $fieldvalues['user_modification_id'] = $user->id;
10968 }
10969 // @phan-suppress-next-line PhanUndeclaredProperty
10970 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
10971 // @phan-suppress-next-line PhanUndeclaredProperty
10972 $tmparray = dol_hash($this->pass, '0', 0, 1);
10973 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10974 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10975 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10976 }
10977 }
10978 if (array_key_exists('ref', $fieldvalues)) {
10979 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10980 }
10981
10982 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
10983
10984 // Add quotes and escape on fields with type string
10985 $keys = array();
10986 $values = array();
10987 $tmp = array();
10988 foreach ($fieldvalues as $k => $v) {
10989 $keys[$k] = $k;
10990 $value = $this->fields[$k];
10991 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10992 $values[$k] = $this->quote($v, $value);
10993 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
10994 // Clean values for text with selectbox
10995 $v = preg_replace('/\s/', ',', $v);
10996 $v = preg_replace('/,+/', ',', $v);
10997 }
10998 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10999 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
11000 }
11001
11002 // Clean and check mandatory fields
11003 foreach ($keys as $key) {
11004 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11005 $values[$key] = ''; // This is an implicit foreign key field
11006 }
11007 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11008 $values[$key] = ''; // This is an explicit foreign key field
11009 }
11010
11011 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11012 /*
11013 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11014 {
11015 $error++;
11016 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11017 }*/
11018 }
11019
11020 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
11021
11022 $this->db->begin();
11023
11024 if (!$error) {
11025 $res = $this->db->query($sql);
11026 if (!$res) {
11027 $error++;
11028 $this->errors[] = $this->db->lasterror();
11029 }
11030 }
11031
11032 // Update extrafield
11033 if (!$error) {
11034 $result = $this->insertExtraFields(); // This update extrafields
11035 if ($result < 0) {
11036 $error++;
11037 }
11038 }
11039
11040 // Triggers
11041 if (!$error && !$notrigger) {
11042 // Call triggers
11043 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11044 if ($result < 0) {
11045 $error++;
11046 } //Do also here what you must do to rollback action if trigger fail
11047 // End call triggers
11048 }
11049
11050 // Commit or rollback
11051 if ($error) {
11052 $this->db->rollback();
11053 return -1;
11054 } else {
11055 $this->db->commit();
11056 return $this->id;
11057 }
11058 }
11059
11068 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11069 {
11070 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11071
11072 $error = 0;
11073
11074 $this->db->begin();
11075
11076 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11077 foreach ($this->childtables as $table) {
11078 $sql = "DELETE FROM ".$this->db->prefix().$table." WHERE ".$this->fk_element." = ".((int) $this->id);
11079 $resql = $this->db->query($sql);
11080 if (!$resql) {
11081 $this->error = $this->db->lasterror();
11082 $this->errors[] = $this->error;
11083 $this->db->rollback();
11084 return -1;
11085 }
11086 }
11087 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11088 $objectisused = $this->isObjectUsed($this->id);
11089 if (!empty($objectisused)) {
11090 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11091 $this->error = 'ErrorRecordHasChildren';
11092 $this->errors[] = $this->error;
11093 $this->db->rollback();
11094 return 0;
11095 }
11096 }
11097
11098 // Delete cascade first
11099 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11100 foreach ($this->childtablesoncascade as $tabletodelete) {
11101 $deleteFromObject = explode(':', $tabletodelete, 4);
11102 if (count($deleteFromObject) >= 2) {
11103 $className = str_replace('@', '', $deleteFromObject[0]);
11104 $filePath = $deleteFromObject[1];
11105 $columnName = $deleteFromObject[2];
11106 $filter = '';
11107 if (!empty($deleteFromObject[3])) {
11108 $filter = $deleteFromObject[3];
11109 }
11110
11111 if (dol_include_once($filePath)) {
11112 $childObject = new $className($this->db);
11113 if (method_exists($childObject, 'deleteByParentField')) {
11114 '@phan-var-force CommonObject $childObject';
11115 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11116 if ($result < 0) {
11117 $error++;
11118 $this->errors[] = $childObject->error;
11119 break;
11120 }
11121 } else {
11122 $error++;
11123 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11124 break;
11125 }
11126 } else {
11127 $error++;
11128 $this->errors[] = 'Cannot include child class file '.$filePath;
11129 break;
11130 }
11131 } else {
11132 // Delete record in child table
11133 $sql = "DELETE FROM ".$this->db->prefix().$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
11134
11135 $resql = $this->db->query($sql);
11136 if (!$resql) {
11137 $error++;
11138 $this->error = $this->db->lasterror();
11139 $this->errors[] = $this->error;
11140 break;
11141 }
11142 }
11143 }
11144 }
11145
11146 if (!$error) {
11147 if (!$notrigger) {
11148 // Call triggers
11149 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11150 if ($result < 0) {
11151 $error++;
11152 } // Do also here what you must do to rollback action if trigger fail
11153 // End call triggers
11154 }
11155 }
11156
11157 // Delete llx_ecm_files
11158 if (!$error) {
11159 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11160 if (!$res) {
11161 $error++;
11162 }
11163 }
11164
11165 if (!$error) {
11166 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11167 // For remove dir
11168 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11169 if (dol_is_dir($dir)) {
11170 if (!dol_delete_dir_recursive($dir)) {
11171 $this->errors[] = 'ErrorFailToDeleteDir';
11172 }
11173 }
11174 }
11175
11176 // Delete linked object
11177 $res = $this->deleteObjectLinked();
11178 if ($res < 0) {
11179 $error++;
11180 }
11181
11182 if (!$error && !empty($this->isextrafieldmanaged)) {
11183 $result = $this->deleteExtraFields();
11184 if ($result < 0) {
11185 $error++;
11186 }
11187 }
11188
11189 if (!$error) {
11190 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11191
11192 $resql = $this->db->query($sql);
11193 if (!$resql) {
11194 $error++;
11195 $this->errors[] = $this->db->lasterror();
11196 }
11197 }
11198
11199 // Commit or rollback
11200 if ($error) {
11201 $this->db->rollback();
11202 return -1;
11203 } else {
11204 $this->db->commit();
11205 return 1;
11206 }
11207 }
11208
11220 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11221 {
11222 global $user;
11223
11224 $error = 0;
11225 $deleted = 0;
11226
11227 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11228
11229 if (!empty($parentId) && !empty($parentField)) {
11230 if (empty($this->table_element)) {
11231 $this->error = 'Property table_element for object is not defined';
11232 $this->errors[] = $this->error;
11233 $error++;
11234 return -1;
11235 }
11236 if (!method_exists($this, 'fetch')) {
11237 $this->error = 'Method fetch for object is not defined';
11238 $this->errors[] = $this->error;
11239 $error++;
11240 return -1;
11241 }
11242 if (!method_exists($this, 'delete')) {
11243 $this->error = 'Method delete for object is not defined';
11244 $this->errors[] = $this->error;
11245 $error++;
11246 return -1;
11247 }
11248
11249 $this->db->begin();
11250
11251 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11252 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11253
11254 // Manage filter
11255 $errormessage = '';
11256 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11257 if ($errormessage) {
11258 $this->errors[] = $errormessage;
11259 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11260 return -1;
11261 }
11262
11263 $resql = $this->db->query($sql);
11264 if (!$resql) {
11265 $this->errors[] = $this->db->lasterror();
11266 $error++;
11267 } else {
11268 while ($obj = $this->db->fetch_object($resql)) {
11269 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11270 if ($result < 0) {
11271 $error++;
11272 $this->errors[] = $this->error;
11273 } else {
11274 $result = $this->delete($user); // @phpstan-ignore-line
11275 if ($result < 0) {
11276 $error++;
11277 $this->errors[] = $this->error;
11278 } else {
11279 $deleted++;
11280 }
11281 }
11282 }
11283 }
11284
11285 if (empty($error)) {
11286 $this->db->commit();
11287 return $deleted;
11288 } else {
11289 $this->error = implode(', ', $this->errors);
11290 $this->db->rollback();
11291 return $error * -1;
11292 }
11293 }
11294
11295 return $deleted;
11296 }
11297
11306 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11307 {
11308 $error = 0;
11309
11310 $tmpforobjectclass = get_class($this);
11311 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11312
11313 $this->db->begin();
11314
11315 // Call trigger
11316 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11317 if ($result < 0) {
11318 $error++;
11319 }
11320 // End call triggers
11321
11322 if (empty($error)) {
11323 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11324 $sql .= " WHERE rowid = ".((int) $idline);
11325
11326 $resql = $this->db->query($sql);
11327 if (!$resql) {
11328 $this->error = "Error ".$this->db->lasterror();
11329 $error++;
11330 }
11331 }
11332
11333 if (empty($error)) {
11334 // Remove extrafields
11335 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11336 '@phan-var-force CommonObjectLine $tmpobjectline';
11337 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11338 $tmpobjectline->id = $idline;
11339 $result = $tmpobjectline->deleteExtraFields();
11340 if ($result < 0) {
11341 $error++;
11342 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11343 }
11344 }
11345 }
11346
11347 if (empty($error)) {
11348 $this->db->commit();
11349 return 1;
11350 } else {
11351 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11352 $this->db->rollback();
11353 return -1;
11354 }
11355 }
11356
11357
11368 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11369 {
11370 $error = 0;
11371
11372 $this->db->begin();
11373
11374 $statusfield = 'status';
11375 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11376 $statusfield = 'fk_statut';
11377 }
11378
11379 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11380 $sql .= " SET ".$statusfield." = ".((int) $status);
11381 $sql .= " WHERE rowid = ".((int) $this->id);
11382
11383 if ($this->db->query($sql)) {
11384 if (!$error) {
11385 $this->oldcopy = clone $this;
11386 }
11387
11388 if (!$error && !$notrigger) {
11389 // Call trigger
11390 $result = $this->call_trigger($triggercode, $user);
11391 if ($result < 0) {
11392 $error++;
11393 }
11394 }
11395
11396 if (!$error) {
11397 $this->status = $status;
11398 if (property_exists($this, 'statut')) { // For backward compatibility
11399 $this->statut = $status;
11400 }
11401 $this->db->commit();
11402 return 1;
11403 } else {
11404 $this->db->rollback();
11405 return -1;
11406 }
11407 } else {
11408 $this->error = $this->db->error();
11409 $this->db->rollback();
11410 return -1;
11411 }
11412 }
11413
11420 public function initAsSpecimenCommon()
11421 {
11422 global $user;
11423
11424 $this->id = 0;
11425 $this->specimen = 1;
11426 $fields = array(
11427 'label' => 'This is label',
11428 'ref' => 'ABCD1234',
11429 'description' => 'This is a description',
11430 'qty' => 123.12,
11431 'note_public' => 'Public note',
11432 'note_private' => 'Private note',
11433 'date_creation' => (dol_now() - 3600 * 48),
11434 'date_modification' => (dol_now() - 3600 * 24),
11435 'fk_user_creat' => $user->id,
11436 'fk_user_modif' => $user->id,
11437 'date' => dol_now(),
11438 );
11439 foreach ($fields as $key => $value) {
11440 if (array_key_exists($key, $this->fields)) {
11441 $this->{$key} = $value; // @phpstan-ignore-line
11442 }
11443 }
11444
11445 // Force values to default values when known
11446 if (property_exists($this, 'fields')) {
11447 foreach ($this->fields as $key => $value) {
11448 // If fields are already set, do nothing
11449 if (array_key_exists($key, $fields)) {
11450 continue;
11451 }
11452
11453 if (!empty($value['default'])) {
11454 $this->$key = $value['default'];
11455 }
11456 }
11457 }
11458
11459 return 1;
11460 }
11461
11462
11463 /* Part for comments */
11464
11470 public function fetchComments()
11471 {
11472 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11473
11474 $comment = new Comment($this->db);
11475 $result = $comment->fetchAllFor($this->element, $this->id);
11476 if ($result < 0) {
11477 $this->setErrorsFromObject($comment);
11478 return -1;
11479 } else {
11480 $this->comments = $comment->comments;
11481 }
11482 return count($this->comments);
11483 }
11484
11490 public function getNbComments()
11491 {
11492 return count($this->comments);
11493 }
11494
11501 public function trimParameters($parameters)
11502 {
11503 if (!is_array($parameters)) {
11504 return;
11505 }
11506 foreach ($parameters as $parameter) {
11507 if (isset($this->$parameter)) {
11508 $this->$parameter = trim($this->$parameter);
11509 }
11510 }
11511 }
11512
11513 /* Part for categories/tags */
11514
11525 public function getCategoriesCommon($type_categ)
11526 {
11527 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11528
11529 // Get current categories
11530 $c = new Categorie($this->db);
11531 $existing = $c->containing($this->id, $type_categ, 'id');
11532
11533 return $existing;
11534 }
11535
11548 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11549 {
11550 // Handle single category
11551 if (!is_array($categories)) {
11552 $categories = array($categories);
11553 }
11554
11555 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11556
11557 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11558
11559 if (empty($type_categ)) {
11560 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11561 return -1;
11562 }
11563
11564 // Get current categories
11565 $c = new Categorie($this->db);
11566 $existing = $c->containing($this->id, $type_categ, 'id');
11567 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11568 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11569 if (!is_array($existing)) {
11570 $existing = array();
11571 }
11572 if ($remove_existing) {
11573 // Diff
11574 $to_del = array_diff($existing, $categories);
11575 $to_add = array_diff($categories, $existing);
11576 } else {
11577 $to_del = array(); // Nothing to delete
11578 $to_add = array_diff($categories, $existing);
11579 }
11580
11581 $error = 0;
11582 $ok = 0;
11583
11584 // Process
11585 foreach ($to_del as $del) {
11586 if ($c->fetch($del) > 0) {
11587 $result = $c->del_type($this, $type_categ);
11588 if ($result < 0) {
11589 $error++;
11590 $this->error = $c->error;
11591 $this->errors = $c->errors;
11592 break;
11593 } else {
11594 $ok += $result;
11595 }
11596 }
11597 }
11598 foreach ($to_add as $add) {
11599 if ($c->fetch($add) > 0) {
11600 $result = $c->add_type($this, $type_categ);
11601 if ($result < 0) {
11602 $error++;
11603 $this->error = $c->error;
11604 $this->errors = $c->errors;
11605 break;
11606 } else {
11607 $ok += $result;
11608 }
11609 }
11610 }
11611
11612 return $error ? (-1 * $error) : $ok;
11613 }
11614
11623 public function cloneCategories($fromId, $toId, $type = '')
11624 {
11625 $this->db->begin();
11626
11627 if (empty($type)) {
11628 $type = $this->table_element;
11629 }
11630
11631 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11632 $categorystatic = new Categorie($this->db);
11633
11634 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11635 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11636
11637 $sql = "INSERT INTO ".$tablename." (fk_categorie, ".$fkname.")";
11638 $sql .= " SELECT fk_categorie, $toId FROM ".$tablename;
11639 $sql .= " WHERE ".$fkname." = ".((int) $fromId);
11640
11641 if (!$this->db->query($sql)) {
11642 $this->error = $this->db->lasterror();
11643 $this->db->rollback();
11644 return -1;
11645 }
11646
11647 $this->db->commit();
11648 return 1;
11649 }
11650
11657 public function deleteEcmFiles($mode = 0)
11658 {
11659 global $conf;
11660
11661 $this->db->begin();
11662
11663 // Delete in database with mode 0
11664 if ($mode == 0) {
11665 switch ($this->element) {
11666 case 'propal':
11667 $element = 'propale';
11668 break;
11669 case 'product':
11670 $element = 'produit';
11671 break;
11672 case 'order_supplier':
11673 $element = 'fournisseur/commande';
11674 break;
11675 case 'invoice_supplier':
11676 // Special cases that need to use get_exdir to get real dir of object
11677 // In future, all object should use this to define path of documents.
11678 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11679 break;
11680 case 'shipping':
11681 $element = 'expedition/sending';
11682 break;
11683 case 'task':
11684 case 'project_task':
11685 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11686
11687 $project_result = $this->fetchProject();
11688 if ($project_result >= 0) {
11689 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11690 }
11691 // no break
11692 case 'contrat':
11693 $element = 'contract';
11694 break;
11695 default:
11696 $element = $this->element;
11697 }
11698 '@phan-var-force string $element';
11699
11700 // Delete ecm_files_extrafields with mode 0 (using name)
11701 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11702 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11703 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11704 $sql .= ")";
11705
11706 if (!$this->db->query($sql)) {
11707 $this->error = $this->db->lasterror();
11708 $this->db->rollback();
11709 return false;
11710 }
11711
11712 // Delete ecm_files with mode 0 (using name)
11713 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11714 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11715 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11716
11717 if (!$this->db->query($sql)) {
11718 $this->error = $this->db->lasterror();
11719 $this->db->rollback();
11720 return false;
11721 }
11722 }
11723
11724 // Delete in database with mode 1
11725 if ($mode == 1) {
11726 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11727 $sql .= " WHERE fk_object IN (SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id).")";
11728 $resql = $this->db->query($sql);
11729 if (!$resql) {
11730 $this->error = $this->db->lasterror();
11731 $this->db->rollback();
11732 return false;
11733 }
11734
11735 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11736 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11737 $resql = $this->db->query($sql);
11738 if (!$resql) {
11739 $this->error = $this->db->lasterror();
11740 $this->db->rollback();
11741 return false;
11742 }
11743 }
11744
11745 $this->db->commit();
11746 return true;
11747 }
11748
11757 public function checkActiveProductInLines($status = 'onsale')
11758 {
11759 global $langs;
11760
11761 if (isModEnabled('product') || isModEnabled('service')) {
11762 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
11763
11764 $ret = true;
11765 $tmpproduct = new Product($this->db);
11766 foreach ($this->lines as $line) {
11767 if ($line->fk_product > 0) {
11768 $tmpproduct->fetch($line->fk_product);
11769 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
11770 if (!$tmpproduct->$statustotest) {
11771 $langs->load('products');
11772 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
11773 $ret = false;
11774 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
11775 break;
11776 }
11777 }
11778 }
11779 if (!$ret) {
11780 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
11781 }
11782 return $ret;
11783 } else {
11784 return true;
11785 }
11786 }
11787
11794 public function setErrorWithoutLog($message)
11795 {
11796 $this->error = $message;
11797 $this->errors[] = $message;
11798 }
11799
11807 public function setErrorWithLog($message, $loglevel = LOG_ERR)
11808 {
11809 $this->setErrorWithoutLog($message);
11810 dol_syslog($message, $loglevel);
11811 }
11812}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
global $dolibarr_main_url_root
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:475
$c
Definition line.php:331
$object ref
Definition info.php:90
Class to manage members of a foundation.
Class to manage categories.
Class to manage comment.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
getCategoriesCommon($type_categ)
Sets object to given categories.
indexFile($destfull, $update_main_doc_field)
Index a file into the ECM database.
getFormatedSupplierRef($objref)
Return supplier ref for screen output.
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteLineCommon(User $user, $idline, $notrigger=0)
Delete a line of object in database.
isEmpty()
isEmpty We consider CommonObject isEmpty if this->id is empty
clearFieldError($fieldKey)
clear validation message result for a field
static getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Count items linked to an object id in association table.
deleteEcmFiles($mode=0)
Delete related files of object in database.
getLastMainDocLink($modulepart, $initsharekey=0, $relativelink=0)
Return the link of last main doc file for direct public download.
liste_type_contact($source='internal', $order='position', $option=0, $activeonly=0, $code='')
Return array with list of possible values for type of contacts.
getTooltipContent($params)
getTooltipContent
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
swapContactStatus($rowid)
Update status of a contact linked to object.
getFieldError($fieldKey)
get field error message
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='')
Set status of an object.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
updateObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $f_user=null, $notrigger=0)
Update object linked of a current object.
setErrorWithoutLog($message)
Set the error message for the object without logging it.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
fetchObjectFrom($table, $field, $key, $element=null)
Load object from specific field.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
static deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Function used to remove all items linked to an object id in association table.
setFieldError($fieldKey, $msg='')
set validation error message a field
validateField($fields, $fieldKey, $fieldValue)
Return validation test result for a field.
getDataToShowPhoto($modulepart, $imagesize)
Function used to get the logos or photos of an object.
setMulticurrencyCode($code)
Change the multicurrency code.
emtpyObjectVars()
Sets all object fields to null.
fetch_projet()
Load the project with id $this->fk_project into this->project.
getIdContact($source, $code, $status=0)
Return id of contacts for a source and a contact code.
setExtraField($key, $value)
Convenience method for setting the value of an extrafield without actually updating it in the databas...
setDocModel($user, $modelpdf)
Set last model used by doc generator.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
getExtraField($key)
Convenience method for retrieving the value of an extrafield without actually fetching it from the da...
updateExtraField($key, $trigger=null, $userused=null)
Update 1 extra field value for the current object.
setPaymentTerms($id, $deposit_percent=null)
Change the payments terms.
isFloat($info)
Function test if type is float.
setBankAccount($fk_account, $notrigger=0, $userused=null)
Change the bank account.
setExtraParameters()
Set extra parameters.
delete_resource($rowid, $element='', $notrigger=0)
Delete a link to resource line.
setErrorsFromObject($object)
setErrorsFromObject
setShippingMethod($shipping_method_id, $notrigger=0, $userused=null)
Change the shipping method.
update_note_public($note)
Update public note (kept for backward compatibility)
getSpecialCode($lineid)
Get special code of a line.
clearObjectLinkedCache()
Clear the cache saying that all linked object were already loaded.
update_ref_ext($ref_ext)
Update external ref of element.
fetchOneLike($ref)
Looks for an object with ref matching the wildcard provided It does only work when $this->table_ref_f...
hasProductsOrServices($predefined=-1)
Function to say how many lines object contains.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
updateRangOfLine($rowid, $rang)
Update position of line (rang)
getDefaultCreateValueFor($fieldname, $alternatevalue=null, $type='alphanohtml')
Return the default value to use for a field when showing the create form of object.
add_element_resource($resource_id, $resource_type, $busy=0, $mandatory=0, $notrigger=0)
Add resources to the current object : add entry into llx_element_resources Need $this->element & $thi...
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
createCommon(User $user, $notrigger=0)
Create object in the database.
getTotalWeightVolume()
Return into unit=0, the calculated total of weight and volume of all lines * qty Calculate by adding ...
update_note($note, $suffix='', $notrigger=0)
Update note of element.
getFullAddress($withcountry=0, $sep="\n", $withregion=0, $extralangcode='')
Return full address of contact.
fetch_project()
Load the project with id $this->fk_project into this->project.
getFieldList($alias='', $excludefields=array())
Function to concat keys of fields.
getNbComments()
Return nb comments already posted.
setVarsFromFetchObj(&$obj)
Function to load data from a SQL pointer into properties of current object $this.
printObjectLines($action, $seller, $buyer, $selected=0, $dateSelector=0, $defaulttpldir='/core/tpl')
Return HTML table for object lines TODO Move this into an output class file (htmlline....
getChildrenOfLine($id, $includealltree=0)
Get children of line.
updateCommon(User $user, $notrigger=0)
Update object into database.
updateExtraLanguages($key, $trigger=null, $userused=null)
Update an extra language value for the current object.
deleteExtraFields()
Delete all extra fields values for the current object.
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
deprecatedProperties()
Provide list of deprecated properties and replacements.
setSaveQuery()
Function to return the array of data key-value from the ->fields and all the ->properties of an objec...
setValuesForExtraLanguages($onlykey='')
Fill array_options property of object by extrafields value (using for data sent by forms) Used for ex...
insertExtraLanguages($trigger='', $userused=null)
Add/Update all extra languages values for the current object.
setTransportMode($id)
Change the transport mode methods.
isArray($info)
Function test if type is array.
isInt($info)
Function test if type is integer.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
delete_contact($rowid, $notrigger=0)
Delete a link to contact line.
updateLineUp($rowid, $rang)
Update position of line up (rang)
fetch_user($userid)
Load the user with id $userid into this->user.
errorsToString()
Method to output saved errors.
getListContactId($source='external')
Return list of id of contacts of object.
setWarehouse($warehouse_id)
Change the warehouse.
setDeliveryAddress($id)
Define delivery address.
fetchBarCode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
getTotalDiscount()
Function that returns the total amount HT of discounts applied for all lines.
initAsSpecimenCommon()
Initialise object with example values Id must be 0 if object instance is a specimen.
printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected=0, $extrafields=null, $defaulttpldir='/core/tpl')
Return HTML content of a detail line TODO Move this into an output class file (htmlline....
copy_linked_contact($objFrom, $source='internal')
Copy contact from one element to current.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
getValueFrom($table, $id, $field)
Getter generic.
trimParameters($parameters)
Trim object parameters.
fetch_product()
Load the product with id $this->fk_product into this->product.
isIndex($info)
Function test if is indexed.
quote($value, $fieldsentry)
Add quote to field value if necessary.
formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir='/core/tpl')
Show add free and predefined products/services form.
fetchComments()
Load comments linked with current task.
line_down($rowid, $fk_parent_line=true)
Update a line to have a higher rank.
setValueFrom($field, $value, $table='', $id=null, $format='', $id_field='', $fuser=null, $trigkey='', $fk_user_field='fk_user_modif')
Setter generic.
fetch_contact($contactid=null)
Load object contact with id=$this->contact_id into $this->contact.
setRetainedWarrantyPaymentTerms($id)
Change the retained warranty payments terms.
delThumbs($file)
Delete thumbs.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $overwritetitle=0, $usesharelink=0, $cache='', $addphotorefcss='photoref')
Show photos of an object (nbmax maximum), into several columns.
fetchLinesCommon($morewhere='', $noextrafields=0)
Load object in memory from the database.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
getRangOfLine($rowid)
Get position of line (rang)
showInputField($val, $key, $value, $moreparam='', $keysuffix='', $keyprefix='', $morecss=0, $nonewbutton=0)
Return HTML string to put an input field into a page Code very similar with showInputField of extra f...
update_contact($rowid, $statut, $type_contact_id=0, $fk_socpeople=0)
Update a link to contact line.
getElementType()
Return an element type string formatted like element_element target_type and source_type.
load_previous_next_ref($filter, $fieldid, $nodbprefix=0)
Load properties id_previous and id_next by comparing $fieldid with $this->ref.
setCategoriesCommon($categories, $type_categ='', $remove_existing=true)
Sets object to given categories.
fetchValuesForExtraLanguages()
Function to get alternative languages of a data into $this->array_languages This method is NOT called...
fetchNoCompute($id)
Function to make a fetch but set environment to avoid to load computed values before.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
getJSListDependancies($type='_extra')
setProject($projectid, $notrigger=0)
Link element with a project.
isForcedToNullIfZero($info)
Function test if field is forced to null if zero or empty.
line_ajaxorder($rows)
Update position of line with ajax (rang)
printOriginLinesList($restrictlist='', $selectedLines=array())
Return HTML table table of source object lines TODO Move this and previous function into output html ...
fetch_origin()
Read linked origin object.
getIdOfLine($rang)
Get rowid of the line relative to its position.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
deleteByParentField($parentId=0, $parentField='', $filter='', $filtermode="AND")
Delete all child object from a parent ID.
static getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
Function used to get an array with all items linked to an object id in association table.
__clone()
Overwrite magic function to solve problem of cloning object that are kept as references.
setPaymentMethods($id)
Change the payments methods.
updateLineDown($rowid, $rang, $max)
Update position of line down (rang)
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
fetchCommon($id, $ref=null, $morewhere='', $noextrafields=0)
Load object in memory from the database.
deleteCommon(User $user, $notrigger=0, $forcechilddeletion=0)
Delete object in database.
getFormatedCustomerRef($objref)
Return customer ref for screen output.
getTooltipContentArray($params)
Return array of data to show into a tooltip.
isText($info)
Function test if type is text.
isDate($info)
Function test if type is date.
printOriginLine($line, $var, $restrictlist='', $defaulttpldir='/core/tpl', $selectedLines=array())
Return HTML with a line of table array of source object lines TODO Move this and previous function in...
showOptionals($extrafields, $mode='view', $params=null, $keysuffix='', $keyprefix='', $onetrtd='', $display_type='card')
Function to show lines of extrafields with output data.
getCanvas($id=0, $ref='')
Load type of canvas of an object if it exists.
line_up($rowid, $fk_parent_line=true)
Update a line to have a lower rank.
isDuration($info)
Function test if type is duration.
setErrorWithLog($message, $loglevel=LOG_ERR)
Set the error message for the object and log it.
canBeNull($info)
Function test if field can be null.
listeTypeContacts($source='internal', $option=0, $activeonly=0, $code='', $element='', $excludeelement='')
Return array with list of possible values for type of contacts.
getRights()
Returns the rights used for this class.
addThumbs($file, $quality=50)
Build thumb.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
cloneCategories($fromId, $toId, $type='')
Copy related categories to another object.
fetch_barcode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
Class to manage contact/addresses.
Class to manage absolute discounts.
Class to manage a WYSIWYG editor.
Class to manage Dolibarr database access.
Class to manage donations.
Definition don.class.php:41
Class to manage ECM files.
Class to manage standard extra fields.
static isEmptyValue($v, string $type)
Return if a value is "empty" for a mandatory vision.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage generation of HTML components Only common components must be here.
Parent class of subscription templates.
Parent class to manage intervention document templates.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_SERVICE
Service.
Class to manage projects.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
Class toolbox to validate values.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
getState($id, $withcode='0', $dbtouse=null, $withregion=0, $outputlangs=null, $entconv=1)
Return state translated from an id.
convertSecondToTime($iSecond, $format='all', $lengthOfDay=86400, $lengthOfWeek=7)
Return, in clear text, value of a number of seconds in days, hours and minutes.
Definition date.lib.php:247
dol_meta_create($object)
Create a meta file with document file into same directory.
completeFileArrayWithDatabaseInfo(&$filearray, $relativedir, $object=null)
Complete $filearray with data from database.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_is_dir($folder)
Test if filename is a directory.
dol_delete_preview($object)
Delete all preview files linked to object instance.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getPictoForType($key, $morecss='')
Return the picto for a data type.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into javascript code.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
getDictionaryValue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
Return the value of a filed into a dictionary for the record $id.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png, bmp and webp)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:129
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:125
getRandomPassword($generic=false, $replaceambiguouschars=null, $length=32)
Return a generated password using default module.
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.
dolEncrypt($chain, $key='', $ciphering='', $forceseed='')
Encode a string with a symmetric encryption.
dolDecrypt($chain, $key='')
Decode a string with a symmetric encryption.