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 ($line->special_code == SUBTOTALS_SPECIAL_CODE) {
5873 $this->tpl['strike'] = 1;
5874 }
5875
5876 // Output template part (modules that overwrite templates must declare this into descriptor)
5877 // Use global variables + $dateSelector + $seller and $buyer
5878 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5879 foreach ($dirtpls as $module => $reldir) {
5880 if (!empty($module)) {
5881 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5882 } else {
5883 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5884 }
5885
5886 if (empty($conf->file->strict_mode)) {
5887 $res = @include $tpl;
5888 } else {
5889 $res = include $tpl; // for debug
5890 }
5891 if ($res) {
5892 break;
5893 }
5894 }
5895 }
5896
5897
5898 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5910 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
5911 {
5912 // phpcs:enable
5913 global $user;
5914 $this->db->begin();
5915
5916 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5917 $sql .= "resource_id";
5918 $sql .= ", resource_type";
5919 $sql .= ", element_id";
5920 $sql .= ", element_type";
5921 $sql .= ", busy";
5922 $sql .= ", mandatory";
5923 $sql .= ") VALUES (";
5924 $sql .= ((int) $resource_id);
5925 $sql .= ", '".$this->db->escape($resource_type)."'";
5926 $sql .= ", '".$this->db->escape((string) $this->id)."'";
5927 $sql .= ", '".$this->db->escape($this->element)."'";
5928 $sql .= ", '".$this->db->escape((string) $busy)."'";
5929 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
5930 $sql .= ")";
5931
5932 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5933 if ($this->db->query($sql)) {
5934 if (!$notrigger) {
5935 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
5936 if ($result < 0) {
5937 $this->db->rollback();
5938 return -1;
5939 }
5940 }
5941 $this->db->commit();
5942 return 1;
5943 } else {
5944 $this->error = $this->db->lasterror();
5945 $this->db->rollback();
5946 return 0;
5947 }
5948 }
5949
5950 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5959 public function delete_resource($rowid, $element = '', $notrigger = 0)
5960 {
5961 // phpcs:enable
5962 global $user;
5963
5964 $this->db->begin();
5965
5966 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
5967 $sql .= " WHERE rowid = ".((int) $rowid);
5968
5969 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5970
5971 $resql = $this->db->query($sql);
5972 if (!$resql) {
5973 $this->error = $this->db->lasterror();
5974 $this->db->rollback();
5975 return -1;
5976 } else {
5977 if (!$notrigger) {
5978 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
5979 if ($result < 0) {
5980 $this->db->rollback();
5981 return -1;
5982 }
5983 }
5984 $this->db->commit();
5985 return 1;
5986 }
5987 }
5988
5989
5995 public function __clone()
5996 {
5997 // Force a copy of this->lines, otherwise it will point to same object.
5998 if (isset($this->lines) && is_array($this->lines)) {
5999 $nboflines = count($this->lines);
6000 for ($i = 0; $i < $nboflines; $i++) {
6001 if (is_object($this->lines[$i])) {
6002 $this->lines[$i] = clone $this->lines[$i];
6003 }
6004 }
6005 }
6006 }
6007
6021 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6022 {
6023 global $conf, $langs, $hookmanager, $action;
6024
6025 $srctemplatepath = '';
6026
6027 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6028 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6029
6030 if (!empty($reshook)) {
6031 return $reshook;
6032 }
6033
6034 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6035
6036 if (empty($modele)) {
6037 $this->error = 'BadValueForParameterModele';
6038 return -1;
6039 }
6040
6041 // Increase limit for PDF build
6042 $err = error_reporting();
6043 error_reporting(0);
6044 @set_time_limit(120);
6045 error_reporting($err);
6046
6047 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6048 $tmp = explode(':', $modele, 2);
6049 $saved_model = $modele;
6050 if (!empty($tmp[1])) {
6051 $modele = $tmp[0];
6052 $srctemplatepath = $tmp[1];
6053 }
6054
6055 // Search template files
6056 $file = '';
6057 $classname = '';
6058 $filefound = '';
6059 $dirmodels = array('/');
6060 if (is_array($conf->modules_parts['models'])) {
6061 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6062 }
6063 foreach ($dirmodels as $reldir) {
6064 foreach (array('doc', 'pdf') as $prefix) {
6065 if (in_array(get_class($this), array('Adherent'))) {
6066 // Member module use prefix_modele.class.php
6067 $file = $prefix."_".$modele.".class.php";
6068 } else {
6069 // Other module use prefix_modele.modules.php
6070 $file = $prefix."_".$modele.".modules.php";
6071 }
6072
6073 $file = dol_sanitizeFileName($file);
6074
6075 // We check if the file exists
6076 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6077 if (file_exists($file)) {
6078 $filefound = $file;
6079 $classname = $prefix.'_'.$modele;
6080 break;
6081 }
6082 }
6083 if ($filefound) {
6084 break;
6085 }
6086 }
6087
6088 if ($filefound === '' || $classname === '') {
6089 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6090 $this->errors[] = $this->error;
6091 dol_syslog($this->error, LOG_ERR);
6092 return -1;
6093 }
6094
6095 // Sanitize $filefound
6096 $filefound = dol_sanitizePathName($filefound);
6097
6098 // If generator was found
6099 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6100
6101 require_once $filefound;
6102
6103 $obj = new $classname($this->db);
6104
6105 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6106 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6107 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6108 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6109 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6110 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6111 // ModelePDFStock|ModelePDFStockTransfer|
6112 // ModeleDon|ModelePDFTask|
6113 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6114 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6115 '@phan-var-force ModelePDFMember $obj';
6116 // '@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';
6117
6118 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6119 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6120 $varfortemplatedir = $obj->scandir;
6121 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6122 $dirtoscan = getDolGlobalString($varfortemplatedir);
6123
6124 $listoffiles = array();
6125
6126 // Now we add first model found in directories scanned
6127 $listofdir = explode(',', $dirtoscan);
6128 foreach ($listofdir as $key => $tmpdir) {
6129 $tmpdir = trim($tmpdir);
6130 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6131 if (!$tmpdir) {
6132 unset($listofdir[$key]);
6133 continue;
6134 }
6135 if (is_dir($tmpdir)) {
6136 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6137 if (count($tmpfiles)) {
6138 $listoffiles = array_merge($listoffiles, $tmpfiles);
6139 }
6140 }
6141 }
6142
6143 if (count($listoffiles)) {
6144 foreach ($listoffiles as $record) {
6145 $srctemplatepath = $record['fullname'];
6146 break;
6147 }
6148 }
6149 }
6150
6151 if (empty($srctemplatepath)) {
6152 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6153 return -1;
6154 }
6155 }
6156
6157 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6158 if (!dol_is_file($srctemplatepath)) {
6159 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6160 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6161 return -1;
6162 }
6163 }
6164
6165 // We save charset_output to restore it because write_file can change it if needed for
6166 // output format that does not support UTF8.
6167 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6168
6169 // update model_pdf in object
6170 $this->model_pdf = $saved_model;
6171
6172 if ($obj instanceof ModelePDFMember) {
6173 if ($this instanceof Adherent) {
6174 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6175 } else {
6176 $resultwritefile = -1;
6177 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6178 }
6179 } elseif ($obj instanceof ModeleDon) {
6180 // Only 3 arguments
6181 if ($this instanceof Don) {
6182 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6183 } else {
6184 $resultwritefile = -1;
6185 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6186 }
6187 } else {
6188 // TODO: Try to set type above again
6189 '@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';
6190 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams); // @phan-suppress-line-PhanTypeMismatchArgument
6191 }
6192 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6193
6194
6195 if ($resultwritefile > 0) {
6196 $outputlangs->charset_output = $sav_charset_output;
6197
6198 // We delete old preview
6199 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6200 dol_delete_preview($this);
6201
6202 // Index file in database
6203 if (!empty($obj->result['fullpath'])) {
6204 $destfull = $obj->result['fullpath'];
6205
6206 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6207 $update_main_doc_field = 0;
6208 if (!empty($obj->update_main_doc_field)) {
6209 $update_main_doc_field = 1;
6210 }
6211
6212 // Check that the file exists, before indexing it.
6213 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6214 if (dol_is_file($destfull)) {
6215 $this->indexFile($destfull, $update_main_doc_field);
6216 }
6217 } else {
6218 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);
6219 }
6220
6221 // Success in building document. We build meta file.
6222 dol_meta_create($this);
6223
6224 $this->warnings = $obj->warnings;
6225
6226 return 1;
6227 } else {
6228 $outputlangs->charset_output = $sav_charset_output;
6229 $this->error = $obj->error;
6230 $this->errors = $obj->errors;
6231 $this->warnings = $obj->warnings;
6232 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
6233 return -1;
6234 }
6235 }
6236
6246 public function indexFile($destfull, $update_main_doc_field)
6247 {
6248 global $conf, $user;
6249
6250 $upload_dir = dirname($destfull);
6251 $destfile = basename($destfull);
6252 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6253
6254 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6255 $filename = basename($destfile);
6256 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6257 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6258
6259 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6260 $ecmfile = new EcmFiles($this->db);
6261 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6262
6263 // Set the public "share" key
6264 $setsharekey = false;
6265 if (
6266 !empty($this->TRIGGER_PREFIX)
6267 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6268 ) {
6269 $setsharekey = true;
6270 }
6271 // TODO Remove case covered by trigger prefix
6272 if ($this->element == 'propal' || $this->element == 'proposal') {
6273 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6274 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6275 }
6276 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6277 $setsharekey = true;
6278 }
6279 }
6280 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6281 $setsharekey = true;
6282 }
6283 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6284 $setsharekey = true;
6285 }
6286 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6287 $setsharekey = true;
6288 }
6289 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6290 $setsharekey = true;
6291 }
6292 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6293 $setsharekey = true;
6294 }
6295
6296 if ($setsharekey) {
6297 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6298 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6299 $ecmfile->share = getRandomPassword(true);
6300 }
6301 }
6302
6303 if ($result > 0) {
6304 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6305 $ecmfile->fullpath_orig = '';
6306 $ecmfile->gen_or_uploaded = 'generated';
6307 $ecmfile->description = ''; // indexed content
6308 $ecmfile->keywords = ''; // keyword content
6309 $ecmfile->src_object_type = $this->table_element;
6310 $ecmfile->src_object_id = $this->id;
6311 $result = $ecmfile->update($user);
6312 if ($result < 0) {
6313 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6314 return -1;
6315 }
6316 } else {
6317 $ecmfile->entity = $conf->entity;
6318 $ecmfile->filepath = $rel_dir;
6319 $ecmfile->filename = $filename;
6320 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6321 $ecmfile->fullpath_orig = '';
6322 $ecmfile->gen_or_uploaded = 'generated';
6323 $ecmfile->description = ''; // indexed content
6324 $ecmfile->keywords = ''; // keyword content
6325 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6326 $ecmfile->src_object_id = $this->id;
6327
6328 $result = $ecmfile->create($user);
6329 if ($result < 0) {
6330 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6331 return -1;
6332 }
6333 }
6334
6335 /*$this->result['fullname']=$destfull;
6336 $this->result['filepath']=$ecmfile->filepath;
6337 $this->result['filename']=$ecmfile->filename;*/
6338 //var_dump($obj->update_main_doc_field);exit;
6339
6340 if ($update_main_doc_field && !empty($this->table_element)) {
6341 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6342 $sql .= " WHERE rowid = ".((int) $this->id);
6343
6344 $resql = $this->db->query($sql);
6345 if (!$resql) {
6346 dol_print_error($this->db);
6347 return -1;
6348 } else {
6349 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6350 }
6351 }
6352 }
6353
6354 return 1;
6355 }
6356
6365 public function addThumbs($file, $quality = 50)
6366 {
6367 $file_osencoded = dol_osencode($file);
6368
6369 if (file_exists($file_osencoded)) {
6370 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6371
6372 $tmparraysize = getDefaultImageSizes();
6373 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6374 $maxheightsmall = $tmparraysize['maxheightsmall'];
6375 $maxwidthmini = $tmparraysize['maxwidthmini'];
6376 $maxheightmini = $tmparraysize['maxheightmini'];
6377 //$quality = $tmparraysize['quality'];
6378
6379 // Create small thumbs for company (Ratio is near 16/9)
6380 // Used on logon for example
6381 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6382
6383 // Create mini thumbs for company (Ratio is near 16/9)
6384 // Used on menu or for setup page for example
6385 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6386 }
6387 }
6388
6396 public function delThumbs($file)
6397 {
6398 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6399 dol_delete_file($imgThumbName);
6400 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6401 dol_delete_file($imgThumbName);
6402 }
6403
6404
6405 /* Functions common to commonobject and commonobjectline */
6406
6407 /* For default values */
6408
6422 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6423 {
6424 /* TODO Remove this. Must use now something like:
6425 $note_private = GETPOST('note_private', 'restricthtml');
6426 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6427 $note_private = $objectsrc->note_private;
6428 }
6429 */
6430
6431 // If param here has been posted, we use this value first.
6432 if (GETPOSTISSET($fieldname)) {
6433 return GETPOST($fieldname, $type, 3);
6434 }
6435
6436 if (isset($alternatevalue)) {
6437 return $alternatevalue;
6438 }
6439
6440 $newelement = $this->element;
6441 if ($newelement == 'facture') {
6442 $newelement = 'invoice';
6443 }
6444 if ($newelement == 'commande') {
6445 $newelement = 'order';
6446 }
6447 if (empty($newelement)) {
6448 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6449 return '';
6450 }
6451
6452 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6453 //var_dump($keyforfieldname);
6454 if (getDolGlobalString($keyforfieldname)) {
6455 return getDolGlobalString($keyforfieldname);
6456 }
6457
6458 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6459 // store content into $conf->cache['overwrite_default']
6460
6461 return '';
6462 }
6463
6464
6465 /* Functions for data in other language */
6466
6467
6477 {
6478 // To avoid SQL errors. Probably not the better solution though
6479 if (!$this->element) {
6480 return 0;
6481 }
6482 if (!($this->id > 0)) {
6483 return 0;
6484 }
6485 if (is_array($this->array_languages)) {
6486 return 1;
6487 }
6488
6489 $this->array_languages = array();
6490
6491 $element = $this->element;
6492 if ($element == 'categorie') {
6493 $element = 'categories'; // For compatibility
6494 }
6495
6496 // Request to get translation values for object
6497 $sql = "SELECT rowid, property, lang , value";
6498 $sql .= " FROM ".$this->db->prefix()."object_lang";
6499 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6500 $sql .= " AND fk_object = ".((int) $this->id);
6501
6502 $resql = $this->db->query($sql);
6503 if ($resql) {
6504 $numrows = $this->db->num_rows($resql);
6505 if ($numrows) {
6506 $i = 0;
6507 while ($i < $numrows) {
6508 $obj = $this->db->fetch_object($resql);
6509 $key = $obj->property;
6510 $value = $obj->value;
6511 $codelang = $obj->lang;
6512 $type = $this->fields[$key]['type'];
6513
6514 // we can add this attribute to object
6515 if (preg_match('/date/', $type)) {
6516 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6517 } else {
6518 $this->array_languages[$key][$codelang] = $value;
6519 }
6520
6521 $i++;
6522 }
6523 }
6524
6525 $this->db->free($resql);
6526
6527 if ($numrows) {
6528 return $numrows;
6529 } else {
6530 return 0;
6531 }
6532 } else {
6533 dol_print_error($this->db);
6534 return -1;
6535 }
6536 }
6537
6545 public function setValuesForExtraLanguages($onlykey = '')
6546 {
6547 // Get extra fields
6548 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6549 $tmparray = explode('-', $postfieldkey);
6550 if ($tmparray[0] != 'field') {
6551 continue;
6552 }
6553
6554 $element = $tmparray[1];
6555 $key = $tmparray[2];
6556 $codelang = $tmparray[3];
6557 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6558
6559 if (!empty($onlykey) && $key != $onlykey) {
6560 continue;
6561 }
6562 if ($element != $this->element) {
6563 continue;
6564 }
6565
6566 $key_type = $this->fields[$key]['type'];
6567
6568 $enabled = 1;
6569 if (isset($this->fields[$key]['enabled'])) {
6570 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6571 }
6572 /*$perms = 1;
6573 if (isset($this->fields[$key]['perms']))
6574 {
6575 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6576 }*/
6577 if (empty($enabled)) {
6578 continue;
6579 }
6580 //if (empty($perms)) continue;
6581
6582 if (in_array($key_type, array('date'))) {
6583 // Clean parameters
6584 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6585 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6586 } elseif (in_array($key_type, array('datetime'))) {
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(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6590 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6591 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6592 if (!empty($value_arr)) {
6593 $value_key = implode(',', $value_arr);
6594 } else {
6595 $value_key = '';
6596 }
6597 } elseif (in_array($key_type, array('price', 'double'))) {
6598 $value_arr = GETPOST($postfieldkey, 'alpha');
6599 $value_key = price2num($value_arr);
6600 } else {
6601 $value_key = GETPOST($postfieldkey);
6602 if (in_array($key_type, array('link')) && $value_key == '-1') {
6603 $value_key = '';
6604 }
6605 }
6606
6607 $this->array_languages[$key][$codelang] = $value_key;
6608
6609 /*if ($nofillrequired) {
6610 $langs->load('errors');
6611 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6612 return -1;
6613 }*/
6614 }
6615
6616 return 1;
6617 }
6618
6619
6620 /* Functions for extrafields */
6621
6628 public function fetchNoCompute($id)
6629 {
6630 global $conf;
6631
6632 $savDisableCompute = $conf->disable_compute;
6633 $conf->disable_compute = 1;
6634
6635 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6636
6637 $conf->disable_compute = $savDisableCompute;
6638
6639 return $ret;
6640 }
6641
6642 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6652 public function fetch_optionals($rowid = null, $optionsArray = null)
6653 {
6654 // phpcs:enable
6655 global $conf, $extrafields;
6656
6657 if (empty($rowid)) {
6658 $rowid = $this->id;
6659 }
6660 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6661 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6662 }
6663
6664 // To avoid SQL errors. Probably not the better solution though
6665 if (!$this->table_element) {
6666 return 0;
6667 }
6668
6669 $this->array_options = array();
6670
6671 if (!is_array($optionsArray)) {
6672 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6673 if (!isset($extrafields) || !is_object($extrafields)) {
6674 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6675 $extrafields = new ExtraFields($this->db);
6676 }
6677
6678 // Load array of extrafields for elementype = $this->table_element
6679 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6680 $extrafields->fetch_name_optionals_label($this->table_element);
6681 }
6682 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6683 } else {
6684 global $extrafields;
6685 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6686 }
6687
6688 $table_element = $this->table_element;
6689 if ($table_element == 'categorie') {
6690 $table_element = 'categories'; // For compatibility
6691 }
6692
6693 // Request to get complementary values
6694 if (is_array($optionsArray) && count($optionsArray) > 0) {
6695 $sql = "SELECT rowid";
6696 foreach ($optionsArray as $name => $label) {
6697 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6698 $sql .= ", ".$this->db->sanitize($name);
6699 }
6700 // use geo sql fonction to read as text
6701 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6702 // TODO Add an abstraction method in the database driver
6703 $sql .= ", ST_AsWKT(".$name.") as ".$this->db->sanitize($name);
6704 }
6705 }
6706 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6707 $sql .= " WHERE fk_object = ".((int) $rowid);
6708
6709 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6710 $resql = $this->db->query($sql);
6711 if ($resql) {
6712 $numrows = $this->db->num_rows($resql);
6713 if ($numrows) {
6714 $tab = $this->db->fetch_array($resql);
6715
6716 foreach ($tab as $key => $value) {
6717 // 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)
6718 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6719 // we can add this attribute to object
6720 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6721 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6722 $this->array_options["options_".$key] = $this->db->jdate($value);
6723 } else {
6724 $this->array_options["options_".$key] = $value;
6725 }
6726
6727 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6728 }
6729 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6730 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6731 $this->array_options["options_".$key] = dolDecrypt($value);
6732 }
6733 }
6734 }
6735 } else {
6740 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6741 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6742 $this->array_options['options_' . $key] = null;
6743 }
6744 }
6745 }
6746
6747 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6748 // in the element's extrafields table)
6749 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6750 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6751 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6752 //var_dump($conf->disable_compute);
6753 if (empty($conf->disable_compute)) {
6754 global $objectoffield; // We set a global variable to $objectoffield so
6755 $objectoffield = $this; // we can use it inside computed formula
6756 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6757 }
6758 }
6759 }
6760 }
6761
6762 $this->db->free($resql);
6763
6764 if ($numrows) {
6765 return $numrows;
6766 } else {
6767 return 0;
6768 }
6769 } else {
6770 $this->errors[] = $this->db->lasterror;
6771 return -1;
6772 }
6773 }
6774 return 0;
6775 }
6776
6783 public function deleteExtraFields()
6784 {
6785 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6786 return 0;
6787 }
6788
6789 $this->db->begin();
6790
6791 $table_element = $this->table_element;
6792 if ($table_element == 'categorie') {
6793 $table_element = 'categories'; // For compatibility
6794 }
6795
6796 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6797
6798 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6799
6800 $resql = $this->db->query($sql_del);
6801 if (!$resql) {
6802 $this->error = $this->db->lasterror();
6803 $this->db->rollback();
6804 return -1;
6805 } else {
6806 $this->db->commit();
6807 return 1;
6808 }
6809 }
6810
6821 public function insertExtraFields($trigger = '', $userused = null)
6822 {
6823 global $langs, $user;
6824
6825 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6826 return 0;
6827 }
6828
6829 if (empty($userused)) {
6830 $userused = $user;
6831 }
6832
6833 $error = 0;
6834
6835 // Check parameters
6836 $langs->load('admin');
6837 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6838 $extrafields = new ExtraFields($this->db);
6839 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6840
6841 // Eliminate copied source object extra fields that do not exist in target object
6842 $new_array_options = array();
6843 foreach ($this->array_options as $key => $value) {
6844 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6845 $new_array_options[$key] = $value;
6846 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6847 $new_array_options['options_'.$key] = $value;
6848 }
6849 }
6850
6851 foreach ($new_array_options as $key => $value) {
6852 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6853 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6854 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6855 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6856 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6857 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6858 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6859 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
6860
6861 // If we clone, we have to clean unique extrafields to prevent duplicates.
6862 // If we clone, we have to clean extrafields having "empty on clone" option on.
6863 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6864 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
6865 $new_array_options[$key] = null;
6866 continue;
6867 }
6868
6869 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6870 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6871 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6872 $new_array_options[$key] = null;
6873 continue;
6874 }
6875
6876 // Similar code than into insertExtraFields
6877 if ($attributeRequired) {
6878 $v = $this->array_options[$key];
6879 if (ExtraFields::isEmptyValue($v, $attributeType)) {
6880 $langs->load("errors");
6881 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6882 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6883 return -1;
6884 }
6885 }
6886
6887 if (!empty($attrfieldcomputed)) {
6888 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6889 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
6890 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
6891 $new_array_options[$key] = $value;
6892 } else {
6893 $new_array_options[$key] = null;
6894 }
6895 continue;
6896 }
6897
6898 switch ($attributeType) {
6899 case 'int':
6900 case 'duration':
6901 case 'stars':
6902 if (!is_numeric($value) && $value != '') {
6903 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6904 return -1;
6905 } elseif ($value === '') {
6906 $new_array_options[$key] = null;
6907 }
6908 break;
6909 case 'boolean':
6910 if ($value === '' || $value === false || $value === null) {
6911 $new_array_options[$key] = null;
6912 }
6913 break;
6914 case 'price':
6915 case 'double':
6916 $value = price2num($value);
6917 if (!is_numeric($value) && $value != '') {
6918 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6919 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6920 return -1;
6921 } elseif ($value == '') {
6922 $value = null;
6923 }
6924 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6925 $new_array_options[$key] = $value;
6926 break;
6927 /*case 'select': // Not required, we chose value='0' for undefined values
6928 if ($value=='-1')
6929 {
6930 $this->array_options[$key] = null;
6931 }
6932 break;*/
6933 case 'password':
6934 $algo = '';
6935 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6936 // If there is an encryption choice, we use it to encrypt data before insert
6937 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6938 $algo = reset($tmparrays);
6939 if ($algo != '') {
6940 //global $action; // $action may be 'create', 'update', 'update_extras'...
6941 //var_dump($action);
6942 //var_dump($this->oldcopy);exit;
6943 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
6944 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6945 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6946 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6947 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6948 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6949 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6950 } else {
6951 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6952 }
6953 } else {
6954 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6955 }
6956 } else {
6957 // If value has changed
6958 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6959 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6960 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6961 } else {
6962 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6963 }
6964 } else {
6965 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
6966 }
6967 }
6968 } else {
6969 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6970 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6971 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
6972 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6973 } else {
6974 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6975 }
6976 }
6977 } else {
6978 // No encryption
6979 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6980 }
6981 } else { // Common usage
6982 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6983 }
6984 break;
6985 case 'date':
6986 case 'datetime':
6987 // If data is a string instead of a timestamp, we convert it
6988 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6989 $this->array_options[$key] = strtotime($this->array_options[$key]);
6990 }
6991 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6992 break;
6993 case 'datetimegmt':
6994 // If data is a string instead of a timestamp, we convert it
6995 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6996 $this->array_options[$key] = strtotime($this->array_options[$key]);
6997 }
6998 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
6999 break;
7000 case 'link':
7001 $param_list = array_keys($attributeParam['options']);
7002 // 0 : ObjectName
7003 // 1 : classPath
7004 $InfoFieldList = explode(":", $param_list[0]);
7005 dol_include_once($InfoFieldList[1]);
7006 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7007 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7008 $new_array_options[$key] = '';
7009 } elseif ($value) {
7010 $object = new $InfoFieldList[0]($this->db);
7011 '@phan-var-force CommonObject $object';
7012
7013 $objectId = 0;
7014
7015 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7016 if (is_numeric($value)) {
7017 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7018 } else {
7019 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7020 }
7021
7022 $obj = $this->db->getRow($sqlFetchObject);
7023
7024 if ($obj !== false) {
7025 $objectId = $obj->rowid;
7026 $res = 1;
7027 } else {
7028 $res = -1;
7029 }
7030
7031 if ($res > 0) {
7032 $new_array_options[$key] = $objectId;
7033 } else {
7034 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7035 return -1;
7036 }
7037 }
7038 } else {
7039 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7040 }
7041 break;
7042 case 'checkbox':
7043 case 'chkbxlst':
7044 if (is_array($this->array_options[$key])) {
7045 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7046 } else {
7047 $new_array_options[$key] = $this->array_options[$key];
7048 }
7049 break;
7050 }
7051 }
7052
7053 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7054 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7055
7056 foreach ($new_array_options as $key => $newValue) {
7057 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7058 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7059 if ($attributeType === 'separate') {
7060 // separator extrafields don't have data per object so they don't have a comlumn in the database
7061 continue;
7062 }
7063 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7064 // Add field of attribute
7065 if (! $geoDataType) {
7066 // not a geodata type
7067 if ($newValue != '') {
7068 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7069 } else {
7070 $sqlColumnValues[$attributeKey] = 'null';
7071 }
7072 continue;
7073 }
7074 if (empty($newValue)) {
7075 $sqlColumnValues[$attributeKey] = 'null';
7076 continue;
7077 }
7078 if (preg_match('/error/i', $newValue)) {
7079 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7080 $sqlColumnValues[$attributeKey] = 'null';
7081 continue;
7082 }
7083
7084 // Geodata type: Text must be a WKT string. Examples:
7085 // - point => "POINT(15 20)"
7086 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7087 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7088 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7089 $sqlColumnValues[$key] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')";
7090 }
7091
7092 $this->db->begin();
7093
7094 $table_element = $this->table_element;
7095 if ($table_element == 'categorie') {
7096 $table_element = 'categories'; // For compatibility
7097 }
7098 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7099
7100 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7101
7102 $linealreadyfound = 0;
7103 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7104 $resql = $this->db->query($sql);
7105 if ($resql) {
7106 $tmpobj = $this->db->fetch_object($resql);
7107 if ($tmpobj) {
7108 $linealreadyfound = $tmpobj->nb;
7109 }
7110 }
7111
7112 // if the extrafields row already exists for the object, we update it
7113 if ($linealreadyfound) {
7114 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7115 $sqlColumnValueString = implode(
7116 ',',
7121 array_map(function ($key) use ($sqlColumnValues) {
7122 return "{$key} = {$sqlColumnValues[$key]}";
7123 }, array_keys($sqlColumnValues))
7124 );
7125 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7126 } else {
7127 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7128 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7129 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7130 if (isset($sqlColumnValues[$key])) {
7131 // extrafield value already provided: no need to add it
7132 continue;
7133 }
7134 // default value: empty string, except for 'int', 'double' and 'price'
7135 $sqlColumnValues[$key] = "''";
7136 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7137 $sqlColumnValues[$key] = 0;
7138 }
7139 }
7140 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7141 $sqlValues = implode(',', array_values($sqlColumnValues));
7142 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7143 }
7144
7145 $resql = $this->db->query($sql);
7146 if (!$resql) {
7147 $this->error = $this->db->lasterror();
7148 $error++;
7149 }
7150
7151 // Update also the user of last modification in parent table
7152 if (!$error && !empty($this->fields['fk_user_modif'])) {
7153 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7154 $sql .= " SET fk_user_modif = ".(int) $user->id;
7155 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7156 $this->db->query($sql);
7157 }
7158
7159 if (!$error && $trigger) {
7160 // Call trigger
7161 $this->context = array('extrafieldaddupdate' => 1);
7162 $result = $this->call_trigger($trigger, $userused);
7163 if ($result < 0) {
7164 $error++;
7165 }
7166 // End call trigger
7167 }
7168
7169 if ($error) {
7170 $this->db->rollback();
7171 return -1;
7172 } else {
7173 $this->db->commit();
7174 return 1;
7175 }
7176 }
7177
7189 public function insertExtraLanguages($trigger = '', $userused = null)
7190 {
7191 global $langs, $user;
7192
7193 if (empty($userused)) {
7194 $userused = $user;
7195 }
7196
7197 $error = 0;
7198
7199 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7200 return 0; // For avoid conflicts if trigger used
7201 }
7202
7203 if (is_array($this->array_languages)) {
7204 $new_array_languages = $this->array_languages;
7205
7206 foreach ($new_array_languages as $key => $value) {
7207 $attributeKey = $key;
7208 $attributeType = $this->fields[$attributeKey]['type'];
7209 $attributeLabel = $this->fields[$attributeKey]['label'];
7210
7211 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7212 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7213
7214 switch ($attributeType) {
7215 case 'int':
7216 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7217 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7218 return -1;
7219 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7220 $new_array_languages[$key] = null;
7221 }
7222 break;
7223 case 'double':
7224 $value = price2num((string) $value);
7225 if (!is_numeric($value) && $value != '') {
7226 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7227 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7228 return -1;
7229 } elseif ($value == '') {
7230 $new_array_languages[$key] = null;
7231 } else {
7232 $new_array_languages[$key] = $value;
7233 }
7234 break;
7235 /*case 'select': // Not required, we chose value='0' for undefined values
7236 if ($value=='-1')
7237 {
7238 $this->array_options[$key] = null;
7239 }
7240 break;*/
7241 }
7242 }
7243
7244 $this->db->begin();
7245
7246 $table_element = $this->table_element;
7247 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7248 $table_element = 'categories'; // For compatibility
7249 }
7250
7251 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7252
7253 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7254 foreach ($langcodearray as $langcode => $value) {
7255 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7256 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7257 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7258 $this->db->query($sql_del);
7259
7260 if ($value !== '') {
7261 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7262 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7263 $sql .= ")";
7264
7265 $resql = $this->db->query($sql);
7266 if (!$resql) {
7267 $this->error = $this->db->lasterror();
7268 $error++;
7269 break;
7270 }
7271 }
7272 }
7273 }
7274
7275 if (!$error && $trigger) {
7276 // Call trigger
7277 $this->context = array('extralanguagesaddupdate' => 1);
7278 $result = $this->call_trigger($trigger, $userused);
7279 if ($result < 0) {
7280 $error++;
7281 }
7282 // End call trigger
7283 }
7284
7285 if ($error) {
7286 $this->db->rollback();
7287 return -1;
7288 } else {
7289 $this->db->commit();
7290 return 1;
7291 }
7292 } else {
7293 return 0;
7294 }
7295 }
7296
7307 public function updateExtraField($key, $trigger = null, $userused = null)
7308 {
7309 global $langs, $user, $hookmanager;
7310
7311 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7312 return 0;
7313 }
7314
7315 if (empty($userused)) {
7316 $userused = $user;
7317 }
7318
7319 $error = 0;
7320
7321 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7322 // Check parameters
7323 $langs->load('admin');
7324 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7325 $extrafields = new ExtraFields($this->db);
7326 $extrafields->fetch_name_optionals_label($this->table_element);
7327
7328 $value = $this->array_options["options_".$key];
7329
7330 $attributeKey = $key;
7331 $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
7332 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
7333 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
7334 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
7335 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7336 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
7337
7338 // Similar code than into insertExtraFields
7339 if ($attributeRequired) {
7340 $mandatorypb = false;
7341 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7342 $mandatorypb = true;
7343 }
7344 if ($this->array_options["options_".$key] === '') {
7345 $mandatorypb = true;
7346 }
7347 if ($mandatorypb) {
7348 $langs->load("errors");
7349 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7350 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7351 return -1;
7352 }
7353 }
7354
7355 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7356 $new_array_options = $this->array_options;
7357
7358 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7359 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7360 if (!empty($attrfieldcomputed)) {
7361 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7362 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7363 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7364
7365 $new_array_options["options_".$key] = $value;
7366
7367 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7368 } else {
7369 $new_array_options["options_".$key] = null;
7370
7371 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7372 }
7373 }
7374
7375 switch ($attributeType) {
7376 case 'int':
7377 if (!is_numeric($value) && $value != '') {
7378 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7379 return -1;
7380 } elseif ($value === '') {
7381 $new_array_options["options_".$key] = null;
7382
7383 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7384 }
7385 break;
7386 case 'price':
7387 case 'double':
7388 $value = price2num($value);
7389 if (!is_numeric($value) && $value != '') {
7390 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7391 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7392 return -1;
7393 } elseif ($value === '') {
7394 $value = null;
7395 }
7396 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7397 $new_array_options["options_".$key] = $value;
7398
7399 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7400 break;
7401 /*case 'select': // Not required, we chose value='0' for undefined values
7402 if ($value=='-1')
7403 {
7404 $new_array_options["options_".$key] = $value;
7405
7406 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7407 }
7408 break;*/
7409 case 'password':
7410 $algo = '';
7411 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7412 // If there is an encryption choice, we use it to encrypt data before insert
7413 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7414 $algo = reset($tmparrays);
7415 if ($algo != '') {
7416 //global $action; // $action may be 'create', 'update', 'update_extras'...
7417 //var_dump($action);
7418 //var_dump($this->oldcopy);exit;
7419 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7420 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
7421 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7422 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.
7423 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7424 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7425 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7426 } else {
7427 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7428 }
7429 } else {
7430 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7431 }
7432 } else {
7433 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7434 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7435 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7436 } else {
7437 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7438 }
7439 } else {
7440 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7441 }
7442 }
7443 } else {
7444 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7445 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7446 } else {
7447 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7448 }
7449 }
7450 } else {
7451 // No encryption
7452 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7453 }
7454 } else { // Common usage
7455 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7456 }
7457
7458 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7459 break;
7460 case 'date':
7461 case 'datetime':
7462 if (empty($this->array_options["options_".$key])) {
7463 $new_array_options["options_".$key] = null;
7464
7465 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7466 } else {
7467 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7468 }
7469 break;
7470 case 'datetimegmt':
7471 if (empty($this->array_options["options_".$key])) {
7472 $new_array_options["options_".$key] = null;
7473
7474 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7475 } else {
7476 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7477 }
7478 break;
7479 case 'boolean':
7480 if (empty($this->array_options["options_".$key])) {
7481 $new_array_options["options_".$key] = null;
7482
7483 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7484 }
7485 break;
7486 case 'link':
7487 if ($this->array_options["options_".$key] === '') {
7488 $new_array_options["options_".$key] = null;
7489
7490 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7491 }
7492 break;
7493 /*
7494 case 'link':
7495 $param_list = array_keys($attributeParam['options']);
7496 // 0 : ObjectName
7497 // 1 : classPath
7498 $InfoFieldList = explode(":", $param_list[0]);
7499 dol_include_once($InfoFieldList[1]);
7500 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7501 {
7502 if ($value == '-1') // -1 is key for no defined in combo list of objects
7503 {
7504 $new_array_options[$key] = '';
7505 } elseif ($value) {
7506 $object = new $InfoFieldList[0]($this->db);
7507 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7508 else $res = $object->fetch(0, $value); // For compatibility
7509
7510 if ($res > 0) $new_array_options[$key] = $object->id;
7511 else {
7512 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7513 $this->db->rollback();
7514 return -1;
7515 }
7516 }
7517 } else {
7518 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7519 }
7520 break;
7521 */
7522 case 'checkbox':
7523 case 'chkbxlst':
7524 $new_array_options = array();
7525 if (is_array($this->array_options["options_".$key])) {
7526 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7527 } else {
7528 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7529 }
7530
7531 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7532 break;
7533 }
7534
7535 $this->db->begin();
7536
7537 $linealreadyfound = 0;
7538
7539 // 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)
7540 $table_element = $this->table_element;
7541 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7542 $table_element = 'categories'; // For compatibility
7543 }
7544
7545 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7546 $resql = $this->db->query($sql);
7547 if ($resql) {
7548 $tmpobj = $this->db->fetch_object($resql);
7549 if ($tmpobj) {
7550 $linealreadyfound = $tmpobj->nb;
7551 }
7552 }
7553
7554 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7555 if ($linealreadyfound) {
7556 if ($this->array_options["options_".$key] === null) {
7557 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
7558 } else {
7559 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7560 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7561 }
7562 $sql .= " WHERE fk_object = ".((int) $this->id);
7563
7564 $resql = $this->db->query($sql);
7565 if (!$resql) {
7566 $error++;
7567 $this->error = $this->db->lasterror();
7568 }
7569 } else {
7570 $result = $this->insertExtraFields('', $user);
7571 if ($result < 0) {
7572 $error++;
7573 }
7574 }
7575
7576 // Update also the user of last modification in parent table
7577 if (!$error && !empty($this->fields['fk_user_modif'])) {
7578 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7579 $sql .= " SET fk_user_modif = ".(int) $user->id;
7580 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7581 $this->db->query($sql);
7582 }
7583
7584 if (!$error) {
7585 $parameters = array('key' => $key);
7586 global $action;
7587 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7588 if ($reshook < 0) {
7589 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7590 }
7591 }
7592
7593 if (!$error && $trigger) {
7594 // Call trigger
7595 $this->context = array('extrafieldupdate' => 1);
7596 $result = $this->call_trigger($trigger, $userused);
7597 if ($result < 0) {
7598 $error++;
7599 }
7600 // End call trigger
7601 }
7602
7603 if ($error) {
7604 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7605 $this->db->rollback();
7606 return -1;
7607 } else {
7608 $this->db->commit();
7609 return 1;
7610 }
7611 } else {
7612 return 0;
7613 }
7614 }
7615
7622 public function getExtraField($key)
7623 {
7624 return $this->array_options['options_'.$key] ?? null;
7625 }
7626
7634 public function setExtraField($key, $value)
7635 {
7636 $this->array_options['options_'.$key] = $value;
7637 }
7638
7649 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7650 {
7651 global $user;
7652
7653 if (empty($userused)) {
7654 $userused = $user;
7655 }
7656
7657 //$error = 0;
7658
7659 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7660 return 0; // For avoid conflicts if trigger used
7661 }
7662
7663 return 0;
7664 }
7665
7666
7681 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7682 {
7683 global $conf, $langs, $form;
7684
7685
7686 // 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...)
7687
7688 if (!is_object($form)) {
7689 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7690 $form = new Form($this->db);
7691 }
7692
7693 if (!empty($this->fields)) {
7694 $val = $this->fields[$key];
7695 }
7696
7697 // Validation tests and output
7698 $fieldValidationErrorMsg = '';
7699 $validationClass = '';
7700 $fieldValidationErrorMsg = $this->getFieldError($key);
7701 if (!empty($fieldValidationErrorMsg)) {
7702 $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
7703 } else {
7704 $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
7705 }
7706
7707 //$valuemultiselectinput = array();
7708 $out = '';
7709 $type = '';
7710 $isDependList = 0;
7711 $param = array();
7712 $param['options'] = array();
7713 $reg = array();
7714 // @phan-suppress-next-line PhanTypeMismatchProperty
7715 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7716 // Because we work on extrafields
7717 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7718 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7719 $type = 'link';
7720 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7721 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7722 $type = 'link';
7723 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7724 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7725 $type = 'link';
7726 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7727 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7728 $type = 'sellist';
7729 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7730 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7731 $type = 'sellist';
7732 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7733 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7734 $type = 'sellist';
7735 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7736 $param['options'] = array($reg[1] => 'N');
7737 $type = 'chkbxlst';
7738 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7739 $param['options'] = array();
7740 $type = 'varchar';
7741 $size = $reg[1];
7742 } elseif (preg_match('/varchar/', $val['type'])) {
7743 $param['options'] = array();
7744 $type = 'varchar';
7745 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7746 $param['options'] = array();
7747 $type = 'stars';
7748 $size = $reg[1];
7749 } else {
7750 $param['options'] = array();
7751 $type = $this->fields[$key]['type'];
7752 }
7753 //var_dump($type); var_dump($param['options']);
7754
7755 // Special case that force options and type ($type can be integer, varchar, ...)
7756 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7757 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7758 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7759 if (empty($this->fields[$key]['multiinput'])) {
7760 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7761 }
7762 }
7763
7764 $label = $this->fields[$key]['label'];
7765 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7766 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7767 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7768 // @phan-suppress-next-line PhanTypeMismatchProperty
7769 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7770 // @phan-suppress-next-line PhanTypeMismatchProperty
7771 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7772 // @phan-suppress-next-line PhanTypeMismatchProperty
7773 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7774 // @phan-suppress-next-line PhanTypeMismatchProperty
7775 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7776 // @phan-suppress-next-line PhanTypeMismatchProperty
7777 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7778
7779 // @phan-suppress-next-line PhanTypeMismatchProperty
7780 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7781 // @phan-suppress-next-line PhanTypeMismatchProperty
7782 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7783 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7784
7785 $objectid = $this->id;
7786
7787 if ($computed) {
7788 if (!preg_match('/^search_/', $keyprefix)) {
7789 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7790 } else {
7791 return '';
7792 }
7793 }
7794
7795 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7796 if (empty($morecss) && !empty($val['css'])) {
7797 $morecss = $val['css'];
7798 } elseif (empty($morecss)) {
7799 if ($type == 'date') {
7800 $morecss = 'minwidth100imp';
7801 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7802 $morecss = 'minwidth200imp';
7803 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7804 $morecss = 'maxwidth75';
7805 } elseif ($type == 'url') {
7806 $morecss = 'minwidth400';
7807 } elseif ($type == 'boolean') {
7808 $morecss = '';
7809 } else {
7810 if (is_numeric($size) && round((float) $size) < 12) {
7811 $morecss = 'minwidth100';
7812 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7813 $morecss = 'minwidth200';
7814 } else {
7815 $morecss = 'minwidth400';
7816 }
7817 }
7818 }
7819
7820 // Add validation state class
7821 if (!empty($validationClass)) {
7822 $morecss .= $validationClass;
7823 }
7824
7825 if (in_array($type, array('date'))) {
7826 $tmp = explode(',', $size);
7827 $newsize = $tmp[0];
7828 $showtime = 0;
7829
7830 // Do not show current date when field not required (see selectDate() method)
7831 if (!$required && $value == '') {
7832 $value = '-1';
7833 }
7834
7835 // TODO Must also support $moreparam
7836 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7837 } elseif (in_array($type, array('datetime'))) {
7838 $tmp = explode(',', $size);
7839 $newsize = $tmp[0];
7840 $showtime = 1;
7841
7842 // Do not show current date when field not required (see selectDate() method)
7843 if (!$required && $value == '') {
7844 $value = '-1';
7845 }
7846
7847 // TODO Must also support $moreparam
7848 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7849 } elseif (in_array($type, array('duration'))) {
7850 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7851 } elseif (in_array($type, array('int', 'integer'))) {
7852 $tmp = explode(',', $size);
7853 $newsize = $tmp[0];
7854 $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' : '').'>';
7855 } elseif (in_array($type, array('real'))) {
7856 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7857 } elseif (preg_match('/varchar/', (string) $type)) {
7858 $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' : '').'>';
7859 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
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('/^text/', (string) $type)) {
7862 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7863 if (!empty($param['options'])) {
7864 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7865 //var_dump($param['options']);
7866 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7867 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7868 $out .= "<script>";
7869 $out .= '
7870 function handlemultiinputdisabling(htmlname){
7871 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
7872 multiinput = $("#"+htmlname+"_multiinput");
7873 multiinput.find("option").each(function(){
7874 tmpval = $("#"+htmlname).val();
7875 tmpvalarray = tmpval.split("\n");
7876 valtotest = $(this).val();
7877 if(tmpvalarray.includes(valtotest)){
7878 $(this).prop("disabled",true);
7879 } else {
7880 if($(this).prop("disabled") == true){
7881 console.log(valtotest)
7882 $(this).prop("disabled", false);
7883 }
7884 }
7885 });
7886 }
7887
7888 $(document).ready(function () {
7889 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
7890 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
7891 tmpvalarray = tmpval.split(",");
7892 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
7893 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
7894 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
7895 if(tmpval == ""){
7896 tmpval = valtotest;
7897 } else {
7898 tmpval = tmpval + "\n" + valtotest;
7899 }
7900 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
7901 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7902 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
7903 } else {
7904 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
7905 }
7906 });
7907 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
7908 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7909 });
7910 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7911 })';
7912 $out .= "</script>";
7913 $value = str_replace(',', "\n", $value);
7914 }
7915 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7916 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
7917 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
7918 } else {
7919 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7920 }
7921 } elseif (preg_match('/^html/', (string) $type)) {
7922 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7923 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7924 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
7925 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
7926 } else {
7927 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7928 }
7929 } elseif ($type == 'boolean') {
7930 $checked = '';
7931 if (!empty($value)) {
7932 $checked = ' checked value="1" ';
7933 } else {
7934 $checked = ' value="1" ';
7935 }
7936 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
7937 } elseif ($type == 'price') {
7938 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7939 $value = price($value);
7940 }
7941 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
7942 } elseif ($type == 'stars') {
7943 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7944 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
7945 $i = 1;
7946 while ($i <= $size) {
7947 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
7948 $i++;
7949 }
7950 $out .= '</div>';
7951 $out .= '<script>
7952 jQuery(function($) { /* commonobject.class.php 1 */
7953 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
7954 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
7955 container.find(".star").each(function() {
7956 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7957 });
7958 container.find(".star").on("mouseover", function() {
7959 let selectedStar = $(this).data("value");
7960 container.find(".star").each(function() {
7961 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
7962 });
7963 });
7964 container.on("mouseout", function() {
7965 container.find(".star").each(function() {
7966 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7967 });
7968 });
7969 container.find(".star").off("click").on("click", function() {
7970 selectedStars = $(this).data("value");
7971 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
7972 selectedStars = 0;
7973 }
7974 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
7975 container.find(".star").each(function() {
7976 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7977 });
7978 });
7979 });
7980 </script>';
7981 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7982 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7983 $value = price($value);
7984 }
7985 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
7986 } elseif ($type == 'select') { // combo list
7987 $out = '';
7988 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7989 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7990 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7991 }
7992
7993 $tmpselect = '';
7994 $nbchoice = 0;
7995
7996 foreach ($param['options'] as $keyb => $valb) {
7997 if ((string) $keyb == '') {
7998 continue;
7999 }
8000 if (strpos($valb, "|") !== false) {
8001 list($valb, $parent) = explode('|', $valb);
8002 }
8003 $nbchoice++;
8004 $tmpselect .= '<option value="'.$keyb.'"';
8005 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8006 if (!empty($parent)) {
8007 $isDependList = 1;
8008 }
8009 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8010 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8011 }
8012
8013 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8014 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8015 $out .= '<option value="0">&nbsp;</option>';
8016 }
8017 $out .= $tmpselect;
8018 $out .= '</select>';
8019 } elseif ($type == 'sellist') {
8020 $out = '';
8021 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8022 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8023 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8024 }
8025
8026 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8027 if (is_array($param['options'])) {
8028 $tmpparamoptions = array_keys($param['options']);
8029 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8030
8031 $InfoFieldList = explode(":", $paramoptions[0], 5);
8032 // 0 : tableName
8033 // 1 : label field name
8034 // 2 : key fields name (if different of rowid)
8035 // optional parameters...
8036 // 3 : key field parent (for dependent lists). How this is used ?
8037 // 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".
8038 // 5 : string category type. This replace the filter.
8039 // 6 : ids categories list separated by comma for category root. This replace the filter.
8040 // 7 : sort field
8041
8042 // If there is filter
8043 if (! empty($InfoFieldList[4])) {
8044 $pos = 0;
8045 $parenthesisopen = 0;
8046 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8047 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8048 $parenthesisopen++;
8049 }
8050 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8051 $parenthesisopen--;
8052 }
8053 $pos++;
8054 }
8055 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8056 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8057 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8058 $InfoFieldList[4] = $tmpbefore;
8059 if ($tmpafter !== '') {
8060 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8061 }
8062 //var_dump($InfoFieldList);
8063
8064 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8065 $reg = array();
8066 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8067 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8068 }
8069
8070 //var_dump($InfoFieldList);
8071 }
8072
8073 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8074
8075 $parentName = '';
8076 $parentField = '';
8077
8078 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8079
8080 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8081 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8082 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8083 } else {
8084 $keyList = $InfoFieldList[2].' as rowid';
8085 }
8086 }
8087 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8088 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8089 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8090 $keyList .= ', main.'.$parentField;
8091 } else {
8092 $keyList .= ', '.$parentField;
8093 }
8094 }
8095
8096 $filter_categorie = false;
8097 if (count($InfoFieldList) > 5) {
8098 if ($InfoFieldList[0] == 'categorie') {
8099 $filter_categorie = true;
8100 }
8101 }
8102
8103 if (!$filter_categorie) {
8104 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8105 if (!empty($fields_label)) {
8106 $keyList .= ', ';
8107 $keyList .= implode(', ', $fields_label);
8108 }
8109
8110 $sqlwhere = '';
8111 $sql = "SELECT " . $keyList;
8112 $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
8113
8114 if (!empty($InfoFieldList[4])) {
8115 // can use SELECT request
8116 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8117 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8118 }
8119
8120 // current object id can be use into filter
8121 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8122 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8123 } else {
8124 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8125 }
8126
8127 // We have to join on extrafield table
8128 $errstr = '';
8129 if (strpos($InfoFieldList[4], 'extra') !== false) {
8130 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8131 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8132 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8133 } else {
8134 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8135 }
8136 } else {
8137 $sqlwhere .= ' WHERE 1=1';
8138 }
8139
8140 // Add Usf filter on second line
8141 /*
8142 if ($Usf) {
8143 $errorstr = '';
8144 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8145 if (!$errorstr) {
8146 $sqlwhere .= $sqlusf;
8147 } else {
8148 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8149 }
8150 }
8151 */
8152
8153 // Some tables may have field, some other not. For the moment we disable it.
8154 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8155 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8156 }
8157 $sql .= $sqlwhere;
8158
8159 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8160 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8161 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8162 } else {
8163 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8164 }
8165 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8166 // print $sql;
8167
8168 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8169 $resql = $this->db->query($sql);
8170 if ($resql) {
8171 $out .= '<option value="0">&nbsp;</option>';
8172 $num = $this->db->num_rows($resql);
8173 $i = 0;
8174 while ($i < $num) {
8175 $labeltoshow = '';
8176 $obj = $this->db->fetch_object($resql);
8177
8178 // Several field into label (eq table:code|libelle:rowid)
8179 $notrans = false;
8180 $fields_label = explode('|', $InfoFieldList[1]);
8181 if (count($fields_label) > 1) {
8182 $notrans = true;
8183 foreach ($fields_label as $field_toshow) {
8184 $labeltoshow .= $obj->$field_toshow . ' ';
8185 }
8186 } else {
8187 $labeltoshow = $obj->{$InfoFieldList[1]};
8188 }
8189 $labeltoshow = dol_trunc($labeltoshow, 45);
8190
8191 if ($value == $obj->rowid) {
8192 foreach ($fields_label as $field_toshow) {
8193 $translabel = $langs->trans($obj->$field_toshow);
8194 if ($translabel != $obj->$field_toshow) {
8195 $labeltoshow = dol_trunc($translabel) . ' ';
8196 } else {
8197 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8198 }
8199 }
8200 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8201 } else {
8202 if (!$notrans) {
8203 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8204 if ($translabel != $obj->{$InfoFieldList[1]}) {
8205 $labeltoshow = dol_trunc($translabel, 18);
8206 } else {
8207 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8208 }
8209 }
8210 if (empty($labeltoshow)) {
8211 $labeltoshow = '(not defined)';
8212 }
8213 if ($value == $obj->rowid) {
8214 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8215 }
8216
8217 if (!empty($InfoFieldList[3]) && $parentField) {
8218 $parent = $parentName . ':' . $obj->{$parentField};
8219 $isDependList = 1;
8220 }
8221
8222 $out .= '<option value="' . $obj->rowid . '"';
8223 $out .= ($value == $obj->rowid ? ' selected' : '');
8224 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8225 $out .= '>' . $labeltoshow . '</option>';
8226 }
8227
8228 $i++;
8229 }
8230 $this->db->free($resql);
8231 } else {
8232 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8233 }
8234 } else {
8235 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8236 $categcode = $InfoFieldList[5];
8237 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8238 $tmpcategory = new Categorie($this->db);
8239 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8240 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8241 }
8242
8243 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8244 $out .= '<option value="0">&nbsp;</option>';
8245 foreach ($data as $data_key => $data_value) {
8246 $out .= '<option value="' . $data_key . '"';
8247 $out .= ($value == $data_key ? ' selected' : '');
8248 $out .= '>' . $data_value . '</option>';
8249 }
8250 }
8251 }
8252 $out .= '</select>';
8253 } elseif ($type == 'checkbox') {
8254 $value_arr = explode(',', $value);
8255 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8256 } elseif ($type == 'radio') {
8257 $out = '';
8258 foreach ($param['options'] as $keyopt => $valopt) {
8259 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8260 $out .= ' value="'.$keyopt.'"';
8261 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8262 $out .= ($value == $keyopt ? 'checked' : '');
8263 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8264 }
8265 } elseif ($type == 'chkbxlst') {
8266 if (is_array($value)) {
8267 $value_arr = $value;
8268 } else {
8269 $value_arr = explode(',', $value);
8270 }
8271
8272 if (is_array($param['options'])) {
8273 $tmpparamoptions = array_keys($param['options']);
8274 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8275
8276 $InfoFieldList = explode(":", $paramoptions[0], 5);
8277 // 0 : tableName
8278 // 1 : label field name
8279 // 2 : key fields name (if different of rowid)
8280 // optional parameters...
8281 // 3 : key field parent (for dependent lists). How this is used ?
8282 // 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".
8283 // 5 : string category type. This replace the filter.
8284 // 6 : ids categories list separated by comma for category root. This replace the filter.
8285 // 7 : sort field
8286
8287 // If there is a filter
8288 if (! empty($InfoFieldList[4])) {
8289 $pos = 0;
8290 $parenthesisopen = 0;
8291 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8292 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8293 $parenthesisopen++;
8294 }
8295 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8296 $parenthesisopen--;
8297 }
8298 $pos++;
8299 }
8300 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8301 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8302 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8303 $InfoFieldList[4] = $tmpbefore;
8304 if ($tmpafter !== '') {
8305 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8306 }
8307
8308 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8309 $reg = array();
8310 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8311 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8312 }
8313
8314 //var_dump($InfoFieldList);
8315 }
8316
8317 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8318
8319 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8320
8321 $parentName = '';
8322 $parentField = '';
8323
8324 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8325
8326 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8327 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8328 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8329 } else {
8330 $keyList = $InfoFieldList[2].' as rowid';
8331 }
8332 }
8333 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8334 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8335 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8336 $keyList .= ', main.'.$parentField;
8337 } else {
8338 $keyList .= ', '.$parentField;
8339 }
8340 }
8341
8342 $filter_categorie = false;
8343 if (count($InfoFieldList) > 5) {
8344 if ($InfoFieldList[0] == 'categorie') {
8345 $filter_categorie = true;
8346 }
8347 }
8348
8349 // Common filter
8350 if (!$filter_categorie) {
8351 $fields_label = explode('|', $InfoFieldList[1]);
8352 if (is_array($fields_label)) {
8353 $keyList .= ', ';
8354 $keyList .= implode(', ', $fields_label);
8355 }
8356
8357 $sqlwhere = '';
8358 $sql = "SELECT " . $keyList;
8359 $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8360
8361 if (!empty($InfoFieldList[4])) {
8362 // can use SELECT request
8363 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8364 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8365 }
8366
8367 // current object id can be use into filter
8368 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8369 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8370 } else {
8371 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8372 }
8373
8374 // We have to join on extrafield table
8375 $errstr = '';
8376 if (strpos($InfoFieldList[4], 'extra') !== false) {
8377 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8378 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8379 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8380 } else {
8381 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8382 }
8383 } else {
8384 $sqlwhere .= ' WHERE 1=1';
8385 }
8386
8387 // Add Usf filter on second line
8388 /*
8389 if ($Usf) {
8390 $errorstr = '';
8391 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8392 if (!$errorstr) {
8393 $sqlwhere .= $sqlusf;
8394 } else {
8395 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8396 }
8397 }
8398 */
8399
8400 // Some tables may have field, some other not. For the moment we disable it.
8401 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8402 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8403 }
8404 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8405 // print $sql;
8406
8407 $sql .= $sqlwhere;
8408
8409 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8410
8411 $resql = $this->db->query($sql);
8412 if ($resql) {
8413 $num = $this->db->num_rows($resql);
8414 $i = 0;
8415
8416 $data = array();
8417
8418 while ($i < $num) {
8419 $labeltoshow = '';
8420 $obj = $this->db->fetch_object($resql);
8421
8422 $notrans = false;
8423 // Several field into label (eq table:code|libelle:rowid)
8424 $fields_label = explode('|', $InfoFieldList[1]);
8425 if (count($fields_label) > 1) {
8426 $notrans = true;
8427 foreach ($fields_label as $field_toshow) {
8428 $labeltoshow .= $obj->$field_toshow . ' ';
8429 }
8430 } else {
8431 $labeltoshow = $obj->{$InfoFieldList[1]};
8432 }
8433 $labeltoshow = dol_trunc($labeltoshow, 45);
8434
8435 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8436 foreach ($fields_label as $field_toshow) {
8437 $translabel = $langs->trans($obj->$field_toshow);
8438 if ($translabel != $obj->$field_toshow) {
8439 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8440 } else {
8441 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8442 }
8443 }
8444
8445 $data[$obj->rowid] = $labeltoshow;
8446 } else {
8447 if (!$notrans) {
8448 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8449 if ($translabel != $obj->{$InfoFieldList[1]}) {
8450 $labeltoshow = dol_trunc($translabel, 18);
8451 } else {
8452 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8453 }
8454 }
8455 if (empty($labeltoshow)) {
8456 $labeltoshow = '(not defined)';
8457 }
8458
8459 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8460 $data[$obj->rowid] = $labeltoshow;
8461 }
8462
8463 if (!empty($InfoFieldList[3]) && $parentField) {
8464 $parent = $parentName . ':' . $obj->{$parentField};
8465 $isDependList = 1;
8466 }
8467
8468 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8469 }
8470
8471 $i++;
8472 }
8473 $this->db->free($resql);
8474
8475 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8476 } else {
8477 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8478 }
8479 } else {
8480 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8481 $categcode = $InfoFieldList[5];
8482 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8483 $tmpcategory = new Categorie($this->db);
8484 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8485 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8486 }
8487
8488 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8489 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8490 }
8491 }
8492 } elseif ($type == 'link') {
8493 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8494 // Filter can contains some ':' inside.
8495 $param_list = array_keys($param['options']);
8496 $param_list_array = explode(':', $param_list[0], 4);
8497
8498 $showempty = (($required && $default != '') ? 0 : 1);
8499
8500 if (!preg_match('/search_/', $keyprefix)) {
8501 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8502 // @phan-suppress-next-line PhanTypeMismatchProperty
8503 if (!empty($this->fields[$key]['picto'])) {
8504 $morecss .= ' widthcentpercentminusxx';
8505 } else {
8506 $morecss .= ' widthcentpercentminusx';
8507 }
8508 } else {
8509 // @phan-suppress-next-line PhanTypeMismatchProperty
8510 if (!empty($this->fields[$key]['picto'])) {
8511 $morecss .= ' widthcentpercentminusx';
8512 }
8513 }
8514 }
8515
8516 // $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.
8517
8518 // $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.
8519
8520 $objectfield = $val;
8521
8522 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8523 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8524
8525 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8526 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8527 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8528 list($class, $classfile) = explode(':', $param_list[0]);
8529 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8530 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8531 } else {
8532 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8533 }
8534 $paramforthenewlink = '';
8535 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8536 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8537 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8538 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8539 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8540 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8541 $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>';
8542 }
8543 }
8544 } elseif ($type == 'password') {
8545 // If prefix is 'search_', field is used as a filter, we use a common text field.
8546 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8547 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8548 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8549 } else {
8550 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8551 }
8552 } elseif ($type == 'array') {
8553 $newval = $val;
8554 $newval['type'] = 'varchar(256)';
8555
8556 $out = '';
8557 if (!empty($value)) {
8558 foreach ($value as $option) {
8559 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8560 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8561 }
8562 }
8563 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8564
8565 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8566 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8567
8568 if (!empty($conf->use_javascript_ajax)) {
8569 $out .= '
8570 <script nonce="'.getNonce().'">
8571 $(document).ready(function() {
8572 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8573 $("'.dol_escape_js($newInput).'").insertBefore(this);
8574 });
8575
8576 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8577 $(this).parent().remove();
8578 });
8579 });
8580 </script>';
8581 }
8582 }
8583 if (!empty($hidden)) {
8584 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8585 }
8586
8587 if ($isDependList == 1) {
8588 $out .= $this->getJSListDependancies('_common');
8589 }
8590 /* Add comments
8591 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8592 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8593 */
8594
8595 // Display error message for field
8596 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8597 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8598 }
8599
8600 return $out;
8601 }
8602
8616 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8617 {
8618 global $conf, $langs, $form;
8619
8620 // 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...)
8621
8622 if (!is_object($form)) {
8623 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8624 $form = new Form($this->db);
8625 }
8626
8627 //$label = empty($val['label']) ? '' : $val['label'];
8628 $type = empty($val['type']) ? '' : $val['type'];
8629 $size = empty($val['css']) ? '' : $val['css'];
8630 $reg = array();
8631
8632 // Convert var to be able to share same code than showOutputField of extrafields
8633 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8634 $type = 'varchar'; // convert varchar(xx) int varchar
8635 $size = $reg[1];
8636 } elseif (preg_match('/varchar/', $type)) {
8637 $type = 'varchar'; // convert varchar(xx) int varchar
8638 }
8639 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8640 // @phan-suppress-next-line PhanTypeMismatchProperty
8641 if (empty($this->fields[$key]['multiinput'])) {
8642 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8643 }
8644 }
8645 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8646 $type = 'link';
8647 }
8648
8649 $default = empty($val['default']) ? '' : $val['default'];
8650 $computed = empty($val['computed']) ? '' : $val['computed'];
8651 $unique = empty($val['unique']) ? '' : $val['unique'];
8652 $required = empty($val['required']) ? '' : $val['required'];
8653 $param = array();
8654 $param['options'] = array();
8655
8656 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8657 $param['options'] = $val['arrayofkeyval'];
8658 }
8659 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8660 $type = 'link';
8661 $stringforoptions = $reg[1].':'.$reg[2];
8662 // Special case: Force addition of getnomurlparam1 to -1 for users
8663 if ($reg[1] == 'User') {
8664 $stringforoptions .= ':#getnomurlparam1=-1';
8665 }
8666 $param['options'] = array($stringforoptions => $stringforoptions);
8667 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8668 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8669 $type = 'sellist';
8670 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8671 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8672 $type = 'sellist';
8673 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8674 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8675 $type = 'sellist';
8676 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8677 $param['options'] = array($reg[1] => 'N');
8678 $type = 'chkbxlst';
8679 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8680 $param['options'] = array();
8681 $type = 'stars';
8682 $size = $reg[1];
8683 }
8684
8685 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8686 $list = (empty($val['list']) ? '' : $val['list']);
8687 $help = (empty($val['help']) ? '' : $val['help']);
8688 $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)
8689
8690 if ($hidden) {
8691 return '';
8692 }
8693
8694 // If field is a computed field, value must become result of compute
8695 if ($computed) {
8696 // Make the eval of compute string
8697 //var_dump($computed);
8698 $value = dol_eval((string) $computed, 1, 0, '2');
8699 }
8700
8701 if (empty($morecss)) {
8702 if ($type == 'date') {
8703 $morecss = 'minwidth100imp';
8704 } elseif ($type == 'datetime' || $type == 'timestamp') {
8705 $morecss = 'minwidth200imp';
8706 } elseif (in_array($type, array('int', 'double', 'price'))) {
8707 $morecss = 'maxwidth75';
8708 } elseif ($type == 'url') {
8709 $morecss = 'minwidth400';
8710 } elseif ($type == 'boolean') {
8711 $morecss = '';
8712 } else {
8713 if (is_numeric($size) && round((float) $size) < 12) {
8714 $morecss = 'minwidth100';
8715 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8716 $morecss = 'minwidth200';
8717 } else {
8718 $morecss = 'minwidth400';
8719 }
8720 }
8721 }
8722
8723 // Format output value differently according to properties of field
8724 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8725 // @phan-suppress-next-line PhanTypeMismatchProperty
8726 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.
8727 $value = $this->getNomUrl(1, '', 0, '', 1);
8728 }
8729 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8730 $value = $this->getLibStatut(3);
8731 } elseif ($type == 'date') {
8732 if (!empty($value)) {
8733 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8734 } else {
8735 $value = '';
8736 }
8737 } elseif ($type == 'datetime' || $type == 'timestamp') {
8738 if (!empty($value)) {
8739 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8740 } else {
8741 $value = '';
8742 }
8743 } elseif ($type == 'duration') {
8744 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8745 if (!is_null($value) && $value !== '') {
8746 $value = convertSecondToTime((int) $value, 'allhourmin');
8747 }
8748 } elseif ($type == 'double' || $type == 'real') {
8749 if (!is_null($value) && $value !== '') {
8750 $value = price($value);
8751 }
8752 } elseif ($type == 'boolean') {
8753 $checked = '';
8754 if (!empty($value)) {
8755 $checked = ' checked ';
8756 }
8757 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8758 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8759 } else {
8760 $value = yn($value ? 1 : 0);
8761 }
8762 } elseif ($type == 'mail' || $type == 'email') {
8763 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8764 } elseif ($type == 'url') {
8765 $value = dol_print_url((string) $value, '_blank', 32, 1);
8766 } elseif ($type == 'phone') {
8767 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8768 } elseif ($type == 'ip') {
8769 $value = dol_print_ip((string) $value, 0);
8770 } elseif ($type == 'stars') {
8771 $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 : '').'>';
8772 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8773 $i = 1;
8774 while ($i <= $size) {
8775 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8776 $i++;
8777 }
8778 $value .= '</div>';
8779 $value .= '<script>
8780 $(document).ready(function() { /* commonobject.class.php 2 */
8781 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8782 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8783 container.find(".star").each(function() {
8784 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8785 });
8786 container.find(".star").on("mouseover", function() {
8787 let selectedStar = $(this).data("value");
8788 container.find(".star").each(function() {
8789 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8790 });
8791 });
8792 container.on("mouseout", function() {
8793 container.find(".star").each(function() {
8794 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8795 });
8796 });
8797 container.find(".star").off("click").on("click", function() {
8798 selectedStars = $(this).data("value");
8799 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8800 selectedStars = 0;
8801 }
8802 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8803 container.find(".star").each(function() {
8804 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8805 });
8806 $.ajax({
8807 url: "ajax/'.$this->element.'.php",
8808 method: "POST",
8809 data: {
8810 objectId: "'.$this->id.'",
8811 field: "'.$keyprefix.$key.$keysuffix.'",
8812 value: selectedStars,
8813 token: "'.newToken().'"
8814 },
8815 success: function(response) {
8816 var res = JSON.parse(response);
8817 console[res.status === "success" ? "log" : "error"](res.message);
8818 },
8819 error: function(xhr, status, error) {
8820 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8821 }
8822 });
8823 });
8824 });
8825 </script>';
8826 } elseif ($type == 'price') {
8827 if (!is_null($value) && $value !== '') {
8828 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8829 }
8830 } elseif ($type == 'select') {
8831 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8832 if (strpos($value, "|") !== false) {
8833 $value = $langs->trans(explode('|', $value)[0]);
8834 } elseif (! is_numeric($value)) {
8835 $value = $langs->trans($value);
8836 }
8837 } elseif ($type == 'sellist') {
8838 $param_list = array_keys($param['options']);
8839 $InfoFieldList = explode(":", $param_list[0]);
8840
8841 $selectkey = "rowid";
8842 $keyList = 'rowid';
8843
8844 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8845 $selectkey = $InfoFieldList[2];
8846 $keyList = $InfoFieldList[2].' as rowid';
8847 }
8848
8849 $fields_label = explode('|', $InfoFieldList[1]);
8850 if (is_array($fields_label)) {
8851 $keyList .= ', ';
8852 $keyList .= implode(', ', $fields_label);
8853 }
8854
8855 $filter_categorie = false;
8856 if (count($InfoFieldList) > 5) {
8857 if ($InfoFieldList[0] == 'categorie') {
8858 $filter_categorie = true;
8859 }
8860 }
8861
8862 $sql = "SELECT ".$keyList;
8863 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8864 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8865 $sql .= ' as main';
8866 }
8867 if ($selectkey == 'rowid' && empty($value)) {
8868 $sql .= " WHERE ".$selectkey." = 0";
8869 } elseif ($selectkey == 'rowid') {
8870 $sql .= " WHERE ".$selectkey." = ".((int) $value);
8871 } else {
8872 $sql .= " WHERE ".$selectkey." = '".$this->db->escape((string) $value)."'";
8873 }
8874
8875 //$sql.= ' AND entity = '.$conf->entity;
8876
8877 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
8878 $resql = $this->db->query($sql);
8879 if ($resql) {
8880 if (!$filter_categorie) {
8881 $value = ''; // value was used, so now we reset it to use it to build final output
8882 $numrows = $this->db->num_rows($resql);
8883 if ($numrows) {
8884 $obj = $this->db->fetch_object($resql);
8885
8886 // Several field into label (eq table:code|libelle:rowid)
8887 $fields_label = explode('|', $InfoFieldList[1]);
8888
8889 if (is_array($fields_label) && count($fields_label) > 1) {
8890 foreach ($fields_label as $field_toshow) {
8891 $translabel = '';
8892 if (!empty($obj->$field_toshow)) {
8893 $translabel = $langs->trans($obj->$field_toshow);
8894 }
8895 if ($translabel != $field_toshow) {
8896 $value .= dol_trunc($translabel, 18) . ' ';
8897 } else {
8898 $value .= $obj->$field_toshow . ' ';
8899 }
8900 }
8901 } else {
8902 $translabel = '';
8903 if (!empty($obj->{$InfoFieldList[1]})) {
8904 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8905 }
8906 if ($translabel != $obj->{$InfoFieldList[1]}) {
8907 $value = dol_trunc($translabel, 18);
8908 } else {
8909 $value = $obj->{$InfoFieldList[1]};
8910 }
8911 }
8912 }
8913 } else {
8914 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8915
8916 $toprint = array();
8917 $obj = $this->db->fetch_object($resql);
8918 $c = new Categorie($this->db);
8919 $c->fetch($obj->rowid);
8920 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8921 foreach ($ways as $way) {
8922 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8923 }
8924 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8925 }
8926 } else {
8927 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8928 }
8929 } elseif ($type == 'radio') {
8930 $value = $param['options'][(string) $value];
8931 } elseif ($type == 'checkbox') {
8932 $value_arr = explode(',', (string) $value);
8933 $value = '';
8934 if (is_array($value_arr) && count($value_arr) > 0) {
8935 $toprint = array();
8936 foreach ($value_arr as $keyval => $valueval) {
8937 if (!empty($valueval)) {
8938 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
8939 }
8940 }
8941 if (!empty($toprint)) {
8942 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8943 }
8944 }
8945 } elseif ($type == 'chkbxlst') {
8946 $value_arr = (isset($value) ? explode(',', $value) : array());
8947
8948 $param_list = array_keys($param['options']);
8949 $InfoFieldList = explode(":", $param_list[0]);
8950
8951 $selectkey = "rowid";
8952 $keyList = 'rowid';
8953
8954 if (count($InfoFieldList) >= 3) {
8955 $selectkey = $InfoFieldList[2];
8956 $keyList = $InfoFieldList[2].' as rowid';
8957 }
8958
8959 $fields_label = explode('|', $InfoFieldList[1]);
8960 if (is_array($fields_label)) {
8961 $keyList .= ', ';
8962 $keyList .= implode(', ', $fields_label);
8963 }
8964
8965 $filter_categorie = false;
8966 if (count($InfoFieldList) > 5) {
8967 if ($InfoFieldList[0] == 'categorie') {
8968 $filter_categorie = true;
8969 }
8970 }
8971
8972 $sql = "SELECT ".$keyList;
8973 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8974 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8975 $sql .= ' as main';
8976 }
8977 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
8978 // $sql.= ' AND entity = '.$conf->entity;
8979
8980 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
8981 $resql = $this->db->query($sql);
8982 if ($resql) {
8983 if (!$filter_categorie) {
8984 $value = ''; // value was used, so now we reset it to use it to build final output
8985 $toprint = array();
8986 while ($obj = $this->db->fetch_object($resql)) {
8987 // Several field into label (eq table:code|libelle:rowid)
8988 $fields_label = explode('|', $InfoFieldList[1]);
8989 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8990 if (is_array($fields_label) && count($fields_label) > 1) {
8991 foreach ($fields_label as $field_toshow) {
8992 $translabel = '';
8993 if (!empty($obj->$field_toshow)) {
8994 $translabel = $langs->trans($obj->$field_toshow);
8995 }
8996 if ($translabel != $field_toshow) {
8997 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8998 } else {
8999 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9000 }
9001 }
9002 } else {
9003 $translabel = '';
9004 if (!empty($obj->{$InfoFieldList[1]})) {
9005 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9006 }
9007 if ($translabel != $obj->{$InfoFieldList[1]}) {
9008 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9009 } else {
9010 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9011 }
9012 }
9013 }
9014 }
9015 } else {
9016 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9017
9018 $toprint = array();
9019 while ($obj = $this->db->fetch_object($resql)) {
9020 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9021 $c = new Categorie($this->db);
9022 $c->fetch($obj->rowid);
9023 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9024 foreach ($ways as $way) {
9025 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9026 }
9027 }
9028 }
9029 }
9030 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9031 } else {
9032 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9033 }
9034 } elseif ($type == 'link') {
9035 $out = '';
9036
9037 // only if something to display (perf)
9038 if ($value) {
9039 $param_list = array_keys($param['options']);
9040 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9041 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9042
9043 $InfoFieldList = explode(":", $param_list[0]);
9044
9045 $classname = $InfoFieldList[0];
9046 $classpath = $InfoFieldList[1];
9047
9048 // Set $getnomurlparam1 et getnomurlparam2
9049 $getnomurlparam = 3;
9050 $getnomurlparam2 = '';
9051 $regtmp = array();
9052 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9053 $getnomurlparam = $regtmp[1];
9054 }
9055 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9056 $getnomurlparam2 = $regtmp[1];
9057 }
9058
9059 if (!empty($classpath)) {
9060 dol_include_once($InfoFieldList[1]);
9061
9062 if ($classname && !class_exists($classname)) {
9063 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9064 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9065 $element_prop = getElementProperties($classname);
9066 if ($element_prop) {
9067 $classname = $element_prop['classname'];
9068 }
9069 }
9070
9071
9072 if ($classname && class_exists($classname)) {
9073 $object = new $classname($this->db);
9074 '@phan-var-force CommonObject $object';
9075 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9076 '@phan-var-force Product $object';
9077 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9078 } else {
9079 $result = $object->fetch($value);
9080 }
9081 if ($result > 0) {
9082 if ($object->element === 'product') {
9083 '@phan-var-force Product $object';
9084 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9085 if (isset($val['get_name_url_params'])) {
9086 $get_name_url_params = explode(':', $val['get_name_url_params']);
9087 if (!empty($get_name_url_params)) {
9088 $param_num_max = count($get_name_url_param_arr) - 1;
9089 foreach ($get_name_url_params as $param_num => $param_value) {
9090 if ($param_num > $param_num_max) {
9091 break;
9092 }
9093 $get_name_url_param_arr[$param_num] = $param_value;
9094 }
9095 }
9096 }
9097
9101 $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]);
9102 } else {
9103 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9104 }
9105 } else {
9106 $value = '';
9107 }
9108 }
9109 } else {
9110 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9111 return 'Error bad setup of extrafield';
9112 }
9113 } else {
9114 $value = '';
9115 }
9116 } elseif ($type == 'password') {
9117 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9118 //$value = preg_replace('/./i', '*', $value);
9119 } elseif ($type == 'array') {
9120 if (is_array($value)) {
9121 $value = implode('<br>', $value);
9122 } else {
9123 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9124 return 'Error unexpected result from code evaluation';
9125 }
9126 } else { // text|html|varchar
9127 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9128 $value = str_replace(',', "\n", $value);
9129 }
9130 $value = dol_htmlentitiesbr((string) $value);
9131 }
9132
9133 //print $type.'-'.$size.'-'.$value;
9134 $out = $value;
9135
9136 return is_null($out) ? '' : $out;
9137 }
9138
9145 public function clearFieldError($fieldKey)
9146 {
9147 $this->error = '';
9148 unset($this->validateFieldsErrors[$fieldKey]);
9149 }
9150
9158 public function setFieldError($fieldKey, $msg = '')
9159 {
9160 global $langs;
9161 if (empty($msg)) {
9162 $msg = $langs->trans("UnknownError");
9163 }
9164
9165 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9166 }
9167
9174 public function getFieldError($fieldKey)
9175 {
9176 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9177 return $this->validateFieldsErrors[$fieldKey];
9178 }
9179 return '';
9180 }
9181
9190 public function validateField($fields, $fieldKey, $fieldValue)
9191 {
9192 global $langs;
9193
9194 if (!class_exists('Validate')) {
9195 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9196 }
9197
9198 $this->clearFieldError($fieldKey);
9199
9200 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9201 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9202 return false;
9203 }
9204
9205 $val = $fields[$fieldKey];
9206
9207 $param = array();
9208 $param['options'] = array();
9209 $type = $val['type'];
9210
9211 $required = false;
9212 if (isset($val['notnull']) && $val['notnull'] === 1) {
9213 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9214 $required = true;
9215 }
9216
9217 $maxSize = 0;
9218 $minSize = 0;
9219
9220 //
9221 // PREPARE Elements
9222 //
9223 $reg = array();
9224
9225 // Convert var to be able to share same code than showOutputField of extrafields
9226 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9227 $type = 'varchar'; // convert varchar(xx) int varchar
9228 $maxSize = (int) $reg[1];
9229 } elseif (preg_match('/varchar/', $type)) {
9230 $type = 'varchar'; // convert varchar(xx) int varchar
9231 }
9232
9233 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9234 $type = 'select';
9235 }
9236
9237 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9238 $type = 'link';
9239 }
9240
9241 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9242 $param['options'] = $val['arrayofkeyval'];
9243 }
9244
9245 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9246 $type = 'link';
9247 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9248 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9249 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9250 $type = 'sellist';
9251 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9252 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9253 $type = 'sellist';
9254 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9255 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9256 $type = 'sellist';
9257 }
9258
9259 //
9260 // TEST Value
9261 //
9262
9263 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9264 $validate = new Validate($this->db, $langs);
9265
9266
9267 // little trick : to perform tests with good performances sort tests by quick to low
9268
9269 //
9270 // COMMON TESTS
9271 //
9272
9273 // Required test and empty value
9274 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9275 $this->setFieldError($fieldKey, $validate->error);
9276 return false;
9277 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9278 // if no value sent and the field is not mandatory, no need to perform tests
9279 return true;
9280 }
9281
9282 // MAX Size test
9283 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9284 $this->setFieldError($fieldKey, $validate->error);
9285 return false;
9286 }
9287
9288 // MIN Size test
9289 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9290 $this->setFieldError($fieldKey, $validate->error);
9291 return false;
9292 }
9293
9294 //
9295 // TESTS for TYPE
9296 //
9297
9298 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9299 if (!$validate->isTimestamp($fieldValue)) {
9300 $this->setFieldError($fieldKey, $validate->error);
9301 return false;
9302 } else {
9303 return true;
9304 }
9305 } elseif ($type == 'duration') {
9306 if (!$validate->isDuration($fieldValue)) {
9307 $this->setFieldError($fieldKey, $validate->error);
9308 return false;
9309 } else {
9310 return true;
9311 }
9312 } elseif (in_array($type, array('double', 'real', 'price'))) {
9313 // is numeric
9314 if (!$validate->isNumeric($fieldValue)) {
9315 $this->setFieldError($fieldKey, $validate->error);
9316 return false;
9317 } else {
9318 return true;
9319 }
9320 } elseif ($type == 'boolean') {
9321 if (!$validate->isBool($fieldValue)) {
9322 $this->setFieldError($fieldKey, $validate->error);
9323 return false;
9324 } else {
9325 return true;
9326 }
9327 } elseif ($type == 'mail' || $type == 'email') {
9328 if (!$validate->isEmail($fieldValue)) {
9329 $this->setFieldError($fieldKey, $validate->error);
9330 return false;
9331 }
9332 } elseif ($type == 'url') {
9333 if (!$validate->isUrl($fieldValue)) {
9334 $this->setFieldError($fieldKey, $validate->error);
9335 return false;
9336 } else {
9337 return true;
9338 }
9339 } elseif ($type == 'phone') {
9340 if (!$validate->isPhone($fieldValue)) {
9341 $this->setFieldError($fieldKey, $validate->error);
9342 return false;
9343 } else {
9344 return true;
9345 }
9346 } elseif ($type == 'select' || $type == 'radio') {
9347 if (!isset($param['options'][$fieldValue])) {
9348 $this->error = $langs->trans('RequireValidValue');
9349 return false;
9350 } else {
9351 return true;
9352 }
9353 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9354 $param_list = array_keys($param['options']);
9355 $InfoFieldList = explode(":", $param_list[0]);
9356 $value_arr = explode(',', $fieldValue);
9357 $value_arr = array_map(array($this->db, 'escape'), $value_arr);
9358
9359 $selectkey = "rowid";
9360 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9361 $selectkey = $InfoFieldList[2];
9362 }
9363
9364 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9365 $this->setFieldError($fieldKey, $validate->error);
9366 return false;
9367 } else {
9368 return true;
9369 }
9370 } elseif ($type == 'link') {
9371 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9372 $InfoFieldList = explode(":", $param_list[0]);
9373 $classname = $InfoFieldList[0];
9374 $classpath = $InfoFieldList[1];
9375 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9376 $lastIsFetchableError = $validate->error;
9377
9378 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9379 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9380 return true;
9381 }
9382
9383 $this->setFieldError($fieldKey, $lastIsFetchableError);
9384 return false;
9385 } else {
9386 return true;
9387 }
9388 }
9389
9390 // if no test failed all is ok
9391 return true;
9392 }
9393
9407 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9408 {
9409 global $db, $conf, $langs, $action, $form, $hookmanager;
9410
9411 if (!is_object($form)) {
9412 $form = new Form($db);
9413 }
9414 if (!is_object($extrafields)) {
9415 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9416 return 'Bad parameter extrafields for showOptionals';
9417 }
9418 if (!is_array($extrafields->attributes[$this->table_element])) {
9419 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9420 }
9421
9422 $out = '';
9423
9424 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9425 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9426 $hookResPrint = $hookmanager->resPrint;
9427
9428 if (empty($reshook)) {
9429 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) {
9430 $out .= "\n";
9431 $out .= '<!-- commonobject:showOptionals --> ';
9432 $out .= "\n";
9433
9434 $nbofextrafieldsshown = 0;
9435 $e = 0; // var to manage the modulo (odd/even)
9436
9437 $lastseparatorkeyfound = '';
9438 $extrafields_collapse_num = '';
9439 $extrafields_collapse_num_old = '';
9440 $i = 0;
9441
9442 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9443 $i++;
9444
9445 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9446 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9447 continue;
9448 }
9449
9450 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9451 $enabled = 1;
9452 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9453 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9454 }
9455 if (empty($enabled)) {
9456 continue;
9457 }
9458
9459 $visibility = 1;
9460 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9461 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9462 }
9463
9464 $perms = 1;
9465 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9466 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9467 }
9468
9469 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9470 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9471 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9472 // 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
9473 $ef_name = 'options_' . $key;
9474 $ef_value = $this->array_options[$ef_name]??'';
9475 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . $ef_value . '" />' . "\n";
9476 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9477 } elseif ($mode == 'view' && empty($visibility)) {
9478 continue;
9479 }
9480 if (empty($perms)) {
9481 continue;
9482 }
9483
9484 // Load language if required
9485 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9486 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9487 }
9488
9489 $colspan = 0;
9490 $value = null;
9491 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9492 if (array_key_exists('cols', $params)) {
9493 $colspan = $params['cols'];
9494 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9495 $reg = array();
9496 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9497 $colspan = $reg[1];
9498 } else {
9499 $colspan = $params['colspan'];
9500 }
9501 }
9502 }
9503 $colspan = intval($colspan);
9504
9505 switch ($mode) {
9506 case "view":
9507 $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
9508 break;
9509 case "create":
9510 case "edit":
9511 // We get the value of property found with GETPOST so it takes into account:
9512 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9513 $check = 'alphanohtml';
9514 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9515 $check = 'restricthtml';
9516 }
9517 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9518 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9519 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9520 if (is_array($getposttemp)) {
9521 // $getposttemp is an array but following code expects a comma separated string
9522 $value = implode(",", $getposttemp);
9523 } else {
9524 $value = $getposttemp;
9525 }
9526 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9527 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9528 } else {
9529 $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.
9530 }
9531 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9532 break;
9533 }
9534
9535 $nbofextrafieldsshown++;
9536
9537 // Output value of the current field
9538 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9539 $extrafields_collapse_num = $key;
9540 /*
9541 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9542 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9543 $extrafield_param_list = array_keys($extrafield_param['options']);
9544
9545 if (count($extrafield_param_list) > 0) {
9546 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9547
9548 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9549 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9550 $extrafields_collapse_num = $key;
9551 }
9552 }
9553 }
9554 */
9555
9556 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9557 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9558
9559 $lastseparatorkeyfound = $key;
9560 } else {
9561 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9562
9563 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9564 $csstyle = '';
9565 if (is_array($params) && count($params) > 0) {
9566 if (array_key_exists('class', $params)) {
9567 $class .= $params['class'].' ';
9568 }
9569 if (array_key_exists('style', $params)) {
9570 $csstyle = $params['style'];
9571 }
9572 }
9573
9574 // add html5 elements
9575 $domData = ' data-element="extrafield"';
9576 $domData .= ' data-targetelement="'.$this->element.'"';
9577 $domData .= ' data-targetid="'.$this->id.'"';
9578
9579 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9580 if ($display_type == 'card') {
9581 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9582 $colspan = 0;
9583 }
9584
9585 if ($action == 'selectlines') {
9586 $colspan++;
9587 }
9588 }
9589
9590 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9591 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9592 $force_values_on_change_company = (
9593 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9594 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9595 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9596 );
9597 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9598 $value = $this->array_options['options_'.$key] ?? $value;
9599 }
9600
9601 // Convert date into timestamp format (value in memory must be a timestamp)
9602 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9603 $datenotinstring = null;
9604 if (array_key_exists('options_'.$key, $this->array_options)) {
9605 $datenotinstring = $this->array_options['options_'.$key];
9606 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9607 $datenotinstring = $this->db->jdate($datenotinstring);
9608 }
9609 }
9610 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9611 $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;
9612 }
9613 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9614 $datenotinstring = null;
9615 if (array_key_exists('options_'.$key, $this->array_options)) {
9616 $datenotinstring = $this->array_options['options_'.$key];
9617 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9618 $datenotinstring = $this->db->jdate($datenotinstring);
9619 }
9620 }
9621 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9622 $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;
9623 }
9624 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9625 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9626 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9627 $value = price2num($value);
9628 } elseif (isset($this->array_options['options_'.$key])) {
9629 $value = $this->array_options['options_'.$key];
9630 }
9631 }
9632
9633 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9634 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9635 if ($action == 'create' || $mode == 'create') {
9636 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9637 }
9638 }
9639
9640 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9641 if ($action == 'create') {
9642 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9643 }
9644 }
9645
9646
9647 $labeltoshow = $langs->trans($label);
9648 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9649 if ($display_type == 'card') {
9650 $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.' >';
9651 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9652 $out .= '<td></td>';
9653 }
9654 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9655 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9656 $out .= ' tdtop';
9657 }
9658 } elseif ($display_type == 'line') {
9659 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9660 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9661 }
9662 //$out .= "titlefield";
9663 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9664 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9665 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9666 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9667 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9668 $out .= ' fieldrequired';
9669 }
9670 }
9671 $out .= '">';
9672 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9673 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9674 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9675 } else {
9676 $out .= $labeltoshow;
9677 }
9678 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9679 $out .= '&nbsp;<span style="color: red">*</span>';
9680 }
9681 } else {
9682 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9683 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9684 } else {
9685 $out .= $labeltoshow;
9686 }
9687 }
9688
9689 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9690
9691 // Second column
9692 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9693 if ($display_type == 'card') {
9694 // 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
9695 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9696 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9697 $out .= '>';
9698 } elseif ($display_type == 'line') {
9699 $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].'">';
9700 }
9701
9702 switch ($mode) {
9703 case "view":
9704 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9705 break;
9706 case "create":
9707 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9708 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9709 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9710 }
9711 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9712 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9713 break;
9714 case "edit":
9715 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9716 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9717 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9718 }
9719 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9720 break;
9721 }
9722
9723 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9724 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9725 $e++;
9726 }
9727 }
9728 $out .= "\n";
9729 // Add code to manage list depending on others
9730 if (!empty($conf->use_javascript_ajax)) {
9731 $out .= $this->getJSListDependancies();
9732 }
9733
9734 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9735
9736 if (empty($nbofextrafieldsshown)) {
9737 $out = '';
9738 }
9739 }
9740 }
9741
9742 $out .= $hookResPrint;
9743
9744 return $out;
9745 }
9746
9751 public function getJSListDependancies($type = '_extra')
9752 {
9753 $out = '
9754 <script nonce="'.getNonce().'">
9755 jQuery(document).ready(function() {
9756 function showOptions'.$type.'(child_list, parent_list, orig_select)
9757 {
9758 var val = $("select[name=\""+parent_list+"\"]").val();
9759 var parentVal = parent_list + ":" + val;
9760 if(typeof val == "string"){
9761 if(val != "") {
9762 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9763 $("select[name=\""+child_list+"\"] option[parent]").remove();
9764 $("select[name=\""+child_list+"\"]").append(options);
9765 } else {
9766 var options = orig_select.find("option[parent]").clone();
9767 $("select[name=\""+child_list+"\"] option[parent]").remove();
9768 $("select[name=\""+child_list+"\"]").append(options);
9769 }
9770 } else if(val > 0) {
9771 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9772 $("select[name=\""+child_list+"\"] option[parent]").remove();
9773 $("select[name=\""+child_list+"\"]").append(options);
9774 } else {
9775 var options = orig_select.find("option[parent]").clone();
9776 $("select[name=\""+child_list+"\"] option[parent]").remove();
9777 $("select[name=\""+child_list+"\"]").append(options);
9778 }
9779 }
9780 function setListDependencies'.$type.'() {
9781 jQuery("select option[parent]").parent().each(function() {
9782 var orig_select = {};
9783 var child_list = $(this).attr("name");
9784 orig_select[child_list] = $(this).clone();
9785 var parent = $(this).find("option[parent]:first").attr("parent");
9786 var infos = parent.split(":");
9787 var parent_list = infos[0];
9788
9789 //Hide daughters lists
9790 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9791 $("#"+child_list).hide();
9792 //Show mother lists
9793 } else if ($("#"+parent_list).val() != 0){
9794 $("#"+parent_list).show();
9795 }
9796 //Show the child list if the parent list value is selected
9797 $("select[name=\""+parent_list+"\"]").click(function() {
9798 if ($(this).val() != 0){
9799 $("#"+child_list).show()
9800 }
9801 });
9802
9803 //When we change parent list
9804 $("select[name=\""+parent_list+"\"]").change(function() {
9805 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9806 //Select the value 0 on child list after a change on the parent list
9807 $("#"+child_list).val(0).trigger("change");
9808 //Hide child lists if the parent value is set to 0
9809 if ($(this).val() == 0){
9810 $("#"+child_list).hide();
9811 }
9812 });
9813 });
9814 }
9815
9816 setListDependencies'.$type.'();
9817 });
9818 </script>'."\n";
9819 return $out;
9820 }
9821
9827 public function getRights()
9828 {
9829 global $user;
9830
9831 $module = empty($this->module) ? '' : $this->module;
9832 $element = $this->element;
9833
9834 if ($element == 'facturerec') {
9835 $element = 'facture';
9836 } elseif ($element == 'invoice_supplier_rec') {
9837 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9838 } elseif ($module && $user->hasRight($module, $element)) {
9839 // for modules built with ModuleBuilder
9840 return $user->hasRight($module, $element);
9841 }
9842
9843 return $user->rights->$element;
9844 }
9845
9858 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9859 {
9860 global $hookmanager;
9861
9862 $parameters = array(
9863 'origin_id' => $origin_id,
9864 'dest_id' => $dest_id,
9865 'tables' => $tables,
9866 );
9867 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
9868 if ($reshook) {
9869 return true; // replacement code
9870 } elseif ($reshook < 0) {
9871 return $ignoreerrors === 1; // failure
9872 } // reshook = 0 => execute normal code
9873
9874 foreach ($tables as $table) {
9875 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
9876
9877 if (!$dbs->query($sql)) {
9878 if ($ignoreerrors) {
9879 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.
9880 }
9881 //$this->errors = $db->lasterror();
9882 return false;
9883 }
9884 }
9885
9886 return true;
9887 }
9888
9901 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9902 {
9903 foreach ($tables as $table) {
9904 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
9905
9906 if (!$dbs->query($sql)) {
9907 if ($ignoreerrors) {
9908 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.
9909 }
9910 //$this->errors = $db->lasterror();
9911 return false;
9912 }
9913 }
9914
9915 return true;
9916 }
9917
9930 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
9931 {
9932 global $conf;
9933
9934 $buyPrice = 0;
9935
9936 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
9937 // When ForceBuyingPriceIfNull is set
9938 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
9939 } else {
9940 // Get cost price for margin calculation
9941 if (!empty($fk_product) && $fk_product > 0) {
9942 $result = 0;
9943 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
9944 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9945 $product = new Product($this->db);
9946 $result = $product->fetch($fk_product);
9947 if ($result <= 0) {
9948 $this->errors[] = 'ErrorProductIdDoesNotExists';
9949 return -1;
9950 }
9951 if ($product->cost_price > 0) {
9952 $buyPrice = $product->cost_price;
9953 } elseif ($product->pmp > 0) {
9954 $buyPrice = $product->pmp;
9955 }
9956 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
9957 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9958 $product = new Product($this->db);
9959 $result = $product->fetch($fk_product);
9960 if ($result <= 0) {
9961 $this->errors[] = 'ErrorProductIdDoesNotExists';
9962 return -1;
9963 }
9964 if ($product->pmp > 0) {
9965 $buyPrice = $product->pmp;
9966 }
9967 }
9968
9969 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
9970 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
9971 $productFournisseur = new ProductFournisseur($this->db);
9972 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
9973 $buyPrice = $productFournisseur->fourn_unitprice;
9974 } elseif ($result < 0) {
9975 $this->errors[] = $productFournisseur->error;
9976 return -2;
9977 }
9978 }
9979 }
9980 }
9981
9982 return (float) $buyPrice;
9983 }
9984
9992 public function getDataToShowPhoto($modulepart, $imagesize)
9993 {
9994 // See getDataToShowPhoto() implemented by Product for example.
9995 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
9996 }
9997
9998
9999 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10019 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')
10020 {
10021 // phpcs:enable
10022 global $user, $langs;
10023
10024 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10025 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10026
10027 $sortfield = 'position_name';
10028 $sortorder = 'asc';
10029
10030 $dir = $sdir.'/';
10031 $pdir = '/';
10032
10033 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10034 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10035
10036 // For backward compatibility
10037 if ($modulepart == 'product') {
10038 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10039 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10040 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10041 }
10042 }
10043 if ($modulepart == 'category') {
10044 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10045 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10046 }
10047
10048 // Defined relative dir to DOL_DATA_ROOT
10049 $relativedir = '';
10050 if ($dir) {
10051 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10052 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10053 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10054 }
10055
10056 $dirthumb = $dir.'thumbs/';
10057 $pdirthumb = $pdir.'thumbs/';
10058
10059 $return = '<!-- Photo -->'."\n";
10060 $nbphoto = 0;
10061
10062 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10063
10064 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10065 {
10066 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10067 $filearray=array_merge($filearray, $filearrayold);
10068 }*/
10069
10070 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10071 '@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';
10072
10073 if (count($filearray)) {
10074 if ($sortfield && $sortorder) {
10075 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10076 }
10077
10078 foreach ($filearray as $key => $val) {
10079 $photo = '';
10080 $file = $val['name'];
10081
10082 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10083 if (image_format_supported($file) >= 0) {
10084 $nbphoto++;
10085 $photo = $file;
10086 $viewfilename = $file;
10087
10088 if ($size == 1 || $size == 'small') { // Format vignette
10089 // Find name of thumb file
10090 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10091 if (!dol_is_file($dirthumb.$photo_vignette)) {
10092 // The thumb does not exists, so we will use the original file
10093 $dirthumb = $dir;
10094 $pdirthumb = $pdir;
10095 $photo_vignette = basename($file);
10096 }
10097
10098 // Get filesize of original file
10099 $imgarray = dol_getImageSize($dir.$photo);
10100
10101 if ($nbbyrow > 0) {
10102 if ($nbphoto == 1) {
10103 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10104 }
10105
10106 if ($nbphoto % $nbbyrow == 1) {
10107 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10108 }
10109 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10110 } elseif ($nbbyrow < 0) {
10111 $return .= '<div class="inline-block">'."\n";
10112 }
10113
10114 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10115 if (empty($nolink)) {
10116 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10117 if ($urladvanced) {
10118 $return .= '<a href="'.$urladvanced.'">';
10119 } else {
10120 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10121 }
10122 }
10123
10124 // Show image (width height=$maxHeight)
10125 // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
10126 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10127 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10128 if ($overwritetitle) {
10129 if (is_numeric($overwritetitle)) {
10130 $alt = '';
10131 } else {
10132 $alt = $overwritetitle;
10133 }
10134 }
10135 if (empty($cache) && !empty($val['label'])) {
10136 // label is md5 of file
10137 // use it in url to say we want to cache this version of the file
10138 $cache = $val['label'];
10139 }
10140 if ($usesharelink) {
10141 if (array_key_exists('share', $val) && $val['share']) {
10142 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10143 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10144 $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).'">';
10145 } else {
10146 $return .= '<!-- Show original file -->';
10147 $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).'">';
10148 }
10149 } else {
10150 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10151 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10152 }
10153 } else {
10154 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10155 $return .= '<!-- Show thumb -->';
10156 $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).'">';
10157 } else {
10158 $return .= '<!-- Show original file -->';
10159 $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).'">';
10160 }
10161 }
10162
10163 if (empty($nolink)) {
10164 $return .= '</a>';
10165 }
10166
10167 if ($showfilename) {
10168 $return .= '<br>'.$viewfilename;
10169 }
10170 if ($showaction) {
10171 $return .= '<br>';
10172 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10173 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10174 $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>';
10175 }
10176 // Special case for product
10177 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10178 // Link to resize
10179 $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; ';
10180
10181 // Link to delete
10182 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10183 $return .= img_delete().'</a>';
10184 }
10185 }
10186 $return .= "\n";
10187
10188 if ($nbbyrow > 0) {
10189 $return .= '</td>';
10190 if (($nbphoto % $nbbyrow) == 0) {
10191 $return .= '</tr>';
10192 }
10193 } elseif ($nbbyrow < 0) {
10194 $return .= '</div>'."\n";
10195 }
10196 }
10197
10198 if (empty($size)) { // Format origine
10199 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10200
10201 if ($showfilename) {
10202 $return .= '<br>'.$viewfilename;
10203 }
10204 if ($showaction) {
10205 // Special case for product
10206 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10207 // Link to resize
10208 $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; ';
10209
10210 // Link to delete
10211 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10212 $return .= img_delete().'</a>';
10213 }
10214 }
10215 }
10216
10217 // On continue ou on arrete de boucler ?
10218 if ($nbmax && $nbphoto >= $nbmax) {
10219 break;
10220 }
10221 }
10222 }
10223
10224 if ($size == 1 || $size == 'small') {
10225 if ($nbbyrow > 0) {
10226 // Ferme tableau
10227 while ($nbphoto % $nbbyrow) {
10228 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10229 $nbphoto++;
10230 }
10231
10232 if ($nbphoto) {
10233 $return .= '</table>';
10234 }
10235 }
10236 }
10237 }
10238
10239 $this->nbphoto = $nbphoto;
10240
10241 return $return;
10242 }
10243
10244
10251 protected function isArray($info)
10252 {
10253 if (is_array($info)) {
10254 if (isset($info['type']) && $info['type'] == 'array') {
10255 return true;
10256 } else {
10257 return false;
10258 }
10259 }
10260 return false;
10261 }
10262
10269 public function isDate($info)
10270 {
10271 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10272 return true;
10273 }
10274 return false;
10275 }
10276
10283 public function isDuration($info)
10284 {
10285 if (is_array($info)) {
10286 if (isset($info['type']) && ($info['type'] == 'duration')) {
10287 return true;
10288 } else {
10289 return false;
10290 }
10291 } else {
10292 return false;
10293 }
10294 }
10295
10302 public function isInt($info)
10303 {
10304 if (is_array($info)) {
10305 if (isset($info['type']) && (preg_match('/(^int|int$)/i', $info['type']))) {
10306 return true;
10307 } else {
10308 return false;
10309 }
10310 } else {
10311 return false;
10312 }
10313 }
10314
10321 public function isFloat($info)
10322 {
10323 if (is_array($info)) {
10324 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10325 return true;
10326 } else {
10327 return false;
10328 }
10329 }
10330 return false;
10331 }
10332
10339 public function isText($info)
10340 {
10341 if (is_array($info)) {
10342 if (isset($info['type']) && $info['type'] == 'text') {
10343 return true;
10344 } else {
10345 return false;
10346 }
10347 }
10348 return false;
10349 }
10350
10357 protected function canBeNull($info)
10358 {
10359 if (is_array($info)) {
10360 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10361 return true;
10362 } else {
10363 return false;
10364 }
10365 }
10366 return true;
10367 }
10368
10375 protected function isForcedToNullIfZero($info)
10376 {
10377 if (is_array($info)) {
10378 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10379 return true;
10380 } else {
10381 return false;
10382 }
10383 }
10384 return false;
10385 }
10386
10393 protected function isIndex($info)
10394 {
10395 if (is_array($info)) {
10396 if (array_key_exists('index', $info) && $info['index'] == true) {
10397 return true;
10398 } else {
10399 return false;
10400 }
10401 }
10402 return false;
10403 }
10404
10405
10414 protected function setSaveQuery()
10415 {
10416 global $conf;
10417
10418 $queryarray = array();
10419 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10420 // Depending on field type ('datetime', ...)
10421 if ($this->isDate($info)) {
10422 if (empty($this->{$field})) {
10423 $queryarray[$field] = null;
10424 } else {
10425 $queryarray[$field] = $this->db->idate($this->{$field});
10426 }
10427 } elseif ($this->isDuration($info)) {
10428 // $this->{$field} may be null, '', 0, '0', 123, '123'
10429 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10430 if (!isset($this->{$field})) {
10431 if (!empty($info['default'])) {
10432 $queryarray[$field] = $info['default'];
10433 } else {
10434 $queryarray[$field] = 0;
10435 }
10436 } else {
10437 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10438 }
10439 } else {
10440 $queryarray[$field] = null;
10441 }
10442 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10443 if ($field == 'entity' && is_null($this->{$field})) {
10444 $queryarray[$field] = ((int) $conf->entity);
10445 } else {
10446 // $this->{$field} may be null, '', 0, '0', 123, '123'
10447 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10448 if (!isset($this->{$field})) {
10449 $queryarray[$field] = 0;
10450 } elseif ($this->isInt($info)) {
10451 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10452 } elseif ($this->isFloat($info)) {
10453 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10454 }
10455 } else {
10456 $queryarray[$field] = null;
10457 }
10458 }
10459 } else {
10460 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10461 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10462 $queryarray[$field] = $this->{$field};
10463 }
10464
10465 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10466 unset($queryarray[$field]);
10467 }
10468 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10469 $queryarray[$field] = null; // May force 0 to null
10470 }
10471 }
10472
10473 return $queryarray;
10474 }
10475
10482 public function setVarsFromFetchObj(&$obj)
10483 {
10484 global $db;
10485
10486 foreach ($this->fields as $field => $info) {
10487 if ($this->isDate($info)) {
10488 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') {
10489 $this->$field = '';
10490 } else {
10491 $this->$field = $db->jdate($obj->$field);
10492 }
10493 } elseif ($this->isInt($info)) {
10494 if ($field == 'rowid') {
10495 $this->id = (int) $obj->$field;
10496 } else {
10497 if ($this->isForcedToNullIfZero($info)) {
10498 if (empty($obj->$field)) {
10499 $this->$field = null;
10500 } else {
10501 $this->$field = (int) $obj->$field;
10502 }
10503 } else {
10504 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10505 $this->$field = (int) $obj->$field;
10506 } else {
10507 $this->$field = null;
10508 }
10509 }
10510 }
10511 } elseif ($this->isFloat($info)) {
10512 if ($this->isForcedToNullIfZero($info)) {
10513 if (empty($obj->$field)) {
10514 $this->$field = null;
10515 } else {
10516 $this->$field = (float) $obj->$field;
10517 }
10518 } else {
10519 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10520 $this->$field = (float) $obj->$field;
10521 } else {
10522 $this->$field = null;
10523 }
10524 }
10525 } else {
10526 $this->$field = isset($obj->$field) ? $obj->$field : null;
10527 }
10528 }
10529
10530 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10531 if (!isset($this->fields['ref']) && isset($this->id)) {
10532 $this->ref = (string) $this->id;
10533 }
10534 }
10535
10540 public function emtpyObjectVars()
10541 {
10542 foreach ($this->fields as $field => $arr) {
10543 $this->$field = null;
10544 }
10545 }
10546
10554 public function getFieldList($alias = '', $excludefields = array())
10555 {
10556 $keys = array_keys($this->fields);
10557 if (!empty($alias)) {
10558 $keys_with_alias = array();
10559 foreach ($keys as $fieldname) {
10560 if (!empty($excludefields)) {
10561 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10562 continue;
10563 }
10564 }
10565 $keys_with_alias[] = $alias . '.' . $fieldname;
10566 }
10567 return implode(', ', $keys_with_alias);
10568 } else {
10569 return implode(', ', $keys);
10570 }
10571 }
10572
10580 protected function quote($value, $fieldsentry)
10581 {
10582 if (is_null($value)) {
10583 return 'NULL';
10584 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10585 return price2num((string) $value);
10586 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10587 return (int) $value;
10588 } elseif ($fieldsentry['type'] == 'boolean') {
10589 if ($value) {
10590 return 'true';
10591 } else {
10592 return 'false';
10593 }
10594 } else {
10595 return "'".$this->db->escape($value)."'";
10596 }
10597 }
10598
10599
10607 public function createCommon(User $user, $notrigger = 0)
10608 {
10609 global $conf;
10610 //global $langs; // Should be able to work with $langs loaded
10611
10612 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10613
10614 $error = 0;
10615
10616 $now = dol_now();
10617
10618 $fieldvalues = $this->setSaveQuery();
10619
10620 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10621
10622 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10623 $fieldvalues['date_creation'] = $this->db->idate($now);
10624 $this->date_creation = $this->db->idate($now);
10625 }
10626 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10627 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10628 $fieldvalues['fk_user_creat'] = $user->id;
10629 $this->fk_user_creat = $user->id;
10630 }
10631 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10632 $fieldvalues['user_creation_id'] = $user->id;
10633 $this->user_creation_id = $user->id;
10634 }
10635 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10636 // @phan-suppress-next-line PhanUndeclaredProperty
10637 $tmparray = dol_hash($this->pass, '0', 0, 1);
10638 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10639 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10640 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10641 }
10642 }
10643 if (array_key_exists('ref', $fieldvalues)) {
10644 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10645 }
10646 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
10647 $fieldvalues['tms'] = $this->db->idate($now);
10648 }
10649
10650 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10651
10652 $keys = array();
10653 $values = array(); // Array to store string forged for SQL syntax
10654 foreach ($fieldvalues as $k => $v) {
10655 $keys[$k] = $k;
10656 $value = $this->fields[$k];
10657 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10658 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10659 }
10660
10661 // Clean and check mandatory
10662 foreach ($keys as $key) {
10663 if (!isset($this->fields[$key])) {
10664 continue;
10665 }
10666 $key_fields = $this->fields[$key];
10667
10668 // If field is an implicit foreign key field (so type = 'integer:...')
10669 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10670 $values[$key] = '';
10671 }
10672 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10673 $values[$key] = '';
10674 }
10675
10676 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10677 $error++;
10678 global $langs;
10679 if (empty($langs)) {
10680 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10681 $langs = new Translate('', $conf);
10682 $langs->setDefaultLang();
10683 $langs->load("errors");
10684 }
10685 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10686 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10687 }
10688
10689 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10690 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'])) {
10691 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10692 }
10693
10694 // If field is an implicit foreign key field (so type = 'integer:...')
10695 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10696 if (isset($key_fields['default'])) {
10697 $values[$key] = ((int) $key_fields['default']);
10698 } else {
10699 $values[$key] = 'null';
10700 }
10701 }
10702 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10703 $values[$key] = 'null';
10704 }
10705 }
10706
10707 if ($error) {
10708 return -1;
10709 }
10710
10711 $this->db->begin();
10712
10713 if (!$error) {
10714 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10715 $sql .= " (".implode(", ", $keys).')';
10716 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10717
10718 $res = $this->db->query($sql);
10719 if (!$res) {
10720 $error++;
10721 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10722 $this->errors[] = "ErrorRefAlreadyExists";
10723 } else {
10724 $this->errors[] = $this->db->lasterror();
10725 }
10726 }
10727 }
10728
10729 if (!$error) {
10730 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10731 }
10732
10733 // If we have a field ref with a default value of (PROV)
10734 if (!$error) {
10735 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10736 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)') {
10737 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10738 $resqlupdate = $this->db->query($sql);
10739
10740 if ($resqlupdate === false) {
10741 $error++;
10742 $this->errors[] = $this->db->lasterror();
10743 } else {
10744 $this->ref = '(PROV'.$this->id.')';
10745 }
10746 }
10747 }
10748
10749 // Create extrafields
10750 if (!$error) {
10751 $result = $this->insertExtraFields();
10752 if ($result < 0) {
10753 $error++;
10754 }
10755 }
10756
10757 // Create lines
10758 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10759 foreach ($this->lines as $line) {
10760 $keyforparent = $this->fk_element;
10761 $line->$keyforparent = $this->id;
10762
10763 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10764 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10765 if (!is_object($line)) {
10766 $line = (object) $line;
10767 }
10768
10769 $result = 0;
10770 if (method_exists($line, 'insert')) {
10771 $result = $line->insert($user, 1);
10772 } elseif (method_exists($line, 'create')) {
10773 $result = $line->create($user, 1);
10774 }
10775 if ($result < 0) {
10776 $this->error = $line->error;
10777 $this->db->rollback();
10778 return -1;
10779 }
10780 }
10781 }
10782
10783 // Triggers
10784 if (!$error && !$notrigger) {
10785 // Call triggers
10786 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10787 if ($result < 0) {
10788 $error++;
10789 }
10790 // End call triggers
10791 }
10792
10793 // Commit or rollback
10794 if ($error) {
10795 $this->db->rollback();
10796 return -1;
10797 } else {
10798 $this->db->commit();
10799 return $this->id;
10800 }
10801 }
10802
10803
10813 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
10814 {
10815 if (empty($id) && empty($ref) && empty($morewhere)) {
10816 return -1;
10817 }
10818
10819 $fieldlist = $this->getFieldList('t');
10820 if (empty($fieldlist)) {
10821 return 0;
10822 }
10823
10824 $sql = "SELECT ".$fieldlist;
10825 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
10826
10827 if (!empty($id)) {
10828 $sql .= ' WHERE t.rowid = '.((int) $id);
10829 } elseif (!empty($ref)) {
10830 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
10831 } else {
10832 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
10833 }
10834 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
10835 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
10836 }
10837 if ($morewhere) {
10838 $sql .= $morewhere;
10839 }
10840 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
10841
10842 $res = $this->db->query($sql);
10843 if ($res) {
10844 $obj = $this->db->fetch_object($res);
10845 if ($obj) {
10846 $this->setVarsFromFetchObj($obj);
10847
10848 // Retrieve all extrafield
10849 // fetch optionals attributes and labels
10850 if (empty($noextrafields)) {
10851 $result = $this->fetch_optionals();
10852 if ($result < 0) {
10853 $this->error = $this->db->lasterror();
10854 $this->errors[] = $this->error;
10855 return -4;
10856 }
10857 }
10858
10859 return $this->id;
10860 } else {
10861 return 0;
10862 }
10863 } else {
10864 $this->error = $this->db->lasterror();
10865 $this->errors[] = $this->error;
10866 return -1;
10867 }
10868 }
10869
10877 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
10878 {
10879 $objectlineclassname = get_class($this).'Line';
10880 if (!class_exists($objectlineclassname)) {
10881 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
10882 return -1;
10883 }
10884
10885 $objectline = new $objectlineclassname($this->db);
10886 '@phan-var-force CommonObjectLine $objectline';
10887
10888 $sql = "SELECT ".$objectline->getFieldList('l');
10889 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
10890 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
10891 if ($morewhere) {
10892 $sql .= $morewhere;
10893 }
10894 if (isset($objectline->fields['position'])) {
10895 $sql .= $this->db->order('position', 'ASC');
10896 }
10897
10898 $resql = $this->db->query($sql);
10899 if ($resql) {
10900 $num_rows = $this->db->num_rows($resql);
10901 $i = 0;
10902 $this->lines = array();
10903 while ($i < $num_rows) {
10904 $obj = $this->db->fetch_object($resql);
10905 if ($obj) {
10906 $newline = new $objectlineclassname($this->db);
10907 '@phan-var-force CommonObjectLine $newline';
10908 $newline->setVarsFromFetchObj($obj);
10909
10910 // Load also extrafields for the line
10911 if (empty($noextrafields)) {
10912 $newline->fetch_optionals();
10913 }
10914
10915 $this->lines[] = $newline;
10916 }
10917 $i++;
10918 }
10919
10920 return 1;
10921 } else {
10922 $this->error = $this->db->lasterror();
10923 $this->errors[] = $this->error;
10924 return -1;
10925 }
10926 }
10927
10935 public function updateCommon(User $user, $notrigger = 0)
10936 {
10937 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
10938
10939 $error = 0;
10940
10941 $now = dol_now();
10942
10943 // $this->oldcopy should have been set by the caller of update
10944 //if (empty($this->oldcopy)) {
10945 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
10946 // $this->oldcopy = dol_clone($this, 2);
10947 //}
10948
10949 $fieldvalues = $this->setSaveQuery();
10950
10951 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10952
10953 if (array_key_exists('date_modification', $fieldvalues)) {
10954 $fieldvalues['date_modification'] = $this->db->idate($now);
10955 }
10956 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
10957 $fieldvalues['tms'] = $this->db->idate($now);
10958 }
10959 if (array_key_exists('fk_user_modif', $fieldvalues)) {
10960 $fieldvalues['fk_user_modif'] = $user->id;
10961 }
10962 if (array_key_exists('user_modification_id', $fieldvalues)) {
10963 $fieldvalues['user_modification_id'] = $user->id;
10964 }
10965 // @phan-suppress-next-line PhanUndeclaredProperty
10966 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
10967 // @phan-suppress-next-line PhanUndeclaredProperty
10968 $tmparray = dol_hash($this->pass, '0', 0, 1);
10969 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10970 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10971 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10972 }
10973 }
10974 if (array_key_exists('ref', $fieldvalues)) {
10975 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10976 }
10977
10978 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
10979
10980 // Add quotes and escape on fields with type string
10981 $keys = array();
10982 $values = array();
10983 $tmp = array();
10984 foreach ($fieldvalues as $k => $v) {
10985 $keys[$k] = $k;
10986 $value = $this->fields[$k];
10987 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10988 $values[$k] = $this->quote($v, $value);
10989 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
10990 // Clean values for text with selectbox
10991 $v = preg_replace('/\s/', ',', $v);
10992 $v = preg_replace('/,+/', ',', $v);
10993 }
10994 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10995 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
10996 }
10997
10998 // Clean and check mandatory fields
10999 foreach ($keys as $key) {
11000 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11001 $values[$key] = ''; // This is an implicit foreign key field
11002 }
11003 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11004 $values[$key] = ''; // This is an explicit foreign key field
11005 }
11006
11007 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11008 /*
11009 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11010 {
11011 $error++;
11012 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11013 }*/
11014 }
11015
11016 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
11017
11018 $this->db->begin();
11019
11020 if (!$error) {
11021 $res = $this->db->query($sql);
11022 if (!$res) {
11023 $error++;
11024 $this->errors[] = $this->db->lasterror();
11025 }
11026 }
11027
11028 // Update extrafield
11029 if (!$error) {
11030 $result = $this->insertExtraFields(); // This update extrafields
11031 if ($result < 0) {
11032 $error++;
11033 }
11034 }
11035
11036 // Triggers
11037 if (!$error && !$notrigger) {
11038 // Call triggers
11039 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11040 if ($result < 0) {
11041 $error++;
11042 } //Do also here what you must do to rollback action if trigger fail
11043 // End call triggers
11044 }
11045
11046 // Commit or rollback
11047 if ($error) {
11048 $this->db->rollback();
11049 return -1;
11050 } else {
11051 $this->db->commit();
11052 return $this->id;
11053 }
11054 }
11055
11064 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11065 {
11066 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11067
11068 $error = 0;
11069
11070 $this->db->begin();
11071
11072 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11073 foreach ($this->childtables as $table) {
11074 $sql = "DELETE FROM ".$this->db->prefix().$table." WHERE ".$this->fk_element." = ".((int) $this->id);
11075 $resql = $this->db->query($sql);
11076 if (!$resql) {
11077 $this->error = $this->db->lasterror();
11078 $this->errors[] = $this->error;
11079 $this->db->rollback();
11080 return -1;
11081 }
11082 }
11083 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11084 $objectisused = $this->isObjectUsed($this->id);
11085 if (!empty($objectisused)) {
11086 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11087 $this->error = 'ErrorRecordHasChildren';
11088 $this->errors[] = $this->error;
11089 $this->db->rollback();
11090 return 0;
11091 }
11092 }
11093
11094 // Delete cascade first
11095 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11096 foreach ($this->childtablesoncascade as $tabletodelete) {
11097 $deleteFromObject = explode(':', $tabletodelete, 4);
11098 if (count($deleteFromObject) >= 2) {
11099 $className = str_replace('@', '', $deleteFromObject[0]);
11100 $filePath = $deleteFromObject[1];
11101 $columnName = $deleteFromObject[2];
11102 $filter = '';
11103 if (!empty($deleteFromObject[3])) {
11104 $filter = $deleteFromObject[3];
11105 }
11106
11107 if (dol_include_once($filePath)) {
11108 $childObject = new $className($this->db);
11109 if (method_exists($childObject, 'deleteByParentField')) {
11110 '@phan-var-force CommonObject $childObject';
11111 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11112 if ($result < 0) {
11113 $error++;
11114 $this->errors[] = $childObject->error;
11115 break;
11116 }
11117 } else {
11118 $error++;
11119 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11120 break;
11121 }
11122 } else {
11123 $error++;
11124 $this->errors[] = 'Cannot include child class file '.$filePath;
11125 break;
11126 }
11127 } else {
11128 // Delete record in child table
11129 $sql = "DELETE FROM ".$this->db->prefix().$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
11130
11131 $resql = $this->db->query($sql);
11132 if (!$resql) {
11133 $error++;
11134 $this->error = $this->db->lasterror();
11135 $this->errors[] = $this->error;
11136 break;
11137 }
11138 }
11139 }
11140 }
11141
11142 if (!$error) {
11143 if (!$notrigger) {
11144 // Call triggers
11145 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11146 if ($result < 0) {
11147 $error++;
11148 } // Do also here what you must do to rollback action if trigger fail
11149 // End call triggers
11150 }
11151 }
11152
11153 // Delete llx_ecm_files
11154 if (!$error) {
11155 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11156 if (!$res) {
11157 $error++;
11158 }
11159 }
11160
11161 if (!$error) {
11162 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11163 // For remove dir
11164 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11165 if (dol_is_dir($dir)) {
11166 if (!dol_delete_dir_recursive($dir)) {
11167 $this->errors[] = 'ErrorFailToDeleteDir';
11168 }
11169 }
11170 }
11171
11172 // Delete linked object
11173 $res = $this->deleteObjectLinked();
11174 if ($res < 0) {
11175 $error++;
11176 }
11177
11178 if (!$error && !empty($this->isextrafieldmanaged)) {
11179 $result = $this->deleteExtraFields();
11180 if ($result < 0) {
11181 $error++;
11182 }
11183 }
11184
11185 if (!$error) {
11186 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11187
11188 $resql = $this->db->query($sql);
11189 if (!$resql) {
11190 $error++;
11191 $this->errors[] = $this->db->lasterror();
11192 }
11193 }
11194
11195 // Commit or rollback
11196 if ($error) {
11197 $this->db->rollback();
11198 return -1;
11199 } else {
11200 $this->db->commit();
11201 return 1;
11202 }
11203 }
11204
11216 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11217 {
11218 global $user;
11219
11220 $error = 0;
11221 $deleted = 0;
11222
11223 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11224
11225 if (!empty($parentId) && !empty($parentField)) {
11226 if (empty($this->table_element)) {
11227 $this->error = 'Property table_element for object is not defined';
11228 $this->errors[] = $this->error;
11229 $error++;
11230 return -1;
11231 }
11232 if (!method_exists($this, 'fetch')) {
11233 $this->error = 'Method fetch for object is not defined';
11234 $this->errors[] = $this->error;
11235 $error++;
11236 return -1;
11237 }
11238 if (!method_exists($this, 'delete')) {
11239 $this->error = 'Method delete for object is not defined';
11240 $this->errors[] = $this->error;
11241 $error++;
11242 return -1;
11243 }
11244
11245 $this->db->begin();
11246
11247 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11248 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11249
11250 // Manage filter
11251 $errormessage = '';
11252 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11253 if ($errormessage) {
11254 $this->errors[] = $errormessage;
11255 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11256 return -1;
11257 }
11258
11259 $resql = $this->db->query($sql);
11260 if (!$resql) {
11261 $this->errors[] = $this->db->lasterror();
11262 $error++;
11263 } else {
11264 while ($obj = $this->db->fetch_object($resql)) {
11265 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11266 if ($result < 0) {
11267 $error++;
11268 $this->errors[] = $this->error;
11269 } else {
11270 $result = $this->delete($user); // @phpstan-ignore-line
11271 if ($result < 0) {
11272 $error++;
11273 $this->errors[] = $this->error;
11274 } else {
11275 $deleted++;
11276 }
11277 }
11278 }
11279 }
11280
11281 if (empty($error)) {
11282 $this->db->commit();
11283 return $deleted;
11284 } else {
11285 $this->error = implode(', ', $this->errors);
11286 $this->db->rollback();
11287 return $error * -1;
11288 }
11289 }
11290
11291 return $deleted;
11292 }
11293
11302 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11303 {
11304 $error = 0;
11305
11306 $tmpforobjectclass = get_class($this);
11307 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11308
11309 $this->db->begin();
11310
11311 // Call trigger
11312 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11313 if ($result < 0) {
11314 $error++;
11315 }
11316 // End call triggers
11317
11318 if (empty($error)) {
11319 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11320 $sql .= " WHERE rowid = ".((int) $idline);
11321
11322 $resql = $this->db->query($sql);
11323 if (!$resql) {
11324 $this->error = "Error ".$this->db->lasterror();
11325 $error++;
11326 }
11327 }
11328
11329 if (empty($error)) {
11330 // Remove extrafields
11331 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11332 '@phan-var-force CommonObjectLine $tmpobjectline';
11333 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11334 $tmpobjectline->id = $idline;
11335 $result = $tmpobjectline->deleteExtraFields();
11336 if ($result < 0) {
11337 $error++;
11338 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11339 }
11340 }
11341 }
11342
11343 if (empty($error)) {
11344 $this->db->commit();
11345 return 1;
11346 } else {
11347 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11348 $this->db->rollback();
11349 return -1;
11350 }
11351 }
11352
11353
11364 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11365 {
11366 $error = 0;
11367
11368 $this->db->begin();
11369
11370 $statusfield = 'status';
11371 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11372 $statusfield = 'fk_statut';
11373 }
11374
11375 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11376 $sql .= " SET ".$statusfield." = ".((int) $status);
11377 $sql .= " WHERE rowid = ".((int) $this->id);
11378
11379 if ($this->db->query($sql)) {
11380 if (!$error) {
11381 $this->oldcopy = clone $this;
11382 }
11383
11384 if (!$error && !$notrigger) {
11385 // Call trigger
11386 $result = $this->call_trigger($triggercode, $user);
11387 if ($result < 0) {
11388 $error++;
11389 }
11390 }
11391
11392 if (!$error) {
11393 $this->status = $status;
11394 if (property_exists($this, 'statut')) { // For backward compatibility
11395 $this->statut = $status;
11396 }
11397 $this->db->commit();
11398 return 1;
11399 } else {
11400 $this->db->rollback();
11401 return -1;
11402 }
11403 } else {
11404 $this->error = $this->db->error();
11405 $this->db->rollback();
11406 return -1;
11407 }
11408 }
11409
11416 public function initAsSpecimenCommon()
11417 {
11418 global $user;
11419
11420 $this->id = 0;
11421 $this->specimen = 1;
11422 $fields = array(
11423 'label' => 'This is label',
11424 'ref' => 'ABCD1234',
11425 'description' => 'This is a description',
11426 'qty' => 123.12,
11427 'note_public' => 'Public note',
11428 'note_private' => 'Private note',
11429 'date_creation' => (dol_now() - 3600 * 48),
11430 'date_modification' => (dol_now() - 3600 * 24),
11431 'fk_user_creat' => $user->id,
11432 'fk_user_modif' => $user->id,
11433 'date' => dol_now(),
11434 );
11435 foreach ($fields as $key => $value) {
11436 if (array_key_exists($key, $this->fields)) {
11437 $this->{$key} = $value; // @phpstan-ignore-line
11438 }
11439 }
11440
11441 // Force values to default values when known
11442 if (property_exists($this, 'fields')) {
11443 foreach ($this->fields as $key => $value) {
11444 // If fields are already set, do nothing
11445 if (array_key_exists($key, $fields)) {
11446 continue;
11447 }
11448
11449 if (!empty($value['default'])) {
11450 $this->$key = $value['default'];
11451 }
11452 }
11453 }
11454
11455 return 1;
11456 }
11457
11458
11459 /* Part for comments */
11460
11466 public function fetchComments()
11467 {
11468 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11469
11470 $comment = new Comment($this->db);
11471 $result = $comment->fetchAllFor($this->element, $this->id);
11472 if ($result < 0) {
11473 $this->setErrorsFromObject($comment);
11474 return -1;
11475 } else {
11476 $this->comments = $comment->comments;
11477 }
11478 return count($this->comments);
11479 }
11480
11486 public function getNbComments()
11487 {
11488 return count($this->comments);
11489 }
11490
11497 public function trimParameters($parameters)
11498 {
11499 if (!is_array($parameters)) {
11500 return;
11501 }
11502 foreach ($parameters as $parameter) {
11503 if (isset($this->$parameter)) {
11504 $this->$parameter = trim($this->$parameter);
11505 }
11506 }
11507 }
11508
11509 /* Part for categories/tags */
11510
11521 public function getCategoriesCommon($type_categ)
11522 {
11523 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11524
11525 // Get current categories
11526 $c = new Categorie($this->db);
11527 $existing = $c->containing($this->id, $type_categ, 'id');
11528
11529 return $existing;
11530 }
11531
11544 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11545 {
11546 // Handle single category
11547 if (!is_array($categories)) {
11548 $categories = array($categories);
11549 }
11550
11551 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11552
11553 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11554
11555 if (empty($type_categ)) {
11556 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11557 return -1;
11558 }
11559
11560 // Get current categories
11561 $c = new Categorie($this->db);
11562 $existing = $c->containing($this->id, $type_categ, 'id');
11563 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11564 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11565 if (!is_array($existing)) {
11566 $existing = array();
11567 }
11568 if ($remove_existing) {
11569 // Diff
11570 $to_del = array_diff($existing, $categories);
11571 $to_add = array_diff($categories, $existing);
11572 } else {
11573 $to_del = array(); // Nothing to delete
11574 $to_add = array_diff($categories, $existing);
11575 }
11576
11577 $error = 0;
11578 $ok = 0;
11579
11580 // Process
11581 foreach ($to_del as $del) {
11582 if ($c->fetch($del) > 0) {
11583 $result = $c->del_type($this, $type_categ);
11584 if ($result < 0) {
11585 $error++;
11586 $this->error = $c->error;
11587 $this->errors = $c->errors;
11588 break;
11589 } else {
11590 $ok += $result;
11591 }
11592 }
11593 }
11594 foreach ($to_add as $add) {
11595 if ($c->fetch($add) > 0) {
11596 $result = $c->add_type($this, $type_categ);
11597 if ($result < 0) {
11598 $error++;
11599 $this->error = $c->error;
11600 $this->errors = $c->errors;
11601 break;
11602 } else {
11603 $ok += $result;
11604 }
11605 }
11606 }
11607
11608 return $error ? (-1 * $error) : $ok;
11609 }
11610
11619 public function cloneCategories($fromId, $toId, $type = '')
11620 {
11621 $this->db->begin();
11622
11623 if (empty($type)) {
11624 $type = $this->table_element;
11625 }
11626
11627 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11628 $categorystatic = new Categorie($this->db);
11629
11630 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11631 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11632
11633 $sql = "INSERT INTO ".$tablename." (fk_categorie, ".$fkname.")";
11634 $sql .= " SELECT fk_categorie, $toId FROM ".$tablename;
11635 $sql .= " WHERE ".$fkname." = ".((int) $fromId);
11636
11637 if (!$this->db->query($sql)) {
11638 $this->error = $this->db->lasterror();
11639 $this->db->rollback();
11640 return -1;
11641 }
11642
11643 $this->db->commit();
11644 return 1;
11645 }
11646
11653 public function deleteEcmFiles($mode = 0)
11654 {
11655 global $conf;
11656
11657 $this->db->begin();
11658
11659 // Delete in database with mode 0
11660 if ($mode == 0) {
11661 switch ($this->element) {
11662 case 'propal':
11663 $element = 'propale';
11664 break;
11665 case 'product':
11666 $element = 'produit';
11667 break;
11668 case 'order_supplier':
11669 $element = 'fournisseur/commande';
11670 break;
11671 case 'invoice_supplier':
11672 // Special cases that need to use get_exdir to get real dir of object
11673 // In future, all object should use this to define path of documents.
11674 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11675 break;
11676 case 'shipping':
11677 $element = 'expedition/sending';
11678 break;
11679 case 'task':
11680 case 'project_task':
11681 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11682
11683 $project_result = $this->fetchProject();
11684 if ($project_result >= 0) {
11685 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11686 }
11687 // no break
11688 case 'contrat':
11689 $element = 'contract';
11690 break;
11691 default:
11692 $element = $this->element;
11693 }
11694 '@phan-var-force string $element';
11695
11696 // Delete ecm_files_extrafields with mode 0 (using name)
11697 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11698 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11699 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11700 $sql .= ")";
11701
11702 if (!$this->db->query($sql)) {
11703 $this->error = $this->db->lasterror();
11704 $this->db->rollback();
11705 return false;
11706 }
11707
11708 // Delete ecm_files with mode 0 (using name)
11709 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11710 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11711 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11712
11713 if (!$this->db->query($sql)) {
11714 $this->error = $this->db->lasterror();
11715 $this->db->rollback();
11716 return false;
11717 }
11718 }
11719
11720 // Delete in database with mode 1
11721 if ($mode == 1) {
11722 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11723 $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).")";
11724 $resql = $this->db->query($sql);
11725 if (!$resql) {
11726 $this->error = $this->db->lasterror();
11727 $this->db->rollback();
11728 return false;
11729 }
11730
11731 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11732 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11733 $resql = $this->db->query($sql);
11734 if (!$resql) {
11735 $this->error = $this->db->lasterror();
11736 $this->db->rollback();
11737 return false;
11738 }
11739 }
11740
11741 $this->db->commit();
11742 return true;
11743 }
11744
11753 public function checkActiveProductInLines($status = 'onsale')
11754 {
11755 global $langs;
11756
11757 if (isModEnabled('product') || isModEnabled('service')) {
11758 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
11759
11760 $ret = true;
11761 $tmpproduct = new Product($this->db);
11762 foreach ($this->lines as $line) {
11763 if ($line->fk_product > 0) {
11764 $tmpproduct->fetch($line->fk_product);
11765 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
11766 if (!$tmpproduct->$statustotest) {
11767 $langs->load('products');
11768 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
11769 $ret = false;
11770 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
11771 break;
11772 }
11773 }
11774 }
11775 if (!$ret) {
11776 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
11777 }
11778 return $ret;
11779 } else {
11780 return true;
11781 }
11782 }
11783
11790 public function setErrorWithoutLog($message)
11791 {
11792 $this->error = $message;
11793 $this->errors[] = $message;
11794 }
11795
11803 public function setErrorWithLog($message, $loglevel = LOG_ERR)
11804 {
11805 $this->setErrorWithoutLog($message);
11806 dol_syslog($message, $loglevel);
11807 }
11808}
$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...
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, ....
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=1, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
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.