dolibarr 24.0.1
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-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
11 * Copyright (C) 2016 Bahfir abbes <bafbes@gmail.com>
12 * Copyright (C) 2017 ATM Consulting <support@atm-consulting.fr>
13 * Copyright (C) 2017-2026 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2017 Rui Strecht <rui.strecht@aliartalentos.com>
15 * Copyright (C) 2018-2026 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-2026 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 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
25*
26 * This program is free software; you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation; either version 3 of the License, or
29 * (at your option) any later version.
30 *
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
35 *
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <https://www.gnu.org/licenses/>.
38 */
39
46require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
47require_once DOL_DOCUMENT_ROOT.'/core/class/commontrigger.class.php';
48
54abstract class CommonObject
55{
56 use DolDeprecationHandler;
57 use CommonTrigger;
58
62 public $module;
63
67 public $db;
68
72 public $id;
73
77 public $entity;
78
83 public $error;
84
88 public $errorhidden;
89
93 public $errors = array();
94
99 public $warning;
100
104 public $warnings = array();
105
109 private $validateFieldsErrors = array();
110
114 public $element;
115
120 public $fk_element;
121
127 public $element_for_permission;
128
132 public $table_element;
133
137 public $table_rowid;
138
142 public $table_element_line = '';
143
148 public $ismultientitymanaged;
149
153 public $import_key;
154
158 public $array_options = array();
159
160
239 public $fields = array();
240
246 public $array_languages = null; // Value is array() when load already tried
247
251 public $contacts_ids;
252
256 public $contacts_ids_internal;
257
261 public $linked_objects;
262
266 public $linkedObjectsIds;
267
271 public $linkedObjects;
272
276 private $linkedObjectsFullLoaded = array();
277
281 public $oldcopy;
282
286 public $oldref;
287
291 protected $table_ref_field = '';
292
296 public $restrictiononfksoc = 0;
297
298
299 // The following vars are used by some objects only.
300 // We keep these properties in CommonObject in order to provide common methods using them.
301
305 public $context = array();
306
310 public $actionmsg;
314 public $actionmsg2;
315
319 public $canvas;
320
325 public $project;
326
331 public $fk_project;
332
338 public $fk_projet;
339
344 public $contact;
345
350 public $contact_id;
351
356 public $thirdparty;
357
362 public $user;
363
368 public $product;
369
374 public $origin_type;
375
380 public $origin_id;
381
386
392 public $origin;
393
402 private $expedition;
403
409 private $livraison;
410
416 private $commandeFournisseur;
417
418
422 public $ref;
423
427 public $ref_ext;
428
432 public $ref_previous;
433
437 public $ref_next;
438
442 public $newref;
443
450 public $statut;
451
460 public $status;
461
466 public $country;
467
472 public $country_id;
473
478 public $country_code;
479
484 public $state;
485
490 public $state_id;
491
496 public $state_code;
497
502 public $region_id;
503
508 public $region_code;
509
514 public $region;
515
516
521 public $barcode_type;
522
527 public $barcode_type_code;
528
533 public $barcode_type_label;
534
539 public $barcode_type_coder;
540
545 public $mode_reglement_id;
546
551 public $cond_reglement_id;
552
556 public $demand_reason_id;
557
562 public $transport_mode_id;
563
571 private $cond_reglement; // Private to call DolDeprecationHandler
575 protected $depr_cond_reglement; // Internal value for deprecation
576
582 public $fk_delivery_address;
583
588 public $shipping_method_id;
589
594 public $shipping_method;
595
596 // Multicurrency
600 public $fk_multicurrency;
601
606 public $multicurrency_code;
607
612 public $multicurrency_tx;
613
617 public $multicurrency_total_ht;
618
622 public $multicurrency_total_tva;
623
627 public $multicurrency_total_localtax1; // not in database
628
632 public $multicurrency_total_localtax2; // not in database
633
637 public $multicurrency_total_ttc;
638
639
644 public $model_pdf;
645
650 public $last_main_doc;
651
657 public $fk_bank;
658
663 public $fk_account;
664
669 public $note_public;
670
675 public $note_private;
676
682 public $note;
683
688 public $total_ht;
689
694 public $total_tva;
695
700 public $total_localtax1;
701
706 public $total_localtax2;
707
712 public $total_ttc;
713
714
718 public $lines;
719
723 public $actiontypecode;
724
729 public $comments = array();
730
734 public $name;
735
739 public $lastname;
740
744 public $firstname;
745
750 public $civility_id;
751
755 public $civility_code;
756
757 // Dates
761 public $date_creation;
762
766 public $date_validation;
767
771 public $date_modification;
772
778 public $tms;
779
785 private $date_update;
786
790 public $date_cloture;
791
795 public $user_creation_id;
796
800 public $user_validation_id;
801
805 public $user_closing_id;
806
810 public $user_modification_id;
811
816 public $fk_user_creat;
817
822 public $fk_user_modif;
823
824
828 public $next_prev_filter;
829
833 public $specimen = 0;
834
838 public $sendtoid;
839
840
845 public $alreadypaid;
846
851 public $totalpaid;
852
857 public $totalpaid_multicurrency;
858
859
863 public $labelStatus = array();
864
868 public $labelStatusShort = array();
869
873 public $tpl;
874
875
879 public $showphoto_on_popup;
880
884 public $nb = array();
885
889 public $nbphoto;
890
894 public $output;
895
899 public $extraparams = array();
900
904 protected $childtables = array();
905
911 protected $childtablesoncascade = array();
912
916 public $cond_reglement_supplier_id;
917
923 public $deposit_percent;
924
925
929 public $warehouse_id;
930
934 public $isextrafieldmanaged = 0;
935
936
937 // No constructor as it is an abstract class
938
944 protected function deprecatedProperties()
945 {
946 return array(
947 'alreadypaid' => 'totalpaid',
948 'cond_reglement' => 'depr_cond_reglement',
949 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
950 'commandeFournisseur' => 'origin_object',
951 'expedition' => 'origin_object',
952 'fk_project' => 'fk_project',
953 'livraison' => 'origin_object',
954 'projet' => 'project',
955 'statut' => 'status',
956 );
957 }
958
959
970 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
971 {
972 global $db, $conf;
973
974 $sql = "SELECT rowid";
975 $sql .= " FROM ".$db->prefix().$element;
976 $sql .= " WHERE entity IN (".getEntity($element).")";
977
978 if ($id > 0) {
979 $sql .= " AND rowid = ".((int) $id);
980 } elseif ($ref) {
981 $sql .= " AND ref = '".$db->escape($ref)."'";
982 } elseif ($ref_ext) {
983 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
984 } else {
985 $error = 'ErrorWrongParameters';
986 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
987 return -1;
988 }
989 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
990 $sql .= " AND entity = ".((int) $conf->entity);
991 }
992
993 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
994 $resql = $db->query($sql);
995 if ($resql) {
996 $num = $db->num_rows($resql);
997 if ($num > 0) {
998 return 1;
999 } else {
1000 return 0;
1001 }
1002 }
1003 return -1;
1004 }
1005
1011 public function isEmpty()
1012 {
1013 return (empty($this->id));
1014 }
1015
1023 {
1024 if (!empty($object->error)) {
1025 $this->error = $object->error;
1026 }
1027 if (!empty($object->errors)) {
1028 $this->errors = array_merge($this->errors, $object->errors);
1029 }
1030 }
1031
1039 public function getTooltipContentArray($params)
1040 {
1041 return [];
1042 }
1043
1051 public function getTooltipContent($params)
1052 {
1053 global $action, $extrafields, $langs, $hookmanager;
1054
1055 // If there is too much extrafields, we do not include them into tooltip
1056 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
1057
1058 $data = $this->getTooltipContentArray($params);
1059 $count = 0;
1060
1061 // Add extrafields
1062 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1063 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1064 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1065 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1066 continue;
1067 }
1068
1069 if (empty($extrafields->attributes[$this->table_element]['showintooltip'][$key])) {
1070 continue;
1071 }
1072
1073
1074 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1075 $data['more_extrafields'] = '<br>...';
1076 break;
1077 }
1078 $enabled = 1;
1079 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1080 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1081 }
1082 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1083 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1084 }
1085 $perms = 1;
1086 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1087 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1088 }
1089 if (empty($enabled)) {
1090 continue; // 0 = Never visible field
1091 }
1092 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1093 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1094 }
1095 if (empty($perms)) {
1096 continue; // 0 = Not visible
1097 }
1098 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1099 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1100 }
1101 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1102 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1103 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1104 } else {
1105 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1106 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1107 $count++;
1108 }
1109 }
1110 $data['closedivextra'] = '</div>';
1111 }
1112
1113 $hookmanager->initHooks(array($this->element . 'dao'));
1114 $parameters = array(
1115 'tooltipcontentarray' => &$data,
1116 'params' => $params,
1117 );
1118 // Note that $action and $object may have been modified by some hooks
1119 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1120
1121 //var_dump($data);
1122 $label = implode($data);
1123
1124 return $label;
1125 }
1126
1127
1133 public function errorsToString()
1134 {
1135 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1136 }
1137
1138
1145 public function getFormatedCustomerRef($objref)
1146 {
1147 global $hookmanager;
1148
1149 $parameters = array('objref' => $objref);
1150 $action = '';
1151 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1152 if ($reshook > 0) {
1153 return $hookmanager->resArray['objref'];
1154 }
1155 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1156 }
1157
1164 public function getFormatedSupplierRef($objref)
1165 {
1166 global $hookmanager;
1167
1168 $parameters = array('objref' => $objref);
1169 $action = '';
1170 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1171 if ($reshook > 0) {
1172 return $hookmanager->resArray['objref'];
1173 }
1174 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1175 }
1176
1186 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1187 {
1188 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1189 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1190 $tmparray = getCountry($this->country_id, 'all');
1191 $this->country_code = $tmparray['code'];
1192 $this->country = $tmparray['label'];
1193 }
1194
1195 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1196 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1197 $tmparray = getState($this->state_id, 'all', null, 1);
1198 $this->state_code = $tmparray['code'];
1199 $this->state = $tmparray['label'];
1200 $this->region_code = $tmparray['region_code'];
1201 $this->region = $tmparray['region'];
1202 }
1203
1204 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1205 }
1206
1207
1216 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1217 {
1218 global $user, $dolibarr_main_url_root;
1219
1220 if (empty($this->last_main_doc)) {
1221 return ''; // No way to known which document name to use
1222 }
1223
1224 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1225 $ecmfile = new EcmFiles($this->db);
1226 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1227 if ($result < 0) {
1228 $this->error = $ecmfile->error;
1229 $this->errors = $ecmfile->errors;
1230 return -1;
1231 }
1232
1233 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1234 // Add entry into index
1235 if ($initsharekey) {
1236 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1237
1238 // 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
1239 /*
1240 $ecmfile->filepath = $rel_dir;
1241 $ecmfile->filename = $filename;
1242 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1243 $ecmfile->fullpath_orig = '';
1244 $ecmfile->gen_or_uploaded = 'generated';
1245 $ecmfile->description = ''; // indexed content
1246 $ecmfile->keywords = ''; // keyword content
1247 $ecmfile->share = getRandomPassword(true);
1248 $result = $ecmfile->create($user);
1249 if ($result < 0)
1250 {
1251 $this->error = $ecmfile->error;
1252 $this->errors = $ecmfile->errors;
1253 }
1254 */
1255 } else {
1256 return '';
1257 }
1258 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1259 // Add entry into index
1260 if ($initsharekey) {
1261 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1262 $ecmfile->share = getRandomPassword(true);
1263 $ecmfile->update($user);
1264 } else {
1265 return '';
1266 }
1267 }
1268 // Define $urlwithroot
1269 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1270 // This is to use external domain name found into config file
1271 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1272 //else
1273 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1274 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1275
1276 $forcedownload = 0;
1277
1278 $paramlink = '';
1279 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1280 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1281 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1282 if (!empty($ecmfile->share)) {
1283 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1284 }
1285 if ($forcedownload) {
1286 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1287 }
1288
1289 if ($relativelink) {
1290 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1291 } else {
1292 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1293 }
1294
1295 // Here $ecmfile->share is defined
1296 return $linktoreturn;
1297 }
1298
1299
1300 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1310 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1311 {
1312 // phpcs:enable
1313 global $user, $langs;
1314
1315 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1316
1317 // Check parameters
1318 if ($fk_socpeople <= 0) {
1319 $langs->load("errors");
1320 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1321 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1322 return -1;
1323 }
1324
1325 // Check that the contact actually exists in database
1326 $sql_check = "SELECT rowid FROM ".$this->db->prefix().($source == 'internal' ? "user" : "socpeople")." WHERE rowid = ".((int) $fk_socpeople);
1327 $resql_check = $this->db->query($sql_check);
1328 if ($resql_check) {
1329 if (!$this->db->num_rows($resql_check)) {
1330 $langs->load("errors");
1331 $this->error = $langs->trans("ErrorRecordNotFound");
1332 dol_syslog(get_class($this)."::add_contact Contact/user id ".$fk_socpeople." does not exist", LOG_ERR);
1333 return -1;
1334 }
1335 } else {
1336 $this->error = $this->db->lasterror();
1337 return -1;
1338 }
1339
1340 if (!$type_contact) {
1341 $langs->load("errors");
1342 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1343 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1344 return -2;
1345 }
1346
1347 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && $user->id > 0 && !$user->hasRight('societe', 'client', 'voir')) {
1348 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1349 $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid;
1350 $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id;
1351
1352 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1353
1354 if (!$resql_allowed_contacts) {
1355 $this->errors[] = $this->db->lasterror();
1356 return -3;
1357 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1358 if ($obj->cnt == 0) {
1359 $langs->load("companies");
1360 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1361 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1362 return -4;
1363 }
1364 }
1365 }
1366
1367 $id_type_contact = 0;
1368 if (is_numeric($type_contact)) {
1369 $id_type_contact = $type_contact;
1370 } else {
1371 // We look for id type_contact
1372 $sql = "SELECT tc.rowid";
1373 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1374 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1375 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1376 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1377 //print $sql;
1378 $resql = $this->db->query($sql);
1379 if ($resql) {
1380 $obj = $this->db->fetch_object($resql);
1381 if ($obj) {
1382 $id_type_contact = $obj->rowid;
1383 }
1384 }
1385 }
1386 if ($id_type_contact == 0) {
1387 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");
1388 return 0;
1389 }
1390
1391 $datecreate = dol_now();
1392
1393 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1394 $TListeContacts = $this->liste_contact(-1, $source);
1395 $already_added = false;
1396 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1397 foreach ($TListeContacts as $array_contact) {
1398 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1399 $already_added = true;
1400 break;
1401 }
1402 }
1403 }
1404
1405 if (!$already_added) {
1406 $this->db->begin();
1407
1408 // Insert into database
1409 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1410 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1411 $sql .= " VALUES (".((int) $this->id).", ".((int) $fk_socpeople)." , ";
1412 $sql .= "'".$this->db->idate($datecreate)."'";
1413 $sql .= ", 4, ".((int) $id_type_contact);
1414 $sql .= ")";
1415
1416 $resql = $this->db->query($sql);
1417 if ($resql) {
1418 if (!$notrigger) {
1419 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1420 $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user);
1421 if ($result < 0) {
1422 $this->db->rollback();
1423 return -5;
1424 }
1425 }
1426
1427 $this->db->commit();
1428 return 1;
1429 } else {
1430 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1431 $this->error = $this->db->errno();
1432 $this->db->rollback();
1433 return -6;
1434 } else {
1435 $this->error = $this->db->lasterror();
1436 $this->db->rollback();
1437 return -7;
1438 }
1439 }
1440 } else {
1441 return 0;
1442 }
1443 }
1444
1445 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1453 public function copy_linked_contact($objFrom, $source = 'internal')
1454 {
1455 // phpcs:enable
1456 $contacts = $objFrom->liste_contact(-1, $source);
1457 foreach ($contacts as $contact) {
1458 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1459 return -1;
1460 }
1461 }
1462 return 1;
1463 }
1464
1465 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1475 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1476 {
1477 // phpcs:enable
1478 // Insert into database
1479 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1480 $sql .= " statut = ".((int) $statut);
1481 if ($type_contact_id) {
1482 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1483 }
1484 if ($fk_socpeople) {
1485 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1486 }
1487 $sql .= " where rowid = ".((int) $rowid);
1488
1489 $resql = $this->db->query($sql);
1490 if ($resql) {
1491 return 0;
1492 } else {
1493 $this->error = $this->db->lasterror();
1494 return -1;
1495 }
1496 }
1497
1498 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1506 public function delete_contact($rowid, $notrigger = 0)
1507 {
1508 // phpcs:enable
1509 global $user;
1510
1511 $error = 0;
1512
1513 $this->db->begin();
1514
1515 if (!$error && empty($notrigger)) {
1516 // Call trigger
1517 $this->context['contact_id'] = ((int) $rowid);
1518 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1519 $result = $this->call_trigger($triggerPrefix.'_DELETE_CONTACT', $user);
1520 if ($result < 0) {
1521 $error++;
1522 }
1523 // End call triggers
1524 }
1525
1526 if (!$error) {
1527 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1528
1529 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1530 $sql .= " WHERE rowid = ".((int) $rowid);
1531
1532 $result = $this->db->query($sql);
1533 if (!$result) {
1534 $error++;
1535 $this->errors[] = $this->db->lasterror();
1536 }
1537 }
1538
1539 if (!$error) {
1540 $this->db->commit();
1541 return 1;
1542 } else {
1543 $this->error = $this->db->lasterror();
1544 $this->db->rollback();
1545 return -1;
1546 }
1547 }
1548
1549 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1557 public function delete_linked_contact($source = '', $code = '')
1558 {
1559 // phpcs:enable
1560 $listId = '';
1561 $temp = array();
1562 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1563
1564 if (!empty($typeContact)) {
1565 foreach ($typeContact as $key => $value) {
1566 array_push($temp, $key);
1567 }
1568 $listId = implode(",", $temp);
1569 }
1570
1571 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1572 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1573 if (empty($listId)) {
1574 return 0;
1575 }
1576
1577 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1578 $sql .= " WHERE element_id = ".((int) $this->id);
1579 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1580
1581 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1582 if ($this->db->query($sql)) {
1583 return 1;
1584 } else {
1585 $this->error = $this->db->lasterror();
1586 return -1;
1587 }
1588 }
1589
1590 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1602 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1603 {
1604 // phpcs:enable
1605 global $langs;
1606
1607 $tab = array();
1608
1609 $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
1610 if ($source == 'internal') {
1611 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1612 }
1613 if ($source == 'external' || $source == 'thirdparty') {
1614 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1615 }
1616 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1617 if (empty($arrayoftcids)) {
1618 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1619 }
1620 $sql .= " FROM";
1621 if (empty($arrayoftcids)) {
1622 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1623 }
1624 $sql .= " ".$this->db->prefix()."element_contact as ec";
1625 if ($source == 'internal') { // internal contact (user)
1626 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1627 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1628 }
1629 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1630 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1631 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1632 }
1633 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1634 if (empty($arrayoftcids)) {
1635 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1636 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1637 if ($code) {
1638 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1639 }
1640 if ($source == 'internal') {
1641 $sql .= " AND tc.source = 'internal'";
1642 }
1643 if ($source == 'external' || $source == 'thirdparty') {
1644 $sql .= " AND tc.source = 'external'";
1645 }
1646 $sql .= " AND tc.active = 1";
1647 } else {
1648 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1649 }
1650 if ($status >= 0) {
1651 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1652 }
1653 if ($statusoflink >= 0) {
1654 $sql .= " AND ec.statut = ".((int) $statusoflink);
1655 }
1656 $sql .= " ORDER BY t.lastname ASC";
1657
1658 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1659 $resql = $this->db->query($sql);
1660 if ($resql) {
1661 $num = $this->db->num_rows($resql);
1662 $i = 0;
1663 while ($i < $num) {
1664 $obj = $this->db->fetch_object($resql);
1665
1666 if (!$list) {
1667 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1668 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1669 $tab[$i] = array(
1670 'parentId' => $this->id,
1671 'source' => $obj->source,
1672 'socid' => $obj->socid,
1673 'id' => $obj->id,
1674 'nom' => $obj->lastname, // For backward compatibility
1675 'civility' => $obj->civility,
1676 'lastname' => $obj->lastname,
1677 'firstname' => $obj->firstname,
1678 'email' => $obj->email,
1679 'address' => $obj->address,
1680 'zip' => $obj->zip,
1681 'town' => $obj->town,
1682 'country_id' => $obj->country_id,
1683 'country' => $obj->country,
1684 'login' => (empty($obj->login) ? '' : $obj->login),
1685 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1686 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1687 'statuscontact' => $obj->statuscontact,
1688 'rowid' => $obj->rowid,
1689 'code' => $obj->code,
1690 'libelle' => $libelle_type,
1691 'status' => (int) $obj->statuslink,
1692 'fk_c_type_contact' => $obj->fk_c_type_contact
1693 );
1694 } else {
1695 $tab[$i] = $obj->id;
1696 }
1697
1698 $i++;
1699 }
1700
1701 return $tab;
1702 } else {
1703 $this->error = $this->db->lasterror();
1704 dol_print_error($this->db);
1705 return -1;
1706 }
1707 }
1708
1709
1716 public function swapContactStatus($rowid)
1717 {
1718 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1719 $sql .= " tc.code, tc.libelle as type_label";
1720 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1721 $sql .= " WHERE ec.rowid =".((int) $rowid);
1722 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1723 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1724
1725 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1726 $resql = $this->db->query($sql);
1727 if ($resql) {
1728 $obj = $this->db->fetch_object($resql);
1729 $newstatut = ($obj->statut == 4) ? 5 : 4;
1730 $result = $this->update_contact($rowid, $newstatut);
1731 $this->db->free($resql);
1732 return $result;
1733 } else {
1734 $this->error = $this->db->error();
1735 dol_print_error($this->db);
1736 return -1;
1737 }
1738 }
1739
1740 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1751 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1752 {
1753 // phpcs:enable
1754 global $langs;
1755
1756 $sanitized_order = $order; // @phan-suppress-current-line SqlInjection
1757
1758 if (empty($sanitized_order)) {
1759 $sanitized_order = 'position';
1760 }
1761 if ($sanitized_order == 'position') {
1762 $sanitized_order .= ',code';
1763 }
1764
1765 $tab = array();
1766
1767 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1768 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1769 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1770 if ($activeonly == 1) {
1771 $sql .= " AND tc.active = 1"; // only the active types
1772 }
1773 if (!empty($source) && $source != 'all') {
1774 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1775 }
1776 if (!empty($code)) {
1777 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1778 }
1779 $sql .= $this->db->order($sanitized_order, 'ASC');
1780
1781 //print "sql=".$sql;
1782 $resql = $this->db->query($sql);
1783 if (!$resql) {
1784 $this->error = $this->db->lasterror();
1785 //dol_print_error($this->db);
1786 return null;
1787 }
1788
1789 $num = $this->db->num_rows($resql);
1790 $i = 0;
1791 while ($i < $num) {
1792 $obj = $this->db->fetch_object($resql);
1793
1794 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1795 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1796 if (empty($option)) {
1797 $tab[$obj->rowid] = $libelle_type;
1798 } elseif ($option == 1) {
1799 $tab[$obj->code] = $libelle_type;
1800 } else {
1801 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1802 }
1803 $i++;
1804 }
1805
1806 return $tab;
1807 }
1808
1820 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1821 {
1822 global $langs;
1823
1824 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1825
1826 $tab = array();
1827
1828 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1829 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1830
1831 $sqlWhere = array();
1832 if (!empty($element)) {
1833 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1834 }
1835 if (!empty($excludeelement)) {
1836 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1837 }
1838
1839 if ($activeonly == 1) {
1840 $sqlWhere[] = " tc.active=1"; // only the active types
1841 }
1842
1843 if (!empty($source) && $source != 'all') {
1844 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1845 }
1846
1847 if (!empty($code)) {
1848 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1849 }
1850
1851 if (count($sqlWhere) > 0) {
1852 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1853 }
1854
1855 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1856
1857 dol_syslog(__METHOD__, LOG_DEBUG);
1858 $resql = $this->db->query($sql);
1859 if ($resql) {
1860 $num = $this->db->num_rows($resql);
1861 if ($num > 0) {
1862 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1863
1864 while ($obj = $this->db->fetch_object($resql)) {
1865 $modulename = $obj->module ?? $obj->element;
1866 if (strpos($obj->element, 'project') !== false) {
1867 $modulename = 'projet';
1868 } elseif ($obj->element == 'contrat') {
1869 $element = 'contract';
1870 } elseif ($obj->element == 'action') {
1871 $modulename = 'agenda';
1872 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1873 $modulename = 'fournisseur';
1874 }
1875 if (isModEnabled($modulename)) {
1876 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1877 $tmpelement = $obj->element;
1878 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1879 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1880 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1881 }
1882 }
1883 }
1884 return $tab;
1885 } else {
1886 $this->error = $this->db->lasterror();
1887 return null;
1888 }
1889 }
1890
1902 public function getIdContact($source, $code, $status = 0)
1903 {
1904 $result = array();
1905 $i = 0;
1906 // Particular case for shipping
1907 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1908 $id = $this->origin_id;
1909 $element = 'commande';
1910 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1911 $id = $this->origin_id;
1912 $element = 'order_supplier';
1913 } else {
1914 $id = $this->id;
1915 $element = $this->element;
1916 }
1917
1918 $sql = "SELECT ec.fk_socpeople";
1919 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1920 if ($source == 'internal') {
1921 $sql .= " ".$this->db->prefix()."user as c,";
1922 }
1923 if ($source == 'external') {
1924 $sql .= " ".$this->db->prefix()."socpeople as c,";
1925 }
1926 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1927 $sql .= " WHERE ec.element_id = ".((int) $id);
1928 $sql .= " AND ec.fk_socpeople = c.rowid";
1929 if ($source == 'internal') {
1930 $sql .= " AND c.entity IN (".getEntity('user').")";
1931 }
1932 if ($source == 'external') {
1933 $sql .= " AND c.entity IN (".getEntity('societe').")";
1934 }
1935 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1936 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1937 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1938 if ($code) {
1939 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1940 }
1941 $sql .= " AND tc.active = 1";
1942 if ($status) {
1943 $sql .= " AND ec.statut = ".((int) $status);
1944 }
1945
1946 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1947 $resql = $this->db->query($sql);
1948 if ($resql) {
1949 while ($obj = $this->db->fetch_object($resql)) {
1950 $result[$i] = $obj->fk_socpeople;
1951 $i++;
1952 }
1953 } else {
1954 $this->error = $this->db->error();
1955 return null;
1956 }
1957
1958 return $result;
1959 }
1960
1961 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1968 public function fetch_contact($contactid = null)
1969 {
1970 // phpcs:enable
1971 if (empty($contactid)) {
1972 $contactid = $this->contact_id;
1973 }
1974
1975 if (empty($contactid)) {
1976 return 0;
1977 }
1978
1979 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1980 $contact = new Contact($this->db);
1981 $result = $contact->fetch($contactid);
1982 $this->contact = $contact;
1983 return $result;
1984 }
1985
1986 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1994 public function fetch_thirdparty($force_thirdparty_id = 0)
1995 {
1996 // phpcs:enable
1997 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1998 return 0;
1999 }
2000
2001 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
2002
2003 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2004 if ($force_thirdparty_id) {
2005 $idtofetch = $force_thirdparty_id;
2006 }
2007
2008 if ($idtofetch) {
2009 $thirdparty = new Societe($this->db);
2010 $result = $thirdparty->fetch($idtofetch);
2011 if ($result < 0) {
2012 $this->errors = array_merge($this->errors, $thirdparty->errors);
2013 }
2014 $this->thirdparty = $thirdparty;
2015
2016 // Use first price level if level not defined for third party
2017 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
2018 $this->thirdparty->price_level = 1;
2019 }
2020
2021 return $result;
2022 } else {
2023 return -1;
2024 }
2025 }
2026
2027
2035 public function fetchOneLike($ref)
2036 {
2037 if (!$this->table_ref_field) {
2038 return 0;
2039 }
2040
2041 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
2042 $sql .= " WHERE ".$this->db->sanitize($this->table_ref_field)." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
2043 $sql .= " LIMIT 1";
2044
2045 $query = $this->db->query($sql);
2046
2047 if (!$this->db->num_rows($query)) {
2048 return 0;
2049 }
2050
2051 $result = $this->db->fetch_object($query);
2052
2053 if (method_exists($this, 'fetch')) {
2054 return $this->fetch($result->rowid);
2055 } else {
2056 $this->error = 'Fetch method not implemented on '.get_class($this);
2057 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2058 array_push($this->errors, $this->error);
2059 return -1;
2060 }
2061 }
2062
2063 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2072 public function fetch_barcode()
2073 {
2074 // phpcs:enable
2075 return $this->fetchBarCode();
2076 }
2077
2085 public function fetchBarCode()
2086 {
2087 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2088
2089 $idtype = $this->barcode_type;
2090 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
2091 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2092 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2093 } elseif ($this->element == 'societe') {
2094 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2095 } else {
2096 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2097 }
2098 }
2099
2100 if ($idtype > 0) {
2101 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
2102 $sql = "SELECT rowid, code, libelle as label, coder";
2103 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2104 $sql .= " WHERE rowid = ".((int) $idtype);
2105
2106 $resql = $this->db->query($sql);
2107 if ($resql) {
2108 $obj = $this->db->fetch_object($resql);
2109
2110 $this->barcode_type = $obj->rowid;
2111 $this->barcode_type_code = $obj->code;
2112 $this->barcode_type_label = $obj->label;
2113 $this->barcode_type_coder = $obj->coder;
2114 return 1;
2115 } else {
2116 dol_print_error($this->db);
2117 return -1;
2118 }
2119 }
2120 }
2121 return 0;
2122 }
2123
2129 public function fetchProject()
2130 {
2131 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2132
2133 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2134 $this->fk_project = $this->fk_projet; // For backward compatibility
2135 }
2136 if (empty($this->fk_project)) {
2137 return 0;
2138 }
2139
2140 $project = new Project($this->db);
2141 $result = $project->fetch($this->fk_project);
2142
2143 $this->project = $project;
2144
2145 return $result;
2146 }
2147
2148 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2156 public function fetch_project()
2157 {
2158 // phpcs:enable
2159 return $this->fetchProject();
2160 }
2161
2162 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2170 public function fetch_projet()
2171 {
2172 // phpcs:enable
2173 return $this->fetchProject();
2174 }
2175
2176 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2182 public function fetch_product()
2183 {
2184 // phpcs:enable
2185 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2186
2187 // @phan-suppress-next-line PhanUndeclaredProperty
2188 if (empty($this->fk_product)) {
2189 return 0;
2190 }
2191
2192 $product = new Product($this->db);
2193 // @phan-suppress-next-line PhanUndeclaredProperty
2194 $result = $product->fetch($this->fk_product);
2195
2196 $this->product = $product;
2197 return $result;
2198 }
2199
2200 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2207 public function fetch_user($userid)
2208 {
2209 // phpcs:enable
2210 $user = new User($this->db);
2211 $result = $user->fetch($userid);
2212 $this->user = $user;
2213 return $result;
2214 }
2215
2216 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2223 public function fetch_origin()
2224 {
2225 // phpcs:enable
2226 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2227
2228 // Manage classes with non standard name
2229 if ($tmpclassname == 'shipping') {
2230 $tmpclassname = 'Expedition';
2231 }
2232 if ($tmpclassname == 'delivery') {
2233 $tmpclassname = 'Livraison';
2234 }
2235 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2236 $tmpclassname = 'CommandeFournisseur';
2237 }
2238
2239 $classname = ucfirst($tmpclassname);
2240
2241 $this->origin_object = new $classname($this->db);
2242 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2243 $this->origin_object->fetch($this->origin_id);
2244 }
2245
2255 public function fetchObjectFrom($table, $field, $key, $element = null)
2256 {
2257 global $conf;
2258
2259 $result = false;
2260
2261 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2262 $sql .= " WHERE ".$this->db->sanitize($field)." = '".$this->db->escape($key)."'";
2263 if (!empty($element)) {
2264 $sql .= " AND entity IN (".getEntity($element).")";
2265 } else {
2266 $sql .= " AND entity = ".((int) $conf->entity);
2267 }
2268
2269 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2270 $resql = $this->db->query($sql);
2271 if ($resql) {
2272 $obj = $this->db->fetch_object($resql);
2273 // Test for avoid error -1
2274 if ($obj) {
2275 if (method_exists($this, 'fetch')) {
2276 $result = $this->fetch($obj->rowid);
2277 } else {
2278 $this->error = 'fetch() method not implemented on '.get_class($this);
2279 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2280 array_push($this->errors, $this->error);
2281 $result = -1;
2282 }
2283 }
2284 }
2285
2286 return $result;
2287 }
2288
2297 public function getValueFrom($table, $id, $field)
2298 {
2299 $result = false;
2300 if (!empty($id) && !empty($field) && !empty($table)) {
2301 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2302 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2303 return -1;
2304 }
2305 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2306 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2307 return -1;
2308 }
2309
2310 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2311 $sql .= " WHERE rowid = ".((int) $id);
2312
2313 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2314 $resql = $this->db->query($sql);
2315 if ($resql) {
2316 $row = $this->db->fetch_row($resql);
2317 $result = $row[0];
2318 }
2319 }
2320 return $result;
2321 }
2322
2339 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2340 {
2341 global $user;
2342
2343 if (empty($table)) {
2344 $table = $this->table_element;
2345 }
2346 if (empty($id)) {
2347 $id = $this->id;
2348 }
2349 if (empty($format)) {
2350 $format = 'text';
2351 }
2352 if (empty($id_field)) {
2353 $id_field = 'rowid';
2354 }
2355
2356 $propfield = $field;
2357
2358 // Special case
2359 if ($table == 'product') {
2360 if ($field == 'note_private') {
2361 $field = 'note';
2362 $propfield = 'note_private';
2363 } elseif ($field == 'status') {
2364 $field = 'tosell';
2365 $propfield = 'status';
2366 } elseif ($field == 'status_buy') {
2367 $field = 'tobuy';
2368 $propfield = 'status_buy';
2369 } elseif ($field == 'status_batch') {
2370 $field = 'tobatch';
2371 $propfield = 'status_batch';
2372 }
2373 }
2374
2375 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2376 $fk_user_field = 'fk_user_mod';
2377 }
2378 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2379 $fk_user_field = '';
2380 }
2381
2382 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2383 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2384 return -1;
2385 }
2386 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2387 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2388 return -1;
2389 }
2390
2391 $oldvalue = null;
2392 if ($trigkey) {
2393 $sql = "SELECT " . $this->db->sanitize($field);
2394 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2395 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2396
2397 $resql = $this->db->query($sql);
2398 if ($resql) {
2399 if ($obj = $this->db->fetch_object($resql)) {
2400 if ($format == 'date') {
2401 $oldvalue = $this->db->jdate($obj->$field);
2402 } else {
2403 $oldvalue = $obj->$field;
2404 }
2405 }
2406 } else {
2407 $this->error = $this->db->lasterror();
2408 return -1;
2409 }
2410 }
2411
2412 $error = 0;
2413
2414 dol_syslog(__METHOD__, LOG_DEBUG);
2415
2416 $this->db->begin();
2417
2418 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2419
2420 if ($format == 'text') {
2421 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2422 } elseif ($format == 'int') {
2423 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2424 } elseif ($format == 'date') {
2425 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2426 } elseif ($format == 'dategmt') {
2427 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2428 }
2429
2430 if ($fk_user_field) {
2431 if (!empty($fuser) && is_object($fuser)) {
2432 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2433 } elseif (empty($fuser) || $fuser != 'none') {
2434 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2435 }
2436 }
2437
2438 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2439
2440 $resql = $this->db->query($sql);
2441 if ($resql) {
2442 if ($trigkey) {
2443 // call trigger with updated object values
2444 if (method_exists($this, 'fetch')) {
2445 $result = $this->fetch($id);
2446 } else {
2447 $result = $this->fetchCommon($id);
2448 }
2449
2450 $this->oldcopy = clone $this;
2451 if (property_exists($this->oldcopy, $field)) {
2452 $this->oldcopy->$field = $oldvalue;
2453 }
2454 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2455 $this->oldcopy->$propfield = $oldvalue;
2456 }
2457
2458 if (empty($this->context['actionmsgmore'])) {
2459 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2460 }
2461
2462 if ($result >= 0) {
2463 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2464 }
2465 if ($result < 0) {
2466 $error++;
2467 }
2468 } else {
2469 if (property_exists($this, $field)) {
2470 $this->$field = $value;
2471 }
2472 if ($propfield != $field && property_exists($this, $propfield)) {
2473 $this->$propfield = $value;
2474 }
2475 }
2476
2477 if (!$error) {
2478 $this->db->commit();
2479 return 1;
2480 } else {
2481 if (property_exists($this, $field)) {
2482 $this->$field = $oldvalue;
2483 }
2484 if ($propfield != $field && property_exists($this, $propfield)) {
2485 $this->$propfield = $oldvalue;
2486 }
2487 $this->db->rollback();
2488 return -2;
2489 }
2490 } else {
2491 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2492 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2493 } else {
2494 $this->error = $this->db->lasterror();
2495 }
2496 $this->db->rollback();
2497 return -1;
2498 }
2499 }
2500
2501 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2512 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2513 {
2514 // phpcs:enable
2515 global $conf, $user;
2516
2517 if (!$this->table_element) {
2518 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2519 return -1;
2520 }
2521 if ($fieldid == 'none') {
2522 return 1;
2523 }
2524
2525 // For backward compatibility
2526 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2527 $fieldid = 'titre';
2528 }
2529
2530 // Security on socid
2531 $socid = 0;
2532 if ($user->socid > 0) {
2533 $socid = $user->socid;
2534 }
2535
2536 // this->ismultientitymanaged contains
2537 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2538 $aliastablesociete = 's';
2539 if ($this->element == 'societe') {
2540 $aliastablesociete = 'te'; // te as table_element
2541 }
2542 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2543 $sql = "SELECT MAX(te.".$this->db->sanitize($fieldid).")";
2544 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2545 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2546 $tmparray = explode('@', $this->ismultientitymanaged);
2547 $sql .= ", ".$this->db->prefix().$this->db->sanitize($tmparray[1])." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2548 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2549 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2550 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2551 $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
2552 }
2553 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2554 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2555 }
2556 if ($fieldid == 'rowid') {
2557 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < ".((int) $this->id);
2558 } elseif ($fieldid == 'label') {
2559 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < '".$this->db->escape((string) $this->label)."'";
2560 } else { // Should be 'ref' or any other string field
2561 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < '".$this->db->escape((string) $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2562 }
2563 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2564 $sql .= " AND sc.fk_user = ".((int) $user->id);
2565 }
2566 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2567 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2568 }
2569
2570 $filtermax = $filter;
2571
2572 // Manage filter
2573 $errormessage = '';
2574 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2575 if ($errormessage) {
2576 if (!preg_match('/^\s*AND/i', $filtermax)) {
2577 $sql .= " AND ";
2578 }
2579 $sql .= $filtermax; // @phan-suppress-current-line SqlInjection
2580 } else {
2581 $sql .= $tmpsql;
2582 }
2583
2584 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2585 $tmparray = explode('@', $this->ismultientitymanaged);
2586 $sql .= " AND te.".$this->db->sanitize($tmparray[0])." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2587 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2588 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2589 }
2590 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2591 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2592 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2593 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2594 } else {
2595 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2596 }
2597 } else {
2598 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2599 }
2600 }
2601 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2602 $tmparray = explode('@', $this->ismultientitymanaged);
2603 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2604 }
2605 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2606 $sql .= ' AND te.fk_soc = '.((int) $socid);
2607 }
2608 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2609 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2610 }
2611 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2612 $sql .= ' AND te.rowid = '.((int) $socid);
2613 }
2614 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2615
2616 $result = $this->db->query($sql);
2617 if (!$result) {
2618 $this->error = $this->db->lasterror();
2619 return -1;
2620 }
2621 $row = $this->db->fetch_row($result);
2622 $this->ref_previous = $row[0];
2623
2624 $sql = "SELECT MIN(te.".$this->db->sanitize($fieldid).")";
2625 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2626 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2627 $tmparray = explode('@', $this->ismultientitymanaged);
2628 $sql .= ", ".$this->db->prefix().$this->db->sanitize($tmparray[1])." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2629 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2630 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2631 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2632 $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
2633 }
2634 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2635 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2636 }
2637 if ($fieldid == 'rowid') {
2638 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > ".((int) $this->id);
2639 } elseif ($fieldid == 'label') {
2640 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > '".$this->db->escape((string) $this->label)."'";
2641 } else { // Should be 'ref' or any other string field
2642 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > '".$this->db->escape((string) $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2643 }
2644 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2645 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2646 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2647 $userschilds = $user->getAllChildIds();
2648 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2649 }
2650 $sql .= ')';
2651 }
2652 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2653 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2654 }
2655
2656 $filtermin = $filter;
2657
2658 // Manage filter
2659 $errormessage = '';
2660 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2661 if ($errormessage) {
2662 if (!preg_match('/^\s*AND/i', $filtermin)) {
2663 $sql .= " AND ";
2664 }
2665 $sql .= $filtermin; // @phan-suppress-current-line SqlInjection
2666
2667 $filtermin = '';
2668 } else {
2669 $sql .= $tmpsql;
2670 }
2671
2672 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2673 $tmparray = explode('@', $this->ismultientitymanaged);
2674 $sql .= " AND te.".$this->db->sanitize($tmparray[0])." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2675 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2676 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2677 }
2678 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2679 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2680 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2681 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2682 } else {
2683 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2684 }
2685 } else {
2686 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2687 }
2688 }
2689 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2690 $tmparray = explode('@', $this->ismultientitymanaged);
2691 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2692 }
2693 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2694 $sql .= ' AND te.fk_soc = '.((int) $socid);
2695 }
2696 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2697 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2698 }
2699 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2700 $sql .= ' AND te.rowid = '.((int) $socid);
2701 }
2702 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2703 // 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
2704
2705 $result = $this->db->query($sql);
2706 if (!$result) {
2707 $this->error = $this->db->lasterror();
2708 return -2;
2709 }
2710 $row = $this->db->fetch_row($result);
2711 $this->ref_next = $row[0];
2712
2713 return 1;
2714 }
2715
2716
2724 public function getListContactId($source = 'external')
2725 {
2726 $contactAlreadySelected = array();
2727 $tab = $this->liste_contact(-1, $source);
2728 $num = count($tab);
2729 $i = 0;
2730 while ($i < $num) {
2731 if ($source == 'thirdparty') {
2732 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2733 } else {
2734 $contactAlreadySelected[$i] = $tab[$i]['id'];
2735 }
2736 $i++;
2737 }
2738 return $contactAlreadySelected;
2739 }
2740
2741
2749 public function setProject($projectid, $notrigger = 0)
2750 {
2751 global $user;
2752 $error = 0;
2753
2754 if (!$this->table_element) {
2755 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2756 return -1;
2757 }
2758
2759 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2760 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2761 if ($projectid) {
2762 $sql .= " SET fk_project = ".((int) $projectid);
2763 } else {
2764 $sql .= " SET fk_project = NULL";
2765 }
2766 $sql .= ' WHERE id = '.((int) $this->id);
2767 // @phan-suppress-next-line PhanTypeMismatchProperty
2768 } elseif (!empty($this->fields['fk_project'])) { // Common case
2769 if ($projectid) {
2770 $sql .= " SET fk_project = ".((int) $projectid);
2771 } else {
2772 $sql .= " SET fk_project = NULL";
2773 }
2774 $sql .= ' WHERE rowid = '.((int) $this->id);
2775 } else { // Special case for old architecture objects
2776 if ($projectid) {
2777 $sql .= ' SET fk_projet = '.((int) $projectid);
2778 } else {
2779 $sql .= ' SET fk_projet = NULL';
2780 }
2781 $sql .= " WHERE rowid = ".((int) $this->id);
2782 }
2783
2784 $this->db->begin();
2785
2786 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2787 if ($this->db->query($sql)) {
2788 $this->fk_project = ((int) $projectid);
2789 } else {
2790 dol_print_error($this->db);
2791 $error++;
2792 }
2793
2794 // Triggers
2795 if (!$error && !$notrigger) {
2796 // Call triggers
2797 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2798 if ($result < 0) {
2799 $error++;
2800 } //Do also here what you must do to rollback action if trigger fail
2801 // End call triggers
2802 }
2803
2804 // Commit or rollback
2805 if ($error) {
2806 $this->db->rollback();
2807 return -1;
2808 } else {
2809 $this->db->commit();
2810 return 1;
2811 }
2812 }
2813
2820 public function setPaymentMethods($id)
2821 {
2822 global $user;
2823
2824 $error = 0;
2825 $notrigger = 0;
2826
2827 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2828
2829 if ($this->status >= 0 || $this->element == 'societe') {
2830 $this->oldcopy = dol_clone($this, 2); // @phan-suppress-current-line PhanTypeMismatchProperty
2831
2832 // TODO uniformize field name
2833 $fieldname = 'fk_mode_reglement';
2834 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2835 if ($this->element == 'societe') {
2836 $fieldname = 'mode_reglement';
2837 $triggerName = 'COMPANY';
2838 }
2839 if (get_class($this) == 'Facture') {
2840 $triggerName = 'BILL';
2841 }
2842 if (get_class($this) == 'Fournisseur') {
2843 $fieldname = 'mode_reglement_supplier';
2844 $triggerName = 'BILL_SUPPLIER';
2845 }
2846 if (get_class($this) == 'Tva') {
2847 $fieldname = 'fk_typepayment';
2848 $triggerName = 'VAT';
2849 }
2850 if (get_class($this) == 'Salary') {
2851 $fieldname = 'fk_typepayment';
2852 }
2853
2854 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2855 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2856 $sql .= ' WHERE rowid = '.((int) $this->id);
2857
2858 if ($this->db->query($sql)) {
2859 $this->mode_reglement_id = $id;
2860 // for supplier
2861 if (get_class($this) == 'Fournisseur') {
2862 $this->mode_reglement_supplier_id = $id;
2863 }
2864
2865 // Triggers
2866 if (!$error && !$notrigger) {
2867 // Call triggers
2868 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2869 if ($result < 0) {
2870 $error++;
2871 }
2872 // End call triggers
2873 }
2874 return 1;
2875 } else {
2876 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2877 $this->error = $this->db->error();
2878 return -1;
2879 }
2880 } else {
2881 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2882 $this->error = 'Status of the object is incompatible '.$this->status;
2883 return -2;
2884 }
2885 }
2886
2893 public function setMulticurrencyCode($code)
2894 {
2895 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2896 if ($this->status >= 0 || $this->element == 'societe') {
2897 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2898 $sql .= " SET multicurrency_code = '".$this->db->escape($code)."'";
2899 $sql .= ' WHERE rowid='.((int) $this->id);
2900
2901 if ($this->db->query($sql)) {
2902 $this->multicurrency_code = $code;
2903
2904 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2905 if ($rate) {
2906 $this->setMulticurrencyRate($rate, 2);
2907 }
2908
2909 return 1;
2910 } else {
2911 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2912 $this->error = $this->db->error();
2913 return -1;
2914 }
2915 } else {
2916 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2917 $this->error = 'Status of the object is incompatible '.$this->status;
2918 return -2;
2919 }
2920 }
2921
2929 public function setMulticurrencyRate($rate, $mode = 1)
2930 {
2931 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2932 if ($this->status >= 0 || $this->element == 'societe') {
2933 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2934 $sql .= " SET multicurrency_tx = ".((float) $rate);
2935 $sql .= ' WHERE rowid='.((int) $this->id);
2936
2937 if ($this->db->query($sql)) {
2938 $this->multicurrency_tx = $rate;
2939
2940 // Update line price
2941 if (!empty($this->lines)) {
2942 foreach ($this->lines as &$line) {
2943 // Amounts in company currency will be recalculated
2944 if ($mode == 1) {
2945 $line->subprice = 0;
2946 }
2947
2948 // Amounts in foreign currency will be recalculated
2949 if ($mode == 2) {
2950 $line->multicurrency_subprice = 0;
2951 }
2952
2953 switch ($this->element) {
2954 case 'propal':
2957 '@phan-var-force Propal $this';
2958 '@phan-var-force PropaleLigne $line';
2959 $this->updateline(
2960 $line->id,
2961 $line->subprice,
2962 $line->qty,
2963 $line->remise_percent,
2964 $line->tva_tx,
2965 $line->localtax1_tx,
2966 $line->localtax2_tx,
2967 ($line->description ? $line->description : $line->desc),
2968 'HT',
2969 $line->info_bits,
2970 $line->special_code,
2971 $line->fk_parent_line,
2972 $line->skip_update_total,
2973 $line->fk_fournprice,
2974 $line->pa_ht,
2975 $line->label,
2976 $line->product_type,
2977 $line->date_start,
2978 $line->date_end,
2979 $line->array_options,
2980 $line->fk_unit,
2981 $line->multicurrency_subprice
2982 );
2983 break;
2984 case 'commande':
2987 '@phan-var-force Commande $this';
2988 '@phan-var-force OrderLine $line';
2989 $this->updateline(
2990 $line->id,
2991 ($line->description ? $line->description : $line->desc),
2992 $line->subprice,
2993 $line->qty,
2994 $line->remise_percent,
2995 $line->tva_tx,
2996 $line->localtax1_tx,
2997 $line->localtax2_tx,
2998 'HT',
2999 $line->info_bits,
3000 $line->date_start,
3001 $line->date_end,
3002 $line->product_type,
3003 $line->fk_parent_line,
3004 $line->skip_update_total,
3005 $line->fk_fournprice,
3006 $line->pa_ht,
3007 $line->label,
3008 $line->special_code,
3009 $line->array_options,
3010 $line->fk_unit,
3011 $line->multicurrency_subprice
3012 );
3013 break;
3014 case 'facture':
3017 '@phan-var-force Facture $this';
3018 '@phan-var-force FactureLigne $line';
3019 $this->updateline(
3020 $line->id,
3021 ($line->description ? $line->description : $line->desc),
3022 $line->subprice,
3023 $line->qty,
3024 $line->remise_percent,
3025 $line->date_start,
3026 $line->date_end,
3027 $line->tva_tx,
3028 $line->localtax1_tx,
3029 $line->localtax2_tx,
3030 'HT',
3031 $line->info_bits,
3032 $line->product_type,
3033 $line->fk_parent_line,
3034 $line->skip_update_total,
3035 $line->fk_fournprice,
3036 $line->pa_ht,
3037 $line->label,
3038 $line->special_code,
3039 $line->array_options,
3040 $line->situation_percent,
3041 $line->fk_unit,
3042 $line->multicurrency_subprice
3043 );
3044 break;
3045 case 'facturerec':
3048 '@phan-var-force FactureRec $this';
3049 '@phan-var-force FactureLigneRec $line';
3050 $this->updateline(
3051 $line->id,
3052 ($line->description ? $line->description : $line->desc),
3053 $line->subprice,
3054 $line->qty,
3055 $line->tva_tx,
3056 $line->localtax1_tx,
3057 $line->localtax2_tx,
3058 $line->fk_product,
3059 $line->remise_percent,
3060 'HT',
3061 $line->info_bits,
3062 0,
3063 0,
3064 $line->product_type,
3065 $line->rang,
3066 $line->special_code,
3067 $line->label,
3068 $line->fk_unit,
3069 $line->multicurrency_subprice,
3070 0,
3071 $line->date_start,
3072 $line->date_end,
3073 $line->fk_fournprice,
3074 $line->pa_ht,
3075 $line->fk_parent_line
3076 );
3077 break;
3078 case 'supplier_proposal':
3081 '@phan-var-force SupplierProposal $this';
3082 '@phan-var-force SupplierProposalLine $line';
3083 $this->updateline(
3084 $line->id,
3085 $line->subprice,
3086 $line->qty,
3087 $line->remise_percent,
3088 $line->tva_tx,
3089 $line->localtax1_tx,
3090 $line->localtax2_tx,
3091 ($line->description ? $line->description : $line->desc),
3092 'HT',
3093 $line->info_bits,
3094 $line->special_code,
3095 $line->fk_parent_line,
3096 $line->skip_update_total,
3097 $line->fk_fournprice,
3098 $line->pa_ht,
3099 $line->label,
3100 $line->product_type,
3101 $line->array_options,
3102 $line->ref_fourn,
3103 (int) $line->fk_unit,
3104 $line->multicurrency_subprice
3105 );
3106 break;
3107 case 'order_supplier':
3110 '@phan-var-force CommandeFournisseur $this';
3111 '@phan-var-force CommandeFournisseurLigne $line';
3112 $this->updateline(
3113 $line->id,
3114 ($line->description ? $line->description : $line->desc),
3115 $line->subprice,
3116 $line->qty,
3117 $line->remise_percent,
3118 $line->tva_tx,
3119 $line->localtax1_tx,
3120 $line->localtax2_tx,
3121 'HT',
3122 $line->info_bits,
3123 $line->product_type,
3124 0,
3125 $line->date_start,
3126 $line->date_end,
3127 $line->array_options,
3128 $line->fk_unit,
3129 $line->multicurrency_subprice,
3130 $line->ref_supplier
3131 );
3132 break;
3133 case 'invoice_supplier':
3136 '@phan-var-force FactureFournisseur $this';
3137 '@phan-var-force SupplierInvoiceLIne $line';
3138 $this->updateline(
3139 $line->id,
3140 ($line->description ? $line->description : $line->desc),
3141 $line->subprice,
3142 $line->tva_tx,
3143 $line->localtax1_tx,
3144 $line->localtax2_tx,
3145 $line->qty,
3146 0,
3147 'HT',
3148 $line->info_bits,
3149 $line->product_type,
3150 $line->remise_percent,
3151 0,
3152 $line->date_start,
3153 $line->date_end,
3154 $line->array_options,
3155 $line->fk_unit,
3156 $line->multicurrency_subprice,
3157 $line->ref_supplier
3158 );
3159 break;
3160 default:
3161 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3162 break;
3163 }
3164 }
3165 }
3166
3167 return 1;
3168 } else {
3169 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3170 $this->error = $this->db->error();
3171 return -1;
3172 }
3173 } else {
3174 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3175 $this->error = 'Status of the object is incompatible '.$this->status;
3176 return -2;
3177 }
3178 }
3179
3187 public function setPaymentTerms($id, $deposit_percent = null)
3188 {
3189 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.formatLogObject($deposit_percent).')');
3190 if ($this->status >= 0 || $this->element == 'societe') {
3191 // TODO uniformize field name
3192 $fieldname = 'fk_cond_reglement';
3193 if ($this->element == 'societe') {
3194 $fieldname = 'cond_reglement';
3195 }
3196 if (get_class($this) == 'Fournisseur') {
3197 $fieldname = 'cond_reglement_supplier';
3198 }
3199
3200 if (empty($deposit_percent) || $deposit_percent < 0) {
3201 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3202 }
3203
3204 if ($deposit_percent > 100) {
3205 $deposit_percent = 100;
3206 }
3207
3208 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3209 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3210 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3211 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3212 }
3213 $sql .= ' WHERE rowid = '.((int) $this->id);
3214
3215 if ($this->db->query($sql)) {
3216 $this->cond_reglement_id = $id;
3217 // for supplier
3218 if (get_class($this) == 'Fournisseur') {
3219 $this->cond_reglement_supplier_id = $id;
3220 }
3221 $this->deposit_percent = $deposit_percent;
3222 return 1;
3223 } else {
3224 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3225 $this->error = $this->db->error();
3226 return -1;
3227 }
3228 } else {
3229 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3230 $this->error = 'Status of the object is incompatible '.$this->status;
3231 return -2;
3232 }
3233 }
3234
3241 public function setTransportMode($id)
3242 {
3243 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3244 if ($this->status >= 0 || $this->element == 'societe') {
3245 $fieldname = 'fk_transport_mode';
3246 if ($this->element == 'societe') {
3247 $fieldname = 'transport_mode';
3248 }
3249 if (get_class($this) == 'Fournisseur') {
3250 $fieldname = 'transport_mode_supplier';
3251 }
3252
3253 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3254 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3255 $sql .= ' WHERE rowid='.((int) $this->id);
3256
3257 if ($this->db->query($sql)) {
3258 $this->transport_mode_id = $id;
3259 // for supplier
3260 if (get_class($this) == 'Fournisseur') {
3261 $this->transport_mode_supplier_id = $id;
3262 }
3263 return 1;
3264 } else {
3265 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3266 $this->error = $this->db->error();
3267 return -1;
3268 }
3269 } else {
3270 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3271 $this->error = 'Status of the object is incompatible '.$this->status;
3272 return -2;
3273 }
3274 }
3275
3283 public function setDeliveryAddress($id)
3284 {
3285 $fieldname = 'fk_delivery_address';
3286 if ($this->element == 'delivery' || $this->element == 'shipping') {
3287 $fieldname = 'fk_address';
3288 }
3289
3290 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$this->db->sanitize($fieldname)." = ".((int) $id);
3291 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3292
3293 if ($this->db->query($sql)) {
3294 $this->fk_delivery_address = $id;
3295 return 1;
3296 } else {
3297 $this->error = $this->db->error();
3298 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3299 return -1;
3300 }
3301 }
3302
3303
3312 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3313 {
3314 global $user;
3315
3316 if (empty($userused)) {
3317 $userused = $user;
3318 }
3319
3320 $error = 0;
3321
3322 if (!$this->table_element) {
3323 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3324 return -1;
3325 }
3326
3327 $this->db->begin();
3328
3329 if ($shipping_method_id < 0) {
3330 $shipping_method_id = 'NULL';
3331 }
3332 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3333
3334 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3335 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3336 $sql .= " WHERE rowid=".((int) $this->id);
3337 $resql = $this->db->query($sql);
3338 if (!$resql) {
3339 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3340 $this->error = $this->db->lasterror();
3341 $error++;
3342 } else {
3343 if (!$notrigger) {
3344 // Call trigger
3345 $this->context = array('shippingmethodupdate' => 1);
3346 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3347 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3348 if ($result < 0) {
3349 $error++;
3350 }
3351 // End call trigger
3352 }
3353 }
3354 if ($error) {
3355 $this->db->rollback();
3356 return -1;
3357 } else {
3358 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3359 $this->db->commit();
3360 return 1;
3361 }
3362 }
3363
3364
3371 public function setWarehouse($warehouse_id)
3372 {
3373 if (!$this->table_element) {
3374 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3375 return -1;
3376 }
3377 if ($warehouse_id < 0) {
3378 $warehouse_id = 'NULL';
3379 }
3380 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3381
3382 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3383 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3384 $sql .= " WHERE rowid=".((int) $this->id);
3385
3386 if ($this->db->query($sql)) {
3387 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3388 return 1;
3389 } else {
3390 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3391 $this->error = $this->db->error();
3392 return 0;
3393 }
3394 }
3395
3396
3404 public function setDocModel($user, $modelpdf)
3405 {
3406 if (!$this->table_element) {
3407 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3408 return -1;
3409 }
3410
3411 $newmodelpdf = dol_trunc($modelpdf, 255);
3412
3413 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3414 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3415 $sql .= " WHERE rowid = ".((int) $this->id);
3416
3417 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3418 $resql = $this->db->query($sql);
3419 if ($resql) {
3420 $this->model_pdf = $modelpdf;
3421 return 1;
3422 } else {
3423 dol_print_error($this->db);
3424 return 0;
3425 }
3426 }
3427
3428
3437 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3438 {
3439 global $user;
3440
3441 if (empty($userused)) {
3442 $userused = $user;
3443 }
3444
3445 $error = 0;
3446
3447 if (!$this->table_element) {
3448 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3449 return -1;
3450 }
3451 $this->db->begin();
3452
3453 if ($fk_account < 0) {
3454 $fk_account = 'NULL';
3455 }
3456 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3457
3458 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3459 $sql .= " SET fk_account = ".((int) $fk_account);
3460 $sql .= " WHERE rowid=".((int) $this->id);
3461
3462 $resql = $this->db->query($sql);
3463 if (!$resql) {
3464 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3465 $this->error = $this->db->lasterror();
3466 $error++;
3467 } else {
3468 if (!$notrigger) {
3469 // Call trigger
3470 $this->context['bankaccountupdate'] = 1;
3471
3472 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3473 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3474 if ($result < 0) {
3475 $error++;
3476 }
3477 // End call trigger
3478 }
3479 }
3480 if ($error) {
3481 $this->db->rollback();
3482 return -1;
3483 } else {
3484 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3485 $this->db->commit();
3486 return 1;
3487 }
3488 }
3489
3490
3491 // TODO: Move line related operations to CommonObjectLine?
3492
3493 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3503 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3504 {
3505 // phpcs:enable
3506 if (!$this->table_element_line) {
3507 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3508 return -1;
3509 }
3510 if (!$this->fk_element) {
3511 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3512 return -1;
3513 }
3514
3515 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3516 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3517 $fieldposition = 'position';
3518 }
3519
3520 // Count number of lines to reorder (according to choice $renum)
3521 $nl = 0;
3522 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3523 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3524 if (!$renum) {
3525 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = 0";
3526 }
3527 if ($renum) {
3528 $sql .= " AND " . $this->db->sanitize($fieldposition) . " <> 0";
3529 }
3530
3531 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3532 $resql = $this->db->query($sql);
3533 if ($resql) {
3534 $row = $this->db->fetch_row($resql);
3535 $nl = $row[0];
3536 } else {
3537 dol_print_error($this->db);
3538 }
3539 if ($nl > 0) {
3540 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3541 $rows = array();
3542
3543 // We first search all lines that are parent lines (for multilevel details lines)
3544 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3545 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3546 if ($fk_parent_line) {
3547 $sql .= ' AND fk_parent_line IS NULL';
3548 }
3549 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC, rowid " . $this->db->sanitize($rowidorder);
3550
3551 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3552 $resql = $this->db->query($sql);
3553 if ($resql) {
3554 $i = 0;
3555 $num = $this->db->num_rows($resql);
3556 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3557 while ($i < $num) {
3558 $row = $this->db->fetch_row($resql);
3559 $rows[] = $row[0]; // Add parent line into array rows
3560 if ($fk_parent_line) {
3561 $children = $this->getChildrenOfLine($row[0], $grandchild);
3562 }
3563 if (!empty($children)) {
3564 foreach ($children as $child) {
3565 array_push($rows, $child);
3566 }
3567 }
3568 $i++;
3569 }
3570
3571 // Now we set a new number for each lines (parent and children with children included into parent tree)
3572 if (!empty($rows)) {
3573 foreach ($rows as $key => $row) {
3574 $this->updateRangOfLine($row, ($key + 1));
3575 }
3576 }
3577 } else {
3578 dol_print_error($this->db);
3579 }
3580 }
3581 return 1;
3582 }
3583
3591 public function getChildrenOfLine($id, $includealltree = 0)
3592 {
3593 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3594 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3595 $fieldposition = 'position';
3596 }
3597
3598 $rows = array();
3599
3600 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3601 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3602 $sql .= ' AND fk_parent_line = '.((int) $id);
3603 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC";
3604
3605 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3606
3607 $resql = $this->db->query($sql);
3608 if (!$resql || $this->db->num_rows($resql) <= 0) {
3609 return array();
3610 }
3611
3612 while ($row = $this->db->fetch_row($resql)) {
3613 $rows[] = $row[0];
3614 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3615 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3616 }
3617 }
3618 return $rows;
3619 }
3620
3621 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3629 public function line_up($rowid, $fk_parent_line = true)
3630 {
3631 // phpcs:enable
3632 $this->line_order(false, 'ASC', $fk_parent_line);
3633
3634 // Get rang of line
3635 $rang = $this->getRangOfLine($rowid);
3636
3637 // Update position of line
3638 $this->updateLineUp($rowid, $rang);
3639 }
3640
3641 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3649 public function line_down($rowid, $fk_parent_line = true)
3650 {
3651 // phpcs:enable
3652 $this->line_order(false, 'ASC', $fk_parent_line);
3653
3654 // Get rang of line
3655 $rang = $this->getRangOfLine($rowid);
3656
3657 // Get max value for rang
3658 $max = $this->line_max();
3659
3660 // Update position of line
3661 $this->updateLineDown($rowid, $rang, $max);
3662 }
3663
3671 public function updateRangOfLine($rowid, $rang)
3672 {
3673 global $hookmanager;
3674 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3675 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3676 $fieldposition = 'position';
3677 }
3678
3679 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3680 $sql .= ' WHERE rowid = '.((int) $rowid);
3681
3682 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3683 if (!$this->db->query($sql)) {
3684 dol_print_error($this->db);
3685 return -1;
3686 }
3687
3688 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3689 $action = '';
3690 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3691 return 1;
3692 }
3693
3694 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3701 public function line_ajaxorder($rows)
3702 {
3703 // phpcs:enable
3704 $num = count($rows);
3705 for ($i = 0; $i < $num; $i++) {
3706 $this->updateRangOfLine($rows[$i], ($i + 1));
3707 }
3708 }
3709
3717 public function updateLineUp($rowid, $rang)
3718 {
3719 if ($rang <= 1) {
3720 return -1;
3721 }
3722
3723 $fieldposition = 'rang';
3724 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3725 $fieldposition = 'position';
3726 }
3727
3728 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3729 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3730 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang - 1));
3731 if (!$this->db->query($sql)) {
3732 $this->error = $this->db->lasterror();
3733 return -1;
3734 }
3735
3736 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang - 1));
3737 $sql .= ' WHERE rowid = '.((int) $rowid);
3738 if (!$this->db->query($sql)) {
3739 $this->error = $this->db->lasterror();
3740 return -1;
3741 }
3742
3743 return 1;
3744 }
3745
3754 public function updateLineDown($rowid, $rang, $max)
3755 {
3756 if ($rang >= $max) {
3757 return -1;
3758 }
3759
3760 $fieldposition = 'rang';
3761 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3762 $fieldposition = 'position';
3763 }
3764
3765 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3766 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3767 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang + 1));
3768 if (!$this->db->query($sql)) {
3769 $this->error = $this->db->lasterror();
3770 return -1;
3771 }
3772
3773 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang + 1));
3774 $sql .= ' WHERE rowid = '.((int) $rowid);
3775 if (!$this->db->query($sql)) {
3776 $this->error = $this->db->lasterror();
3777 return -1;
3778 }
3779
3780 return 1;
3781 }
3782
3789 public function getRangOfLine($rowid)
3790 {
3791 $fieldposition = 'rang';
3792 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3793 $fieldposition = 'position';
3794 }
3795
3796 $sql = "SELECT " . $this->db->sanitize($fieldposition) . " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3797 $sql .= " WHERE rowid = ".((int) $rowid);
3798
3799 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3800 $resql = $this->db->query($sql);
3801 if (!$resql) {
3802 return 0;
3803 }
3804
3805 $row = $this->db->fetch_row($resql);
3806 return $row[0];
3807 }
3808
3815 public function getIdOfLine($rang)
3816 {
3817 $fieldposition = 'rang';
3818 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3819 $fieldposition = 'position';
3820 }
3821
3822 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3823 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3824 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = ".((int) $rang);
3825 $resql = $this->db->query($sql);
3826 if (!$resql) {
3827 return 0;
3828 }
3829
3830 $row = $this->db->fetch_row($resql);
3831 return $row[0];
3832 }
3833
3834 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3841 public function line_max($fk_parent_line = 0)
3842 {
3843 // phpcs:enable
3844 $positionfield = 'rang';
3845 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3846 $positionfield = 'position';
3847 }
3848
3849 $sql = "SELECT max(".$this->db->sanitize($positionfield).") FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3850 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3851
3852 // Search the last rang with fk_parent_line
3853 if ($fk_parent_line) {
3854 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3855 }
3856
3857 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3858 $resql = $this->db->query($sql);
3859 if ($resql) {
3860 $row = $this->db->fetch_row($resql);
3861
3862 if ($fk_parent_line) {
3863 if (!empty($row[0])) {
3864 return $row[0];
3865 } else {
3866 return $this->getRangOfLine($fk_parent_line);
3867 }
3868 } else {
3869 return $row[0];
3870 }
3871 }
3872
3873 return 0;
3874 }
3875
3876 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3883 public function update_ref_ext($ref_ext)
3884 {
3885 // phpcs:enable
3886 if (!$this->table_element) {
3887 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3888 return -1;
3889 }
3890
3891 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3892 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3893 // @phan-suppress-next-line PhanUndeclaredProperty
3894 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3895
3896 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3897 if ($this->db->query($sql)) {
3898 $this->ref_ext = $ref_ext;
3899 return 1;
3900 } else {
3901 $this->error = $this->db->error();
3902 return -1;
3903 }
3904 }
3905
3906 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3915 public function update_note($note, $suffix = '', $notrigger = 0)
3916 {
3917 // phpcs:enable
3918 global $user;
3919
3920 if (!$this->table_element) {
3921 $this->error = 'update_note was called on object with property table_element not defined';
3922 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3923 return -1;
3924 }
3925 if (!in_array($suffix, array('', '_public', '_private'))) {
3926 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3927 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3928 return -2;
3929 }
3930
3931 $newsuffix = $suffix;
3932
3933 // Special case
3934 if ($this->table_element == 'product' && $newsuffix == '_private') {
3935 $newsuffix = '';
3936 }
3937 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3938 $fieldusermod = "fk_user_mod";
3939 } elseif ($this->table_element == 'ecm_files') {
3940 $fieldusermod = "fk_user_m";
3941 } else {
3942 $fieldusermod = "fk_user_modif";
3943 }
3944 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3945 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3946 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3947 $sql .= " WHERE rowid = ".((int) $this->id);
3948
3949 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3950 if ($this->db->query($sql)) {
3951 if ($suffix == '_public') {
3952 $this->note_public = $note;
3953 } elseif ($suffix == '_private') {
3954 $this->note_private = $note;
3955 } else {
3956 $this->note = $note; // deprecated
3957 $this->note_private = $note;
3958 }
3959 if (empty($notrigger)) {
3960 // TODO Use the $this->TRIGGER_PREFIX when implemented
3961 switch ($this->element) {
3962 case 'societe':
3963 $triggerName = 'COMPANY_MODIFY';
3964 break;
3965 case 'facture':
3966 $triggerName = 'BILL_MODIFY';
3967 break;
3968 case 'invoice_supplier':
3969 $triggerName = 'BILL_SUPPLIER_MODIFY';
3970 break;
3971 case 'facturerec':
3972 $triggerName = 'BILLREC_MODIFIY';
3973 break;
3974 case 'expensereport':
3975 $triggerName = 'EXPENSE_REPORT_MODIFY';
3976 break;
3977 default:
3978 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
3979 }
3980 $ret = $this->call_trigger($triggerName, $user);
3981 if ($ret < 0) {
3982 return -1;
3983 }
3984 }
3985 return 1;
3986 } else {
3987 $this->error = $this->db->lasterror();
3988 return -1;
3989 }
3990 }
3991
3992 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4001 public function update_note_public($note)
4002 {
4003 // phpcs:enable
4004 return $this->update_note($note, '_public');
4005 }
4006
4007 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4018 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4019 {
4020 // phpcs:enable
4021 global $conf, $hookmanager, $action;
4022
4023 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4024 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4025 if ($reshook > 0) {
4026 return 1; // replacement code
4027 } elseif ($reshook < 0) {
4028 return -1; // failure
4029 } // reshook = 0 => execute normal code
4030
4031 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4032 $isElementForSupplier = false;
4033 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4034 $MODULE = "";
4035 if ($this->element == 'propal') {
4036 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4037 } elseif ($this->element == 'commande' || $this->element == 'order') {
4038 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4039 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4040 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4041 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4042 $isElementForSupplier = true;
4043 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4044 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4045 $isElementForSupplier = true;
4046 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4047 } elseif ($this->element == 'supplier_proposal') {
4048 $isElementForSupplier = true;
4049 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4050 }
4051 if ($isElementForSupplier) {
4052 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4053 }
4054
4055 if (!empty($MODULE)) {
4056 if (getDolGlobalString($MODULE)) {
4057 $modsactivated = explode(',', getDolGlobalString($MODULE));
4058 foreach ($modsactivated as $mod) {
4059 if (isModEnabled($mod)) {
4060 return 1; // update was disabled by specific setup
4061 }
4062 }
4063 }
4064 }
4065
4066 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4067
4068 $forcedroundingmode = $roundingadjust;
4069 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4070 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4071 } elseif ($forcedroundingmode == 'auto') {
4072 $forcedroundingmode = '0';
4073 }
4074
4075 $error = 0;
4076
4077 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4078
4079 // Define constants to find lines to sum (field name into the table_element_line not into table_element)
4080 $fieldtva = 'total_tva';
4081 $fieldlocaltax1 = 'total_localtax1';
4082 $fieldlocaltax2 = 'total_localtax2';
4083 $fieldup = 'subprice';
4084 $base_price_type = 'HT';
4085 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4086 $fieldtva = 'tva';
4087 $fieldup = 'pu_ht';
4088 }
4089 if ($this->element == 'invoice_supplier_rec') {
4090 $fieldup = 'pu_ht';
4091 }
4092 if ($this->element == 'expensereport') {
4093 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4094 // 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
4095 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4096 $forcedroundingmode = '0';
4097 $fieldup = 'value_unit';
4098 $base_price_type = 'TTC';
4099 }
4100
4101 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4102 $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,";
4103 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4104 $sql .= ' info_bits, product_type,';
4105 if ($this->element == 'expensereport') {
4106 $sql .= ' comments as description';
4107 } else {
4108 $sql .= ' description';
4109 }
4110 if ($this->table_element_line == 'facturedet') {
4111 $sql .= ', situation_percent';
4112 }
4113 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4114 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
4115 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
4116 if ($exclspec) {
4117 $product_field = 'product_type';
4118 if ($this->table_element_line == 'contratdet') {
4119 $product_field = ''; // contratdet table has no product_type field
4120 }
4121 if ($product_field) {
4122 $sql .= " AND ".$this->db->sanitize($product_field)." <> 9";
4123 }
4124 }
4125 $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
4126
4127 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4128
4129 $resql = $this->db->query($sql);
4130 if ($resql) {
4131 $this->total_ht = 0;
4132 $this->total_tva = 0;
4133 $this->total_localtax1 = 0;
4134 $this->total_localtax2 = 0;
4135 $this->total_ttc = 0;
4136 $total_ht_by_vats = array();
4137 $total_tva_by_vats = array();
4138 $total_ttc_by_vats = array();
4139 $this->multicurrency_total_ht = 0;
4140 $this->multicurrency_total_tva = 0;
4141 $this->multicurrency_total_ttc = 0;
4142
4143 $this->db->begin();
4144
4145 $num = $this->db->num_rows($resql);
4146 $i = 0;
4147 while ($i < $num) {
4148 $obj = $this->db->fetch_object($resql);
4149
4150 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4151 $parameters = array('fk_element' => $obj->rowid);
4152 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4153
4154 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'
4155 // This part of code is to fix data. We should not call it too often.
4156 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4157 $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);
4158
4159 $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.
4160 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4161 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4162 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4163
4164 if ($diff_on_current_total) {
4165 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4166 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4167 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4168 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4169 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4170 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);
4171 $resqlfix = $this->db->query($sqlfix);
4172 if (!$resqlfix) {
4173 dol_print_error($this->db, 'Failed to update line');
4174 }
4175 $obj->total_tva = $tmpcal[1];
4176 $obj->total_ttc = $tmpcal[2];
4177 $obj->multicurrency_total_tva = $tmpcal[17];
4178 $obj->multicurrency_total_ttc = $tmpcal[18];
4179 } elseif ($diff_when_using_price_ht) {
4180 // 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
4181 // 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),
4182 // so we continue only if price HT are same.
4183 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4184 // 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
4185 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4186 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4187 // 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,
4188 // 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.
4189 // 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.
4190 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4191 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4192 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4193 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4194
4195 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);
4196
4197 $resqlfix = $this->db->query($sqlfix);
4198 if (!$resqlfix) {
4199 dol_print_error($this->db, 'Failed to update line');
4200 }
4201 $obj->total_tva = $tmpcal[1];
4202 $obj->total_ttc = $tmpcal[2];
4203 $obj->multicurrency_total_tva = $tmpcal[17];
4204 $obj->multicurrency_total_ttc = $tmpcal[18];
4205 }
4206 }
4207 }
4208 }
4209
4210 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4211 $this->total_tva += $obj->total_tva;
4212 $this->total_localtax1 += $obj->total_localtax1;
4213 $this->total_localtax2 += $obj->total_localtax2;
4214 $this->total_ttc += $obj->total_ttc;
4215 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4216 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4217 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4218
4219 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4220 $total_ht_by_vats[$obj->vatrate] = 0;
4221 }
4222 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4223 $total_tva_by_vats[$obj->vatrate] = 0;
4224 }
4225 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4226 $total_ttc_by_vats[$obj->vatrate] = 0;
4227 }
4228 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4229 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4230 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4231
4232 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4233 if ($base_price_type == 'TTC') {
4234 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4235 } else {
4236 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4237 }
4238 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4239 //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";
4240 if ($diff) {
4241 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4242 if (abs((float) $diff) > $maxdiff) {
4243 // If error is more than 10 times the accuracy of rounding. This should not happen.
4244 $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.';
4245 dol_syslog($errmsg, LOG_WARNING);
4246 $this->error = $errmsg;
4247 $error++;
4248 break;
4249 }
4250
4251 if ($base_price_type == 'TTC') {
4252 $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);
4253 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);
4254 } else {
4255 $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);
4256 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);
4257 }
4258
4259 $resqlfix = $this->db->query($sqlfix);
4260
4261 if (!$resqlfix) {
4262 dol_print_error($this->db, 'Failed to update line');
4263 }
4264
4265 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4266 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4267 if ($base_price_type == 'TTC') {
4268 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4269 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4270 } else {
4271 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4272 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4273 }
4274 }
4275 }
4276
4277 $i++;
4278 }
4279
4280 // Add revenue stamp to total
4281 // @phan-suppress-next-line PhanUndeclaredProperty
4282 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4283 // @phan-suppress-next-line PhanUndeclaredProperty
4284 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4285
4286 // Situations totals
4287 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
4288 '@phan-var-force Facture $this';
4289 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
4290 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4291 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4292 $prev_sits = $this->get_prev_sits();
4293
4294 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4295 $this->total_ht -= $sit->total_ht;
4296 $this->total_tva -= $sit->total_tva;
4297 $this->total_localtax1 -= $sit->total_localtax1;
4298 $this->total_localtax2 -= $sit->total_localtax2;
4299 $this->total_ttc -= $sit->total_ttc;
4300 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4301 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4302 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4303 }
4304 }
4305 }
4306 }
4307
4308 // Clean total
4309 $this->total_ht = (float) price2num($this->total_ht);
4310 $this->total_tva = (float) price2num($this->total_tva);
4311 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4312 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4313 $this->total_ttc = (float) price2num($this->total_ttc);
4314
4315 $this->db->free($resql);
4316
4317 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4318 $fieldht = 'total_ht';
4319 $fieldtva = 'tva';
4320 $fieldlocaltax1 = 'localtax1';
4321 $fieldlocaltax2 = 'localtax2';
4322 $fieldttc = 'total_ttc';
4323 // Specific code for backward compatibility with old field names
4324 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4325 $fieldtva = 'total_tva';
4326 }
4327
4328 if (!$error && empty($nodatabaseupdate)) {
4329 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4330 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4331 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4332 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4333 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4334 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4335 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4336 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4337 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4338 $sql .= " WHERE rowid = ".((int) $this->id);
4339
4340 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4341 $resql = $this->db->query($sql);
4342
4343 if (!$resql) {
4344 $error++;
4345 $this->error = $this->db->lasterror();
4346 $this->errors[] = $this->db->lasterror();
4347 }
4348 }
4349
4350 if (!$error) {
4351 $this->db->commit();
4352 return 1;
4353 } else {
4354 $this->db->rollback();
4355 return -1;
4356 }
4357 } else {
4358 dol_print_error($this->db, 'Bad request in update_price');
4359 return -1;
4360 }
4361 }
4362
4363 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4374 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4375 {
4376 // phpcs:enable
4377 global $user, $hookmanager, $action;
4378 dol_syslog(__METHOD__, LOG_DEBUG);
4379
4380 if (empty($this->origin_type) && !empty($this->origin)) {
4381 $this->origin_type = $this->origin;
4382 }
4383 $origin = (!empty($origin) ? $origin : $this->origin_type);
4384 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4385 $f_user = isset($f_user) ? $f_user : $user;
4386
4387 // Special case
4388 if ($origin == 'order') {
4389 $origin = 'commande';
4390 }
4391 if ($origin == 'invoice') {
4392 $origin = 'facture';
4393 }
4394 if ($origin == 'invoice_template') {
4395 $origin = 'facturerec';
4396 }
4397 if ($origin == 'supplierorder') {
4398 $origin = 'order_supplier';
4399 }
4400
4401 // Add module part to target type
4402 $targettype = $this->getElementType();
4403
4404 $parameters = array('targettype' => $targettype);
4405 // Hook for explicitly set the targettype if it must be different than $this->element
4406 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4407 if ($reshook > 0) {
4408 if (!empty($hookmanager->resArray['targettype'])) {
4409 $targettype = $hookmanager->resArray['targettype'];
4410 }
4411 }
4412
4413 $this->db->begin();
4414 $error = 0;
4415
4416 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4417 $sql .= "fk_source";
4418 $sql .= ", sourcetype";
4419 $sql .= ", fk_target";
4420 $sql .= ", targettype";
4421 $sql .= ") VALUES (";
4422 $sql .= ((int) $origin_id);
4423 $sql .= ", '" . $this->db->escape($origin) . "'";
4424 $sql .= ", " . ((int) $this->id);
4425 $sql .= ", '" . $this->db->escape($targettype) . "'";
4426 $sql .= ")";
4427
4428 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4429 if ($this->db->query($sql)) {
4430 // If we link a supplier order to a supplier invoice that already uses discounts coming from other supplier invoices
4431 // (deposit invoice / credit note used as payment), also link those source invoices to the same supplier order.
4432 // This helps keep "linked elements" consistent when totals are impacted by such discounts.
4433 if ($origin === 'order_supplier' && $targettype === 'invoice_supplier' && $this->element === 'invoice_supplier' && (int) $origin_id > 0 && (int) $this->id > 0) {
4434 $sourceinvoices = array();
4435
4436 $sqlsources = "SELECT DISTINCT rc.fk_invoice_supplier_source as rowid";
4437 $sqlsources .= " FROM ".$this->db->prefix()."societe_remise_except as rc";
4438 $sqlsources .= " WHERE rc.fk_invoice_supplier = ".((int) $this->id);
4439 $sqlsources .= " AND rc.fk_invoice_supplier_source IS NOT NULL";
4440
4441 $resqlsources = $this->db->query($sqlsources);
4442 if ($resqlsources) {
4443 while ($objsrc = $this->db->fetch_object($resqlsources)) {
4444 $srcid = (int) $objsrc->rowid;
4445 if ($srcid > 0 && $srcid !== (int) $this->id) {
4446 $sourceinvoices[$srcid] = $srcid;
4447 }
4448 }
4449 } else {
4450 dol_syslog(get_class($this)."::add_object_linked Failed to read supplier invoice discounts sources", LOG_WARNING);
4451 }
4452
4453 if (!empty($sourceinvoices)) {
4454 $existing = array();
4455 $sourcelist = implode(',', $sourceinvoices);
4456
4457 $sqlexists = "SELECT fk_target";
4458 $sqlexists .= " FROM ".$this->db->prefix()."element_element";
4459 $sqlexists .= " WHERE fk_source = ".((int) $origin_id);
4460 $sqlexists .= " AND sourcetype = '".$this->db->escape($origin)."'";
4461 $sqlexists .= " AND targettype = '".$this->db->escape($targettype)."'";
4462 $sqlexists .= " AND fk_target IN (".$this->db->sanitize($sourcelist).")";
4463
4464 $resqlexists = $this->db->query($sqlexists);
4465 if ($resqlexists) {
4466 while ($objexist = $this->db->fetch_object($resqlexists)) {
4467 $existing[(int) $objexist->fk_target] = 1;
4468 }
4469 } else {
4470 dol_syslog(get_class($this)."::add_object_linked Failed to read existing linked supplier invoices", LOG_WARNING);
4471 }
4472
4473 foreach ($sourceinvoices as $srcid) {
4474 if (!empty($existing[(int) $srcid])) {
4475 continue;
4476 }
4477
4478 $sqladd = "INSERT INTO " . $this->db->prefix() . "element_element (fk_source, sourcetype, fk_target, targettype)";
4479 $sqladd .= " VALUES (".((int) $origin_id).", '".$this->db->escape($origin)."', ".((int) $srcid).", '".$this->db->escape($targettype)."')";
4480 $this->db->query($sqladd); // Best-effort: do not fail original link action
4481 }
4482 }
4483 }
4484
4485 if (!$notrigger) {
4486 // Call trigger
4487 $this->context['link_origin'] = $origin;
4488 $this->context['link_origin_id'] = $origin_id;
4489
4490 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4491 if ($result < 0) {
4492 $error++;
4493 }
4494 // End call triggers
4495 }
4496 } else {
4497 $this->error = $this->db->lasterror();
4498 $error++;
4499 }
4500
4501 if (!$error) {
4502 $this->db->commit();
4503 return 1;
4504 } else {
4505 $this->db->rollback();
4506 return 0;
4507 }
4508 }
4509
4515 public function getElementType()
4516 {
4517 // 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.
4518 // 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).
4519 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4520 // Add module part to target type if object has $module property and isn't in core modules.
4521 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4522 }
4523
4524
4547 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4548 {
4549 global $hookmanager, $action;
4550
4551 // Important for pdf generation time reduction
4552 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4553 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4554 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4555 return 1;
4556 }
4557
4558 $this->linkedObjectsIds = array();
4559 $this->linkedObjects = array();
4560
4561 // Hook for allowing modules to completely alter the behavior of the method
4562 $parameters = array(
4563 'sourceid' => $sourceid,
4564 'sourcetype' => $sourcetype,
4565 'targetid' => $targetid,
4566 'targettype' => $targettype,
4567 'clause' => $clause,
4568 'alsosametype' => $alsosametype,
4569 'orderby' => $orderby,
4570 'loadalsoobjects' => $loadalsoobjects
4571 );
4572 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4573 if ($reshook > 0) {
4574 return $reshook;
4575 }
4576
4577 $justsource = false;
4578 $justtarget = false;
4579 $withtargettype = false;
4580 $withsourcetype = false;
4581
4582 // Hook for explicitly set the targettype if it must be differtent than $this->element
4583 if (is_object($hookmanager)) {
4584 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4585 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4586 if ($reshook > 0) {
4587 if (!empty($hookmanager->resArray['sourcetype'])) {
4588 $sourcetype = $hookmanager->resArray['sourcetype'];
4589 }
4590 if (!empty($hookmanager->resArray['sourceid'])) {
4591 $sourceid = $hookmanager->resArray['sourceid'];
4592 }
4593 if (!empty($hookmanager->resArray['targettype'])) {
4594 $targettype = $hookmanager->resArray['targettype'];
4595 }
4596 if (!empty($hookmanager->resArray['targetid'])) {
4597 $targetid = $hookmanager->resArray['targetid'];
4598 }
4599 }
4600 }
4601
4602 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4603 $justsource = true; // the source (id and type) is a search criteria
4604 if (!empty($targettype)) {
4605 $withtargettype = true;
4606 }
4607 }
4608 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4609 $justtarget = true; // the target (id and type) is a search criteria
4610 if (!empty($sourcetype)) {
4611 $withsourcetype = true;
4612 }
4613 }
4614
4615 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4616 $targetid = (!empty($targetid) ? $targetid : $this->id);
4617 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4618 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4619
4620 /*if (empty($sourceid) && empty($targetid))
4621 {
4622 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4623 return -1;
4624 }*/
4625
4626 // Links between objects are stored in table element_element
4627 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4628 $sql .= " FROM ".$this->db->prefix()."element_element";
4629 $sql .= " WHERE ";
4630 if ($justsource || $justtarget) {
4631 if ($justsource) {
4632 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4633 if ($withtargettype) {
4634 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4635 }
4636 } elseif ($justtarget) {
4637 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4638 if ($withsourcetype) {
4639 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4640 }
4641 }
4642 } else {
4643 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4644 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4645 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4646 $this->linkedObjectsFullLoaded[$this->id] = true;
4647 }
4648 }
4649 $sql .= $this->db->order($orderby);
4650
4651 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4652
4653 $resql = $this->db->query($sql);
4654 if ($resql) {
4655 $num = $this->db->num_rows($resql);
4656 $i = 0;
4657 while ($i < $num) {
4658 $obj = $this->db->fetch_object($resql);
4659 if ($justsource || $justtarget) {
4660 if ($justsource) {
4661 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4662 } elseif ($justtarget) {
4663 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4664 }
4665 } else {
4666 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4667 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4668 }
4669 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4670 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4671 }
4672 }
4673 $i++;
4674 }
4675
4676 if (!empty($this->linkedObjectsIds)) {
4677 $tmparray = $this->linkedObjectsIds;
4678 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4679 $element_properties = getElementProperties($objecttype);
4680 $element = $element_properties['element'];
4681 $classPath = $element_properties['classpath'];
4682 $classFile = $element_properties['classfile'];
4683 $className = $element_properties['classname'];
4684 $module = $element_properties['module'];
4685
4686 // Here $module, $classFile and $className are set, we can use them.
4687 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4688 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4689 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4690 if (class_exists($className)) {
4691 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4692 $object = new $className($this->db);
4693 '@phan-var-force CommonObject $object';
4694 $ret = $object->fetch($objectid);
4695 if ($ret > 0) {
4696 $this->linkedObjects[$objecttype][$i] = $object;
4697 }
4698 }
4699 }
4700 }
4701 } else {
4702 unset($this->linkedObjectsIds[$objecttype]);
4703 }
4704 }
4705 }
4706 return 1;
4707 } else {
4708 dol_print_error($this->db);
4709 return -1;
4710 }
4711 }
4712
4719 public function clearObjectLinkedCache()
4720 {
4721 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4722 unset($this->linkedObjectsFullLoaded[$this->id]);
4723 }
4724
4725 return 1;
4726 }
4727
4740 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4741 {
4742 global $user;
4743 $updatesource = false;
4744 $updatetarget = false;
4745 $f_user = isset($f_user) ? $f_user : $user;
4746 dol_syslog(__METHOD__, LOG_DEBUG);
4747
4748 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4749 $updatesource = true;
4750 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4751 $updatetarget = true;
4752 }
4753
4754 $this->db->begin();
4755 $error = 0;
4756
4757 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4758 if ($updatesource) {
4759 $sql .= "fk_source = " . ((int) $sourceid);
4760 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4761 $sql .= " WHERE fk_target = " . ((int) $this->id);
4762 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4763 } elseif ($updatetarget) {
4764 $sql .= "fk_target = " . ((int) $targetid);
4765 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4766 $sql .= " WHERE fk_source = " . ((int) $this->id);
4767 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4768 }
4769
4770 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4771 if ($this->db->query($sql)) {
4772 if (!$notrigger) {
4773 // Call trigger
4774 $this->context['link_source_id'] = $sourceid;
4775 $this->context['link_source_type'] = $sourcetype;
4776 $this->context['link_target_id'] = $targetid;
4777 $this->context['link_target_type'] = $targettype;
4778
4779 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4780 if ($result < 0) {
4781 $error++;
4782 }
4783 // End call triggers
4784 }
4785 } else {
4786 $this->error = $this->db->lasterror();
4787 $error++;
4788 }
4789
4790 if (!$error) {
4791 $this->db->commit();
4792 return 1;
4793 } else {
4794 $this->db->rollback();
4795 return -1;
4796 }
4797 }
4798
4812 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4813 {
4814 global $user;
4815 $deletesource = false;
4816 $deletetarget = false;
4817 $f_user = isset($f_user) ? $f_user : $user;
4818
4819 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4820 $deletesource = true;
4821 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4822 $deletetarget = true;
4823 }
4824
4825 $element = $this->getElementType();
4826 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4827 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4828 $targetid = (!empty($targetid) ? $targetid : $this->id);
4829 $targettype = (!empty($targettype) ? $targettype : $element);
4830 $this->db->begin();
4831 $error = 0;
4832
4833 if (!$notrigger) {
4834 // Call trigger
4835 $this->context['link_id'] = $rowid;
4836 $this->context['link_source_id'] = $sourceid;
4837 $this->context['link_source_type'] = $sourcetype;
4838 $this->context['link_target_id'] = $targetid;
4839 $this->context['link_target_type'] = $targettype;
4840
4841 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4842 if ($result < 0) {
4843 $error++;
4844 }
4845 // End call triggers
4846 }
4847
4848 if (!$error) {
4849 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4850 $sql .= " WHERE";
4851 if ($rowid > 0) {
4852 $sql .= " rowid = " . ((int) $rowid);
4853 } else {
4854 if ($deletesource) {
4855 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4856 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4857 } elseif ($deletetarget) {
4858 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4859 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4860 } else {
4861 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4862 $sql .= " OR";
4863 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4864 }
4865 }
4866
4867 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4868 if (!$this->db->query($sql)) {
4869 $this->error = $this->db->lasterror();
4870 $this->errors[] = $this->error;
4871 $error++;
4872 }
4873 }
4874
4875 if (!$error) {
4876 $this->db->commit();
4877 return 1;
4878 } else {
4879 $this->db->rollback();
4880 return 0;
4881 }
4882 }
4883
4893 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4894 {
4895 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4896 return -1;
4897 }
4898 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4899 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4900 }
4901
4902 global $db;
4903
4904 $sql = "SELECT ".$db->sanitize($field_select)." FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4905 $resql = $db->query($sql);
4906
4907 $TRes = array();
4908 if (!empty($resql)) {
4909 while ($res = $db->fetch_object($resql)) {
4910 $TRes[] = $res->{$field_select};
4911 }
4912 }
4913
4914 return $TRes;
4915 }
4916
4925 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4926 {
4927 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4928 return -1;
4929 }
4930
4931 global $db;
4932
4933 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4934 $resql = $db->query($sql);
4935 $n = 0;
4936 if ($resql) {
4937 $res = $db->fetch_object($resql);
4938 if ($res) {
4939 $n = $res->nb;
4940 }
4941 }
4942
4943 return $n;
4944 }
4945
4954 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4955 {
4956 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4957 return -1;
4958 }
4959
4960 global $db;
4961
4962 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4963 $resql = $db->query($sql);
4964
4965 if (empty($resql)) {
4966 return 0;
4967 }
4968
4969 return 1;
4970 }
4971
4983 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
4984 {
4985 global $user;
4986
4987 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4988
4989 $elementId = (!empty($elementId) ? $elementId : $this->id);
4990 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4991
4992 $this->db->begin();
4993
4994 if (empty($fieldstatus)) {
4995 if ($elementTable == 'facture_rec') {
4996 $fieldstatus = "suspended";
4997 }
4998 if ($elementTable == 'mailing') {
4999 $fieldstatus = "statut";
5000 }
5001 if ($elementTable == 'cronjob') {
5002 $fieldstatus = "status";
5003 }
5004 if ($elementTable == 'user') {
5005 $fieldstatus = "statut";
5006 }
5007 if ($elementTable == 'expensereport') {
5008 $fieldstatus = "fk_statut";
5009 }
5010 if ($elementTable == 'receptiondet_batch') {
5011 $fieldstatus = "status";
5012 }
5013 if ($elementTable == 'prelevement_bons') {
5014 $fieldstatus = "statut";
5015 }
5016 if ($elementTable == 'bank_account') {
5017 $fieldstatus = "clos";
5018 }
5019 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
5020 $fieldstatus = 'status';
5021 }
5022 // If still empty
5023 if (empty($fieldstatus)) {
5024 $fieldstatus = 'fk_statut';
5025 }
5026 }
5027
5028 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
5029 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
5030 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
5031 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
5032 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
5033 $sql .= ", fk_user_valid = ".((int) $user->id);
5034 }
5035 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
5036 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
5037 }
5038 if ($status == 1 && in_array($elementTable, array('inventory'))) {
5039 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
5040 }
5041 $sql .= " WHERE rowid = ".((int) $elementId);
5042 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
5043
5044 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
5045 $resql = $this->db->query($sql);
5046 if ($resql) {
5047 $error = 0;
5048
5049 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
5050
5051 if ($nb_rows_affected > 0) {
5052 if (empty($trigkey)) {
5053 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5054 if ($this->element == 'supplier_proposal' && $status == 2) {
5055 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5056 }
5057 if ($this->element == 'supplier_proposal' && $status == 3) {
5058 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5059 }
5060 if ($this->element == 'supplier_proposal' && $status == 4) {
5061 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5062 }
5063 if ($this->element == 'fichinter' && $status == 3) {
5064 $trigkey = 'FICHINTER_CLASSIFY_DONE';
5065 }
5066 if ($this->element == 'fichinter' && $status == 2) {
5067 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5068 }
5069 if ($this->element == 'fichinter' && $status == 1) {
5070 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5071 }
5072 }
5073
5074 $this->context = array_merge($this->context, array('newstatus' => $status));
5075
5076 if ($trigkey && $trigkey != 'none') {
5077 $this->oldcopy = dol_clone($this);
5078
5079 // Call trigger
5080 $result = $this->call_trigger($trigkey, $user);
5081 if ($result < 0) {
5082 $error++;
5083 }
5084 // End call triggers
5085 }
5086 } else {
5087 // The status was probably already good. We do nothing more, no triggers.
5088 }
5089
5090 if (!$error) {
5091 $this->db->commit();
5092
5093 if (empty($savElementId)) {
5094 // If the element we update is $this (so $elementId was provided as null)
5095 if ($fieldstatus == 'dispute_status') {
5096 $this->dispute_status = $status;
5097 } elseif ($fieldstatus == 'tosell') {
5098 $this->status = $status;
5099 } elseif ($fieldstatus == 'tobuy') {
5100 $this->status_buy = $status; // @phpstan-ignore-line
5101 } elseif ($fieldstatus == 'tobatch') {
5102 $this->status_batch = $status; // @phpstan-ignore-line
5103 } else {
5104 $this->status = $status;
5105 }
5106 }
5107
5108 return 1;
5109 } else {
5110 $this->db->rollback();
5111 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5112 return -1;
5113 }
5114 } else {
5115 $this->error = $this->db->lasterror();
5116 $this->db->rollback();
5117 return -1;
5118 }
5119 }
5120
5121
5129 public function getCanvas($id = 0, $ref = '')
5130 {
5131 if (empty($id) && empty($ref)) {
5132 return 0;
5133 }
5134 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5135 return 0; // To increase speed. Not enabled by default.
5136 }
5137
5138 // Clean parameters
5139 $ref = trim($ref);
5140
5141 $sql = "SELECT rowid, canvas";
5142 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5143 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5144 if (!empty($id)) {
5145 $sql .= " AND rowid = ".((int) $id);
5146 }
5147 if (!empty($ref)) {
5148 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5149 }
5150
5151 $resql = $this->db->query($sql);
5152 if ($resql) {
5153 $obj = $this->db->fetch_object($resql);
5154 if ($obj) {
5155 $this->canvas = $obj->canvas;
5156 return 1;
5157 } else {
5158 return 0;
5159 }
5160 } else {
5161 dol_print_error($this->db);
5162 return -1;
5163 }
5164 }
5165
5166
5173 public function getSpecialCode($lineid)
5174 {
5175 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
5176 $sql .= " WHERE rowid = ".((int) $lineid);
5177 $resql = $this->db->query($sql);
5178 if ($resql) {
5179 $row = $this->db->fetch_row($resql);
5180 return (!empty($row[0]) ? $row[0] : 0);
5181 }
5182
5183 return 0;
5184 }
5185
5194 public function isObjectUsed($id = 0, $entity = 0)
5195 {
5196 global $langs;
5197
5198 if (empty($id)) {
5199 $id = $this->id;
5200 }
5201
5202 // Check parameters
5203 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5204 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5205 return -1;
5206 }
5207
5208 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5209 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5210 $tmparray = array_keys($this->childtables);
5211 if (is_numeric($tmparray[0])) {
5212 $arraytoscan = array_flip($this->childtables);
5213 }
5214
5215 // Test on all child tables to scan if child exists
5216 $haschild = 0;
5217 foreach ($arraytoscan as $table => $element) {
5218 //print $id.'-'.$table.'-'.$elementname.'<br>';
5219
5220 // Check if module is enabled (to avoid error if tables of module not created)
5221 if (isset($element['enabled']) && !empty($element['enabled'])) {
5222 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5223 if (empty($enabled)) {
5224 continue;
5225 }
5226 }
5227
5228 // Check if element can be deleted
5229 $sql = "SELECT COUNT(*) as nb";
5230 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($table)." as c";
5231 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5232 $sql .= ", ".$this->db->prefix().$this->db->sanitize($element['parent'])." as p";
5233 }
5234 if (!empty($element['fk_element'])) {
5235 $sql .= " WHERE c.".$this->db->sanitize($element['fk_element'])." = ".((int) $id);
5236 } else {
5237 $sql .= " WHERE c.".$this->db->sanitize($this->fk_element)." = ".((int) $id);
5238 }
5239 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5240 $sql .= " AND c.".$this->db->sanitize($element['parentkey'])." = p.rowid";
5241 }
5242 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5243 $sql .= " AND c.".$this->db->sanitize($element['parenttypefield'])." = '".$this->db->escape($element['parenttypevalue'])."'";
5244 }
5245 if (!empty($entity)) {
5246 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5247 $sql .= " AND p.entity = ".((int) $entity);
5248 } else {
5249 $sql .= " AND c.entity = ".((int) $entity);
5250 }
5251 }
5252 //var_dump($table, $element, $sql);
5253
5254 $resql = $this->db->query($sql);
5255 if ($resql) {
5256 $obj = $this->db->fetch_object($resql);
5257 if ($obj->nb > 0) {
5258 $langs->load("errors");
5259 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5260 $haschild += (int) $obj->nb;
5261 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5262 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5263 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5264 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5265 } else { // new usage: $element['name']=Translation key
5266 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5267 }
5268 break; // We found at least one, we stop here
5269 }
5270 } else {
5271 $this->errors[] = $this->db->lasterror();
5272 return -1;
5273 }
5274 }
5275
5276 if ($haschild > 0) {
5277 $this->errors[] = "ErrorRecordHasChildren";
5278 return $haschild;
5279 } else {
5280 return 0;
5281 }
5282 }
5283
5290 public function hasProductsOrServices($predefined = -1)
5291 {
5292 $nb = 0;
5293
5294 foreach ($this->lines as $key => $val) {
5295 $qualified = 0;
5296 if ($predefined == -1) {
5297 $qualified = 1;
5298 }
5299 if ($predefined == 1 && $val->fk_product > 0) {
5300 $qualified = 1;
5301 }
5302 if ($predefined == 0 && $val->fk_product <= 0) {
5303 $qualified = 1;
5304 }
5305 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5306 $qualified = 1;
5307 }
5308 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5309 $qualified = 1;
5310 }
5311 if ($qualified) {
5312 $nb++;
5313 }
5314 }
5315 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5316 return $nb;
5317 }
5318
5324 public function getTotalDiscount()
5325 {
5326 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5327 $total_discount = 0.00;
5328
5329 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5330 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5331 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
5332
5333 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5334 $resql = $this->db->query($sql);
5335 if ($resql) {
5336 $num = $this->db->num_rows($resql);
5337 $i = 0;
5338 while ($i < $num) {
5339 $obj = $this->db->fetch_object($resql);
5340
5341 $pu_ht = $obj->pu_ht;
5342 $qty = $obj->qty;
5343 $total_ht = $obj->total_ht;
5344
5345 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5346 $total_discount += $total_discount_line;
5347
5348 $i++;
5349 }
5350 }
5351
5352 //print $total_discount; exit;
5353 return (float) price2num($total_discount);
5354 }
5355
5356 return null;
5357 }
5358
5359
5366 public function getTotalWeightVolume()
5367 {
5368 $totalWeight = 0;
5369 $totalVolume = 0;
5370 // defined for shipment only
5371 $totalOrdered = 0;
5372 // defined for shipment only
5373 $totalToShip = 0;
5374
5375 if (empty($this->lines)) {
5376 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5377 }
5378
5379 foreach ($this->lines as $line) {
5380 if (isset($line->qty_asked)) {
5381 $totalOrdered += $line->qty_asked; // defined for shipment only
5382 }
5383 if (isset($line->qty_shipped)) {
5384 $totalToShip += $line->qty_shipped; // defined for shipment only
5385 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5386 if (empty($totalToShip)) {
5387 $totalToShip = 0;
5388 }
5389 $totalToShip += $line->qty; // defined for reception only
5390 }
5391
5392 // Define qty, weight, volume, weight_units, volume_units
5393 if ($this->element == 'shipping') {
5394 // for shipments
5395 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5396 } else {
5397 $qty = $line->qty ? $line->qty : 0;
5398 }
5399
5400 $weight = !empty($line->weight) ? $line->weight : 0;
5401 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5402 $volume = !empty($line->volume) ? $line->volume : 0;
5403 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5404
5405 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5406 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5407 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5408 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5409
5410 $weightUnit = 0;
5411 $volumeUnit = 0;
5412 if (!empty($weight_units)) {
5413 $weightUnit = $weight_units;
5414 }
5415 if (!empty($volume_units)) {
5416 $volumeUnit = $volume_units;
5417 }
5418
5419 if (empty($totalWeight)) {
5420 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5421 }
5422 if (empty($totalVolume)) {
5423 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5424 }
5425
5426 //var_dump($line->volume_units);
5427 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5428 $trueWeightUnit = pow(10, $weightUnit);
5429 $totalWeight += $weight * $qty * $trueWeightUnit;
5430 } else {
5431 if ($weight_units == 99) {
5432 // conversion 1 Pound = 0.45359237 KG
5433 $trueWeightUnit = 0.45359237;
5434 $totalWeight += $weight * $qty * $trueWeightUnit;
5435 } elseif ($weight_units == 98) {
5436 // conversion 1 Ounce = 0.0283495 KG
5437 $trueWeightUnit = 0.0283495;
5438 $totalWeight += $weight * $qty * $trueWeightUnit;
5439 } else {
5440 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5441 }
5442 }
5443 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5444 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5445 $trueVolumeUnit = pow(10, $volumeUnit);
5446 //print $line->volume;
5447 $totalVolume += $volume * $qty * $trueVolumeUnit;
5448 } else {
5449 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5450 }
5451 }
5452
5453 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5454 }
5455
5456
5462 public function setExtraParameters()
5463 {
5464 $this->db->begin();
5465
5466 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5467 $extraparams = dol_trunc($extraparams, 250);
5468
5469 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5470 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5471 $sql .= " WHERE rowid = ".((int) $this->id);
5472
5473 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5474 $resql = $this->db->query($sql);
5475 if (!$resql) {
5476 $this->error = $this->db->lasterror();
5477 $this->db->rollback();
5478 return -1;
5479 } else {
5480 $this->db->commit();
5481 return 1;
5482 }
5483 }
5484
5485
5486 // --------------------
5487 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5488 // --------------------
5489
5490 /* This is to show add lines */
5491
5501 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5502 {
5503 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5504
5505 // Line extrafield
5506 if (!is_object($extrafields)) {
5507 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5508 $extrafields = new ExtraFields($this->db);
5509 }
5510 $extrafields->fetch_name_optionals_label($this->table_element_line);
5511
5512 // Output template part (modules that overwrite templates must declare this into descriptor)
5513 // Use global variables + $dateSelector + $seller and $buyer
5514 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5515 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5516 foreach ($dirtpls as $module => $reldir) {
5517 if (!empty($module)) {
5518 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5519 } else {
5520 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5521 }
5522
5523 if (empty($conf->file->strict_mode)) {
5524 $res = @include $tpl;
5525 } else {
5526 $res = include $tpl; // for debug
5527 }
5528 if ($res) {
5529 break;
5530 }
5531 }
5532 }
5533
5534
5535
5536 /* This is to show array of line of details */
5537
5538
5553 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5554 {
5555 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5556 // TODO We should not use global var for this
5557 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5558
5559 // Define $usemargins (used by objectline_xxx.tpl.php files)
5560 $usemargins = 0;
5561 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5562 $usemargins = 1;
5563 }
5564
5565 $num = count($this->lines);
5566
5567 // Line extrafield
5568 if (!is_object($extrafields)) {
5569 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5570 $extrafields = new ExtraFields($this->db);
5571 }
5572 $extrafields->fetch_name_optionals_label($this->table_element_line);
5573
5574 if (method_exists($this, 'loadExpeditions')) {
5575 // 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.
5576 $this->loadExpeditions();
5577 }
5578
5579 $parameters = array();
5580 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5581 if (empty($reshook)) {
5582 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5583 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5584 if (empty($reshook)) {
5585 // Output template part (modules that overwrite templates must declare this into descriptor)
5586 // Use global variables + $dateSelector + $seller and $buyer
5587 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5588 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5589 foreach ($dirtpls as $module => $reldir) {
5590 $res = 0;
5591 if (!empty($module)) {
5592 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5593 } else {
5594 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5595 }
5596 if (file_exists($tpl)) {
5597 if (empty($conf->file->strict_mode)) {
5598 $res = @include $tpl;
5599 } else {
5600 $res = include $tpl; // for debug
5601 }
5602 }
5603 if ($res) {
5604 break;
5605 }
5606 }
5607 }
5608
5609 $i = 0;
5610
5611 print "<!-- begin printObjectLines() -->\n";
5612 foreach ($this->lines as $line) {
5613 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5614 //$line->fetch_optionals();
5615
5616 if (is_object($hookmanager)) {
5617 if (empty($line->fk_parent_line)) {
5618 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5619 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5620 } else {
5621 $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);
5622 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5623 }
5624 }
5625 if (empty($reshook)) {
5626 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5627 }
5628
5629 $i++;
5630 }
5631 print "<!-- end printObjectLines() -->\n";
5632 }
5633 }
5634
5652 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5653 {
5654 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5655 global $form;
5656 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5657
5658 // var used into tpl
5659 $text = '';
5660 $description = '';
5661
5662 // Line in view mode
5663 if ($action != 'editline' || $selected != $line->id) {
5664 // Product
5665 if (!empty($line->fk_product) && $line->fk_product > 0) {
5666 $product_static = new Product($this->db);
5667 $product_static->fetch($line->fk_product);
5668
5669 $product_static->ref = (string) $line->ref; //can change ref in hook
5670 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5671
5672 $text = $product_static->getNomUrl(1);
5673
5674 // Define output language and label
5675 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5676 // @phan-suppress-next-line PhanUndeclaredProperty
5677 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5678 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5679 return;
5680 }
5681
5682 $prod = new Product($this->db);
5683 $prod->fetch($line->fk_product);
5684
5685 $outputlangs = $langs;
5686 $newlang = '';
5687 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5688 $newlang = GETPOST('lang_id', 'aZ09');
5689 }
5690 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5691 $newlang = $this->thirdparty->default_lang; // To use language of customer
5692 }
5693 if (!empty($newlang)) {
5694 $outputlangs = new Translate("", $conf);
5695 $outputlangs->setDefaultLang($newlang);
5696 }
5697
5698 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5699 } else {
5700 $label = $line->product_label;
5701 }
5702
5703 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5704 $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.
5705 }
5706
5707 // Recalculate unit price with tax if not defined
5708 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)
5709 // So we calculate an estimated value just to show something on screen.
5710 // Not: the unit price is always for 100% of line, it is not a prorata of situation invoice (when total is)
5711 if ($line->remise_percent != 100) {
5712 $line->subprice_ttc = (float) price2num($line->total_ttc * ($line->situation_percent ? 100 / $line->situation_percent : 1) / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5713 } else {
5714 // Other method is less accurate
5715 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5716 }
5717 }
5718 $line->pu_ttc = $line->subprice_ttc; // deprecated
5719
5720 // Output template part (modules that overwrite templates must declare this into descriptor)
5721 // Use global variables + $dateSelector + $seller and $buyer
5722 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5723
5724 $qty_shipped = 0;
5725 if (isset($this->expeditions[$line->id])) {
5726 $qty_shipped = $this->expeditions[$line->id];
5727 }
5728 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5729
5730 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5731 foreach ($dirtpls as $module => $reldir) {
5732 $res = 0;
5733 if (!empty($module)) {
5734 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5735 } else {
5736 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5737 }
5738 //var_dump($tpl);
5739 if (file_exists($tpl)) {
5740 if (empty($conf->file->strict_mode)) {
5741 $res = @include $tpl;
5742 } else {
5743 $res = include $tpl; // for debug
5744 }
5745 }
5746 if ($res) {
5747 break;
5748 }
5749 }
5750 }
5751
5752 // Line in update mode
5753 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5754 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5755
5756 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5757 $line->pu_ttc = $line->subprice_ttc; // deprecated
5758
5759 // Output template part (modules that overwrite templates must declare this into descriptor)
5760 // Use global variables + $dateSelector + $seller and $buyer
5761 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5762 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5763 foreach ($dirtpls as $module => $reldir) {
5764 if (!empty($module)) {
5765 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5766 } else {
5767 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5768 }
5769
5770 if (empty($conf->file->strict_mode)) {
5771 $res = @include $tpl;
5772 } else {
5773 $res = include $tpl; // for debug
5774 }
5775 if ($res) {
5776 break;
5777 }
5778 }
5779 }
5780 }
5781
5782
5783 /* This is to show array of line of details of source object */
5784
5785
5796 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5797 {
5798 global $langs, $hookmanager, $form, $action;
5799
5800 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5801 print '<tr class="liste_titre">';
5802 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5803 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5804 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5805 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5806 if (isModEnabled("multicurrency")) {
5807 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5808 }
5809 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5810 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5811 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5812 }
5813 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5814 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5815 print '<td class="center">';
5816 print $form->showCheckAddButtons('checkforselect', 1);
5817 print '</td>';
5818 print '</tr>';
5819 $i = 0;
5820
5821 if (!empty($this->lines)) {
5822 foreach ($this->lines as $line) {
5823 $reshook = 0;
5824 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5825 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5826 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5827 if (!empty($line->fk_parent_line)) {
5828 $parameters['fk_parent_line'] = $line->fk_parent_line;
5829 }
5830 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5831 }
5832 if (empty($reshook)) {
5833 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5834 }
5835
5836 $i++;
5837 }
5838 }
5839 }
5840
5854 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5855 {
5856 global $langs, $conf;
5857
5858 //var_dump($line);
5859 if (!empty($line->date_start)) {
5860 // @phan-suppress-next-line PhanUndeclaredProperty
5861 $date_start = $line->date_start;
5862 } else {
5863 $date_start = $line->date_debut_prevue;
5864 if ($line->date_debut_reel) {
5865 $date_start = $line->date_debut_reel;
5866 }
5867 }
5868 if (!empty($line->date_end)) {
5869 // @phan-suppress-next-line PhanUndeclaredProperty
5870 $date_end = $line->date_end;
5871 } else {
5872 $date_end = $line->date_fin_prevue;
5873 if ($line->date_fin_reel) {
5874 $date_end = $line->date_fin_reel;
5875 }
5876 }
5877
5878 // Set thevalue into ->tpl[] array.
5879 $this->tpl['id'] = $line->id;
5880
5881 $this->tpl['label'] = '';
5882 if (!empty($line->fk_parent_line)) {
5883 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5884 }
5885
5886 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5887 $discount = new DiscountAbsolute($this->db);
5888 if (property_exists($this, 'socid')) {
5889 $discount->fk_soc = $this->socid;
5890 $discount->socid = $this->socid;
5891 }
5892 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5893 } elseif (!empty($line->fk_product)) {
5894 $productstatic = new Product($this->db);
5895 $productstatic->id = $line->fk_product;
5896 $productstatic->ref = (string) $line->ref;
5897 $productstatic->type = $line->fk_product_type;
5898 if (empty($productstatic->ref)) {
5899 $line->fetch_product();
5900 $productstatic = $line->product;
5901 }
5902
5903 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5904 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5905 // Dates
5906 if ($line->product_type == 1 && ($date_start || $date_end)) {
5907 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5908 }
5909 } else {
5910 $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"')));
5911 if (!empty($line->desc)) {
5912 $this->tpl['label'] .= $line->desc;
5913 } else {
5914 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5915 }
5916
5917 // Dates
5918 if ($line->product_type == 1 && ($date_start || $date_end)) {
5919 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5920 }
5921 }
5922
5923 if (!empty($line->desc)) {
5924 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5925 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5926 $discount = new DiscountAbsolute($this->db);
5927 $discount->fetch($line->fk_remise_except);
5928 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5929 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5930 $discount = new DiscountAbsolute($this->db);
5931 $discount->fetch($line->fk_remise_except);
5932 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5933 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5934 $discount = new DiscountAbsolute($this->db);
5935 $discount->fetch($line->fk_remise_except);
5936 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5937 } elseif ($line->desc == '(EXCESS PAID)') {
5938 $discount = new DiscountAbsolute($this->db);
5939 $discount->fetch($line->fk_remise_except);
5940 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5941 } else {
5942 $this->tpl['description'] = dol_trunc($line->desc, 60);
5943 }
5944 } else {
5945 $this->tpl['description'] = '&nbsp;';
5946 }
5947
5948 // VAT Rate
5949 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5950 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
5951 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5952 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5953 }
5954
5955 $this->tpl['price'] = price($line->subprice);
5956 $this->tpl['total_ht'] = price($line->total_ht);
5957 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5958 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5959 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5960 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
5961 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
5962 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
5963 }
5964 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
5965
5966 // Is the line strike or not
5967 $this->tpl['strike'] = 0;
5968 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5969 $this->tpl['strike'] = 1;
5970 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
5971 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
5972 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
5973 // Without the guard, printing origin lines from a PO into a vendor
5974 // invoice fatals (#37663). Other tpl files already follow this pattern.
5975 $this->tpl['strike'] = 1;
5976 }
5977
5978 // Output template part (modules that overwrite templates must declare this into descriptor)
5979 // Use global variables + $dateSelector + $seller and $buyer
5980 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5981 foreach ($dirtpls as $module => $reldir) {
5982 if (!empty($module)) {
5983 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5984 } else {
5985 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5986 }
5987
5988 if (empty($conf->file->strict_mode)) {
5989 $res = @include $tpl;
5990 } else {
5991 $res = include $tpl; // for debug
5992 }
5993 if ($res) {
5994 break;
5995 }
5996 }
5997 }
5998
5999
6000 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6012 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
6013 {
6014 // phpcs:enable
6015 global $user;
6016 $this->db->begin();
6017
6018 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
6019 $sql .= "resource_id";
6020 $sql .= ", resource_type";
6021 $sql .= ", element_id";
6022 $sql .= ", element_type";
6023 $sql .= ", busy";
6024 $sql .= ", mandatory";
6025 $sql .= ") VALUES (";
6026 $sql .= ((int) $resource_id);
6027 $sql .= ", '".$this->db->escape($resource_type)."'";
6028 $sql .= ", '".$this->db->escape((string) $this->id)."'";
6029 $sql .= ", '".$this->db->escape($this->element)."'";
6030 $sql .= ", '".$this->db->escape((string) $busy)."'";
6031 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
6032 $sql .= ")";
6033
6034 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
6035 if ($this->db->query($sql)) {
6036 if (!$notrigger) {
6037 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
6038 if ($result < 0) {
6039 $this->db->rollback();
6040 return -1;
6041 }
6042 }
6043 $this->db->commit();
6044 return 1;
6045 } else {
6046 $this->error = $this->db->lasterror();
6047 $this->db->rollback();
6048 return 0;
6049 }
6050 }
6051
6052 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6061 public function delete_resource($rowid, $element = '', $notrigger = 0)
6062 {
6063 // phpcs:enable
6064 global $user;
6065
6066 $this->db->begin();
6067
6068 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6069 $sql .= " WHERE rowid = ".((int) $rowid);
6070
6071 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6072
6073 $resql = $this->db->query($sql);
6074 if (!$resql) {
6075 $this->error = $this->db->lasterror();
6076 $this->db->rollback();
6077 return -1;
6078 } else {
6079 if (!$notrigger) {
6080 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6081 if ($result < 0) {
6082 $this->db->rollback();
6083 return -1;
6084 }
6085 }
6086 $this->db->commit();
6087 return 1;
6088 }
6089 }
6090
6091
6097 public function __clone()
6098 {
6099 // Force a copy of this->lines, otherwise it will point to same object.
6100 if (isset($this->lines) && is_array($this->lines)) {
6101 $nboflines = count($this->lines);
6102 for ($i = 0; $i < $nboflines; $i++) {
6103 if (is_object($this->lines[$i])) {
6104 $this->lines[$i] = clone $this->lines[$i];
6105 }
6106 }
6107 }
6108 }
6109
6123 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6124 {
6125 global $conf, $langs, $hookmanager, $action;
6126
6127 $srctemplatepath = '';
6128
6129 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6130 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6131
6132 if (!empty($reshook)) {
6133 return $reshook;
6134 }
6135
6136 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6137
6138 if (empty($modele)) {
6139 $this->error = 'BadValueForParameterModele';
6140 return -1;
6141 }
6142
6143 // Increase limit for PDF build
6144 $err = error_reporting();
6145 error_reporting(0);
6146 @set_time_limit(120);
6147 error_reporting($err);
6148
6149 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6150 $tmp = explode(':', $modele, 2);
6151 $saved_model = $modele;
6152 if (!empty($tmp[1])) {
6153 $modele = $tmp[0];
6154 $srctemplatepath = $tmp[1];
6155
6156 if (!preg_match('/^'.preg_quote(DOL_DATA_ROOT, '/').'\/(ecm|doctemplates)/', $srctemplatepath)) {
6157 $this->error = 'BadDirForTemplateFile';
6158 return -1;
6159 }
6160 }
6161
6162 // Search template files
6163 $file = '';
6164 $classname = '';
6165 $filefound = '';
6166 $dirmodels = array('/');
6167 if (is_array($conf->modules_parts['models'])) {
6168 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6169 }
6170 foreach ($dirmodels as $reldir) {
6171 foreach (array('doc', 'pdf') as $prefix) {
6172 if (in_array(get_class($this), array('Adherent'))) {
6173 // Member module use prefix_modele.class.php
6174 $file = $prefix."_".$modele.".class.php";
6175 } else {
6176 // Other module use prefix_modele.modules.php
6177 $file = $prefix."_".$modele.".modules.php";
6178 }
6179
6180 $file = dol_sanitizeFileName($file);
6181
6182 // We check if the file exists
6183 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6184 if (file_exists($file)) {
6185 $filefound = $file;
6186 $classname = $prefix.'_'.$modele;
6187 break;
6188 }
6189 }
6190 if ($filefound) {
6191 break;
6192 }
6193 }
6194
6195 if ($filefound === '' || $classname === '') {
6196 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6197 $this->errors[] = $this->error;
6198 dol_syslog($this->error, LOG_ERR);
6199 return -1;
6200 }
6201
6202 // Sanitize $filefound
6203 $filefound = dol_sanitizePathName($filefound);
6204
6205 // If generator was found
6206 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6207
6208 require_once $filefound;
6209
6210 $obj = new $classname($this->db);
6211
6212 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6213 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6214 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6215 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6216 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6217 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6218 // ModelePDFStock|ModelePDFStockTransfer|
6219 // ModeleDon|ModelePDFTask|
6220 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6221 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6222 '@phan-var-force ModelePDFMember $obj';
6223 // '@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';
6224
6225 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6226 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6227 $varfortemplatedir = $obj->scandir;
6228 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6229 $dirtoscan = getDolGlobalString($varfortemplatedir);
6230
6231 $listoffiles = array();
6232
6233 // Now we add first model found in directories scanned
6234 $listofdir = explode(',', $dirtoscan);
6235 foreach ($listofdir as $key => $tmpdir) {
6236 $tmpdir = trim($tmpdir);
6237 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6238 if (!$tmpdir) {
6239 unset($listofdir[$key]);
6240 continue;
6241 }
6242 if (is_dir($tmpdir)) {
6243 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6244 if (count($tmpfiles)) {
6245 $listoffiles = array_merge($listoffiles, $tmpfiles);
6246 }
6247 }
6248 }
6249
6250 if (count($listoffiles)) {
6251 foreach ($listoffiles as $record) {
6252 $srctemplatepath = $record['fullname'];
6253 break;
6254 }
6255 }
6256 }
6257
6258 if (empty($srctemplatepath)) {
6259 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6260 return -1;
6261 }
6262 }
6263
6264 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6265 if (!dol_is_file($srctemplatepath)) {
6266 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6267 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6268 return -1;
6269 }
6270 }
6271
6272 // We save charset_output to restore it because write_file can change it if needed for
6273 // output format that does not support UTF8.
6274 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6275
6276 // update model_pdf in object
6277 $this->model_pdf = $saved_model;
6278
6279 try {
6280 if ($obj instanceof ModelePDFMember) {
6281 if ($this instanceof Adherent) {
6282 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6283 } else {
6284 $resultwritefile = -1;
6285 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6286 }
6287 } elseif ($obj instanceof ModeleDon) {
6288 // Only 3 arguments
6289 if ($this instanceof Don) {
6290 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6291 } else {
6292 $resultwritefile = -1;
6293 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6294 }
6295 } else {
6296 // TODO: Try to set type above again
6297 '@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';
6298 $this->context['moreparams'] = $moreparams;
6299 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref); // @phan-suppress-current-line PhanTypeMismatchArgument
6300 }
6301 } catch (\Throwable $e) {
6302 $resultwritefile = -1;
6303 $this->error = $e->getMessage();
6304 dol_syslog("Exception in write_file() for ".get_class($this).": ".$e->getMessage(), LOG_ERR);
6305 }
6306 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6307
6308
6309 if ($resultwritefile > 0) {
6310 $outputlangs->charset_output = $sav_charset_output;
6311
6312 // We delete old preview
6313 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6314 dol_delete_preview($this);
6315
6316 // Index file in database
6317 if (!empty($obj->result['fullpath'])) {
6318 $destfull = $obj->result['fullpath'];
6319
6320 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6321 $update_main_doc_field = 0;
6322 if (!empty($obj->update_main_doc_field)) {
6323 $update_main_doc_field = 1;
6324 }
6325
6326 // Check that the file exists, before indexing it.
6327 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6328 if (dol_is_file($destfull)) {
6329 $this->indexFile($destfull, $update_main_doc_field);
6330 }
6331 } else {
6332 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);
6333 }
6334
6335 // Success in building document. We build meta file.
6336 dol_meta_create($this);
6337
6338 $this->warnings = $obj->warnings;
6339
6340 return 1;
6341 } else {
6342 $outputlangs->charset_output = $sav_charset_output;
6343 if (empty($this->error)) {
6344 $this->error = $obj->error;
6345 }
6346 if (empty($this->errors)) {
6347 $this->errors = $obj->errors;
6348 }
6349 $this->warnings = $obj->warnings;
6350 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$this->error, LOG_ERR);
6351 return -1;
6352 }
6353 }
6354
6364 public function indexFile($destfull, $update_main_doc_field)
6365 {
6366 global $conf, $user;
6367
6368 $upload_dir = dirname($destfull);
6369 $destfile = basename($destfull);
6370 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6371
6372 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6373 $filename = basename($destfile);
6374 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6375 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6376
6377 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6378 $ecmfile = new EcmFiles($this->db);
6379 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6380
6381 // Set the public "share" key
6382 $setsharekey = false;
6383 if (
6384 !empty($this->TRIGGER_PREFIX)
6385 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6386 ) {
6387 $setsharekey = true;
6388 }
6389 // TODO Remove case covered by trigger prefix
6390 if ($this->element == 'propal' || $this->element == 'proposal') {
6391 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6392 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6393 }
6394 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6395 $setsharekey = true;
6396 }
6397 }
6398 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6399 $setsharekey = true;
6400 }
6401 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6402 $setsharekey = true;
6403 }
6404 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6405 $setsharekey = true;
6406 }
6407 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6408 $setsharekey = true;
6409 }
6410 if ($this->element == 'order_supplier' && getDolGlobalInt("SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6411 $setsharekey = true;
6412 }
6413 if ($this->element == 'invoice_supplier' && getDolGlobalInt("SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6414 $setsharekey = true;
6415 }
6416 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6417 $setsharekey = true;
6418 }
6419
6420 if ($setsharekey) {
6421 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6422 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6423 $ecmfile->share = getRandomPassword(true);
6424 }
6425 }
6426
6427 if ($result > 0) {
6428 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6429 $ecmfile->fullpath_orig = '';
6430 $ecmfile->gen_or_uploaded = 'generated';
6431 $ecmfile->description = ''; // indexed content
6432 $ecmfile->keywords = ''; // keyword content
6433 $ecmfile->src_object_type = $this->table_element;
6434 $ecmfile->src_object_id = $this->id;
6435 $result = $ecmfile->update($user);
6436 if ($result < 0) {
6437 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6438 return -1;
6439 }
6440 } else {
6441 $ecmfile->entity = $conf->entity;
6442 $ecmfile->filepath = $rel_dir;
6443 $ecmfile->filename = $filename;
6444 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6445 $ecmfile->fullpath_orig = '';
6446 $ecmfile->gen_or_uploaded = 'generated';
6447 $ecmfile->description = ''; // indexed content
6448 $ecmfile->keywords = ''; // keyword content
6449 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6450 $ecmfile->src_object_id = $this->id;
6451
6452 $result = $ecmfile->create($user);
6453 if ($result < 0) {
6454 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6455 return -1;
6456 }
6457 }
6458
6459 /*$this->result['fullname']=$destfull;
6460 $this->result['filepath']=$ecmfile->filepath;
6461 $this->result['filename']=$ecmfile->filename;*/
6462 //var_dump($obj->update_main_doc_field);exit;
6463
6464 if ($update_main_doc_field && !empty($this->table_element)) {
6465 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6466 $sql .= " WHERE rowid = ".((int) $this->id);
6467
6468 $resql = $this->db->query($sql);
6469 if (!$resql) {
6470 dol_print_error($this->db);
6471 return -1;
6472 } else {
6473 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6474 }
6475 }
6476 }
6477
6478 return 1;
6479 }
6480
6489 public function addThumbs($file, $quality = 50)
6490 {
6491 $file_osencoded = dol_osencode($file);
6492
6493 if (file_exists($file_osencoded)) {
6494 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6495
6496 $tmparraysize = getDefaultImageSizes();
6497 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6498 $maxheightsmall = $tmparraysize['maxheightsmall'];
6499 $maxwidthmini = $tmparraysize['maxwidthmini'];
6500 $maxheightmini = $tmparraysize['maxheightmini'];
6501 //$quality = $tmparraysize['quality'];
6502
6503 // Create small thumbs for company (Ratio is near 16/9)
6504 // Used on logon for example
6505 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6506
6507 // Create mini thumbs for company (Ratio is near 16/9)
6508 // Used on menu or for setup page for example
6509 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6510 }
6511 }
6512
6520 public function delThumbs($file)
6521 {
6522 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6523 dol_delete_file($imgThumbName);
6524 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6525 dol_delete_file($imgThumbName);
6526 }
6527
6528
6529 /* Functions common to commonobject and commonobjectline */
6530
6531 /* For default values */
6532
6546 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6547 {
6548 /* TODO Remove this. Must use now something like:
6549 $note_private = GETPOST('note_private', 'restricthtml');
6550 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6551 $note_private = $objectsrc->note_private;
6552 }
6553 */
6554
6555 // If param here has been posted, we use this value first.
6556 if (GETPOSTISSET($fieldname)) {
6557 return GETPOST($fieldname, $type, 3);
6558 }
6559
6560 if (isset($alternatevalue)) {
6561 return $alternatevalue;
6562 }
6563
6564 $newelement = $this->element;
6565 if ($newelement == 'facture') {
6566 $newelement = 'invoice';
6567 }
6568 if ($newelement == 'commande') {
6569 $newelement = 'order';
6570 }
6571 if (empty($newelement)) {
6572 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6573 return '';
6574 }
6575
6576 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6577 //var_dump($keyforfieldname);
6578 if (getDolGlobalString($keyforfieldname)) {
6579 return getDolGlobalString($keyforfieldname);
6580 }
6581
6582 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6583 // store content into $conf->cache['overwrite_default']
6584
6585 return '';
6586 }
6587
6588
6589 /* Functions for data in other language */
6590
6591
6601 {
6602 // To avoid SQL errors. Probably not the better solution though
6603 if (!$this->element) {
6604 return 0;
6605 }
6606 if (!($this->id > 0)) {
6607 return 0;
6608 }
6609 if (is_array($this->array_languages)) {
6610 return 1;
6611 }
6612
6613 $this->array_languages = array();
6614
6615 $element = $this->element;
6616 if ($element == 'categorie') {
6617 $element = 'categories'; // For compatibility
6618 }
6619
6620 // Request to get translation values for object
6621 $sql = "SELECT rowid, property, lang , value";
6622 $sql .= " FROM ".$this->db->prefix()."object_lang";
6623 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6624 $sql .= " AND fk_object = ".((int) $this->id);
6625
6626 $resql = $this->db->query($sql);
6627 if ($resql) {
6628 $numrows = $this->db->num_rows($resql);
6629 if ($numrows) {
6630 $i = 0;
6631 while ($i < $numrows) {
6632 $obj = $this->db->fetch_object($resql);
6633 $key = $obj->property;
6634 $value = $obj->value;
6635 $codelang = $obj->lang;
6636 $type = $this->fields[$key]['type'];
6637
6638 // we can add this attribute to object
6639 if (preg_match('/date/', $type)) {
6640 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6641 } else {
6642 $this->array_languages[$key][$codelang] = $value;
6643 }
6644
6645 $i++;
6646 }
6647 }
6648
6649 $this->db->free($resql);
6650
6651 if ($numrows) {
6652 return $numrows;
6653 } else {
6654 return 0;
6655 }
6656 } else {
6657 dol_print_error($this->db);
6658 return -1;
6659 }
6660 }
6661
6669 public function setValuesForExtraLanguages($onlykey = '')
6670 {
6671 // Get extra fields
6672 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6673 $tmparray = explode('-', $postfieldkey);
6674 if ($tmparray[0] != 'field') {
6675 continue;
6676 }
6677
6678 $element = $tmparray[1];
6679 $key = $tmparray[2];
6680 $codelang = $tmparray[3];
6681 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6682
6683 if (!empty($onlykey) && $key != $onlykey) {
6684 continue;
6685 }
6686 if ($element != $this->element) {
6687 continue;
6688 }
6689
6690 $key_type = $this->fields[$key]['type'];
6691
6692 $enabled = 1;
6693 if (isset($this->fields[$key]['enabled'])) {
6694 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6695 }
6696 /*$perms = 1;
6697 if (isset($this->fields[$key]['perms']))
6698 {
6699 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6700 }*/
6701 if (empty($enabled)) {
6702 continue;
6703 }
6704 //if (empty($perms)) continue;
6705
6706 if (in_array($key_type, array('date'))) {
6707 // Clean parameters
6708 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6709 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6710 } elseif (in_array($key_type, array('datetime'))) {
6711 // Clean parameters
6712 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6713 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6714 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6715 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6716 if (!empty($value_arr)) {
6717 $value_key = implode(',', $value_arr);
6718 } else {
6719 $value_key = '';
6720 }
6721 } elseif (in_array($key_type, array('price', 'double'))) {
6722 $value_arr = GETPOST($postfieldkey, 'alpha');
6723 $value_key = price2num($value_arr);
6724 } else {
6725 $value_key = GETPOST($postfieldkey);
6726 if (in_array($key_type, array('link')) && $value_key == '-1') {
6727 $value_key = '';
6728 }
6729 }
6730
6731 $this->array_languages[$key][$codelang] = $value_key;
6732
6733 /*if ($nofillrequired) {
6734 $langs->load('errors');
6735 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6736 return -1;
6737 }*/
6738 }
6739
6740 return 1;
6741 }
6742
6743
6744 /* Functions for extrafields */
6745
6752 public function fetchNoCompute($id)
6753 {
6754 global $conf;
6755
6756 $savDisableCompute = $conf->disable_compute;
6757 $conf->disable_compute = 1;
6758
6759 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6760
6761 $conf->disable_compute = $savDisableCompute;
6762
6763 return $ret;
6764 }
6765
6766 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6776 public function fetch_optionals($rowid = null, $optionsArray = null)
6777 {
6778 // phpcs:enable
6779 global $conf, $extrafields;
6780
6781 if (empty($rowid)) {
6782 $rowid = $this->id;
6783 }
6784 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6785 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6786 }
6787
6788 // To avoid SQL errors. Probably not the better solution though
6789 if (!$this->table_element) {
6790 return 0;
6791 }
6792 $this->array_options = array();
6793
6794 if (!is_array($optionsArray)) {
6795 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6796 if (!isset($extrafields) || !is_object($extrafields)) {
6797 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6798 $extrafields = new ExtraFields($this->db);
6799 }
6800
6801 // Load array of extrafields for elementype = $this->table_element
6802 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6803 $extrafields->fetch_name_optionals_label($this->table_element);
6804 }
6805 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6806 } else {
6807 global $extrafields;
6808 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6809 }
6810
6811 $table_element = $this->table_element;
6812 if ($table_element == 'categorie') {
6813 $table_element = 'categories'; // For compatibility
6814 }
6815
6816 // Request to get complementary values
6817 if (is_array($optionsArray) && count($optionsArray) > 0) {
6818 $sql = "SELECT rowid";
6819 foreach ($optionsArray as $name => $label) {
6820 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6821 $sql .= ", ".$this->db->sanitize($name);
6822 }
6823 // use geo sql function to read as text
6824 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6825 // TODO Add an abstraction method in the database driver
6826 $sql .= ", ST_AsWKT(".$this->db->sanitize($name).") as ".$this->db->sanitize($name);
6827 }
6828 }
6829 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6830 $sql .= " WHERE fk_object = ".((int) $rowid);
6831
6832 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6833 $resql = $this->db->query($sql);
6834 if ($resql) {
6835 $numrows = $this->db->num_rows($resql);
6836 if ($numrows) {
6837 $tab = $this->db->fetch_array($resql);
6838 foreach ($tab as $key => $value) {
6839 // 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)
6840 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6841 // we can add this attribute to object
6842 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6843 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6844 $this->array_options["options_".$key] = $this->db->jdate($value);
6845 } else {
6846 $this->array_options["options_".$key] = $value;
6847 }
6848
6849 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6850 }
6851 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6852 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6853 $this->array_options["options_".$key] = dolDecrypt($value);
6854 }
6855 }
6856 }
6857 } else {
6862 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6863 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6864 $this->array_options['options_' . $key] = null;
6865 }
6866 }
6867 }
6868
6869 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6870 // in the element's extrafields table)
6871 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6872 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6873 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6874 //var_dump($conf->disable_compute);
6875 if (empty($conf->disable_compute)) {
6876 // We set a global variable to $objectoffield so we can use it inside computed formula (must be before the assignement)
6877 global $objectoffield;
6878 $objectoffield = dol_clone($this, 2);
6879 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6880 }
6881 }
6882 }
6883 }
6884
6885 $this->db->free($resql);
6886
6887 if ($numrows) {
6888 return $numrows;
6889 } else {
6890 return 0;
6891 }
6892 } else {
6893 $this->errors[] = $this->db->lasterror();
6894 return -1;
6895 }
6896 }
6897 return 0;
6898 }
6899
6906 public function deleteExtraFields()
6907 {
6908 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6909 return 0;
6910 }
6911
6912 $this->db->begin();
6913
6914 $table_element = $this->table_element;
6915 if ($table_element == 'categorie') {
6916 $table_element = 'categories'; // For compatibility
6917 }
6918
6919 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6920
6921 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6922
6923 $resql = $this->db->query($sql_del);
6924 if (!$resql) {
6925 $this->error = $this->db->lasterror();
6926 $this->db->rollback();
6927 return -1;
6928 } else {
6929 $this->db->commit();
6930 return 1;
6931 }
6932 }
6933
6944 public function insertExtraFields($trigger = '', $userused = null)
6945 {
6946 global $langs, $extrafields, $user;
6947
6948 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6949 return 0;
6950 }
6951
6952 if (empty($userused)) {
6953 $userused = $user;
6954 }
6955
6956 $error = 0;
6957
6958 // Check parameters
6959 $langs->load('admin');
6960 if (!isset($extrafields) || !is_object($extrafields)) {
6961 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6962 $extrafields = new ExtraFields($this->db);
6963 }
6964 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6965
6966 // Eliminate copied source object extra fields that do not exist in target object
6967 $new_array_options = array();
6968 foreach ($this->array_options as $key => $value) {
6969 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6970 $new_array_options[$key] = $value;
6971 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6972 $new_array_options['options_'.$key] = $value;
6973 }
6974 }
6975
6976 foreach ($new_array_options as $key => $value) {
6977 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6978 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6979 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6980 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6981 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6982 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6983 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6984 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
6985 /*
6986 $attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
6987 $enabled = 0;
6988 if (!empty($extrafields->attributes[$this->table_element]['enabled'][$key])) {
6989 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
6990 }
6991 */
6992
6993 // If we clone, we have to clean unique extrafields to prevent duplicates.
6994 // If we clone, we have to clean extrafields having "empty on clone" option on.
6995 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6996 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
6997 $new_array_options[$key] = null;
6998 continue;
6999 }
7000
7001 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
7002 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
7003 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
7004 $new_array_options[$key] = null;
7005 continue;
7006 }
7007
7008 // Similar code than into insertExtraFields
7009 if ($attributeRequired) { // If attribute is "Mandatory", then it is also in database as "Not null", so we must check even if attribute is not 'enabled'.
7010 $v = $this->array_options[$key];
7011 if (ExtraFields::isEmptyValue($v, $attributeType)) {
7012 $langs->load("errors");
7013 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
7014 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7015 return -1;
7016 }
7017 }
7018
7019 if (!empty($attrfieldcomputed)) {
7020 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7021 global $objectoffield; // We set a global variable to $objectoffield so
7022 $objectoffield = $this; // we can use it inside the computed formula
7023 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7024 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7025 $new_array_options[$key] = $value;
7026 } else {
7027 $new_array_options[$key] = null;
7028 }
7029 continue;
7030 }
7031
7032 switch ($attributeType) {
7033 case 'int':
7034 case 'duration':
7035 case 'stars':
7036 if (!is_numeric($value) && $value != '') {
7037 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7038 return -1;
7039 } elseif ($value === '') {
7040 $new_array_options[$key] = null;
7041 }
7042 break;
7043 case 'boolean':
7044 if ($value === '' || $value === false || $value === null) {
7045 $new_array_options[$key] = null;
7046 }
7047 break;
7048 case 'price':
7049 case 'double':
7050 $value = price2num($value);
7051 if (!is_numeric($value) && $value != '') {
7052 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7053 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7054 return -1;
7055 } elseif ($value == '') {
7056 $value = null;
7057 }
7058 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7059 $new_array_options[$key] = $value;
7060 break;
7061 /*case 'select': // Not required, we chose value='0' for undefined values
7062 if ($value=='-1')
7063 {
7064 $this->array_options[$key] = null;
7065 }
7066 break;*/
7067 case 'password':
7068 $algo = '';
7069 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7070 // If there is an encryption choice, we use it to encrypt data before insert
7071 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7072 $algo = reset($tmparrays);
7073 if ($algo != '') {
7074 //global $action; // $action may be 'create', 'update', 'update_extras'...
7075 //var_dump($action);
7076 //var_dump($this->oldcopy);exit;
7077 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
7078 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7079 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7080 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7081 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7082 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7083 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7084 } else {
7085 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7086 }
7087 } else {
7088 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7089 }
7090 } else {
7091 // If value has changed
7092 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7093 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7094 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7095 } else {
7096 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7097 }
7098 } else {
7099 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7100 }
7101 }
7102 } else {
7103 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7104 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7105 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7106 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7107 } else {
7108 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7109 }
7110 }
7111 } else {
7112 // No encryption
7113 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7114 }
7115 } else { // Common usage
7116 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7117 }
7118 break;
7119 case 'date':
7120 case 'datetime':
7121 // If data is a string instead of a timestamp, we convert it
7122 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7123 $this->array_options[$key] = strtotime($this->array_options[$key]);
7124 }
7125 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7126 break;
7127 case 'datetimegmt':
7128 // If data is a string instead of a timestamp, we convert it
7129 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7130 $this->array_options[$key] = strtotime($this->array_options[$key]);
7131 }
7132 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7133 break;
7134 case 'link':
7135 $param_list = array_keys($attributeParam['options']);
7136 // 0 : ObjectName
7137 // 1 : classPath
7138 $InfoFieldList = explode(":", $param_list[0]);
7139 dol_include_once($InfoFieldList[1]);
7140 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7141 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7142 $new_array_options[$key] = '';
7143 } elseif ($value) {
7144 $object = new $InfoFieldList[0]($this->db);
7145 '@phan-var-force CommonObject $object';
7146
7147 $objectId = 0;
7148
7149 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7150 if (is_numeric($value)) {
7151 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7152 } else {
7153 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7154 }
7155
7156 $obj = $this->db->getRow($sqlFetchObject);
7157
7158 if ($obj !== false) {
7159 $objectId = $obj->rowid;
7160 $res = 1;
7161 } else {
7162 $res = -1;
7163 }
7164
7165 if ($res > 0) {
7166 $new_array_options[$key] = $objectId;
7167 } else {
7168 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7169 return -1;
7170 }
7171 }
7172 } else {
7173 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7174 }
7175 break;
7176 case 'checkbox':
7177 case 'chkbxlst':
7178 if (is_array($this->array_options[$key])) {
7179 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7180 } else {
7181 $new_array_options[$key] = $this->array_options[$key];
7182 }
7183 break;
7184 }
7185 }
7186
7187 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7188 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7189
7190 foreach ($new_array_options as $key => $newValue) {
7191 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7192 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7193 if ($attributeType === 'separate') {
7194 // separator extrafields don't have data per object so they don't have a comlumn in the database
7195 continue;
7196 }
7197 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7198 // Add field of attribute
7199 if (! $geoDataType) {
7200 // not a geodata type
7201 if ($newValue != '') {
7202 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7203 } else {
7204 $sqlColumnValues[$attributeKey] = 'null';
7205 }
7206 continue;
7207 }
7208 if (empty($newValue)) {
7209 $sqlColumnValues[$attributeKey] = 'null';
7210 continue;
7211 }
7212 if (preg_match('/error/i', $newValue)) {
7213 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7214 $sqlColumnValues[$attributeKey] = 'null';
7215 continue;
7216 }
7217
7218 // Geodata type: Text must be a WKT string. Examples:
7219 // - point => "POINT(15 20)"
7220 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7221 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7222 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7223 $sqlColumnValues[$attributeKey] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')"; // @phan-suppress-current-line SqlInjection
7224 }
7225
7226 $this->db->begin();
7227
7228 $table_element = $this->table_element;
7229 if ($table_element == 'categorie') {
7230 $table_element = 'categories'; // For compatibility
7231 }
7232 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7233
7234 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7235
7236 $linealreadyfound = 0;
7237 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7238 $resql = $this->db->query($sql);
7239 if ($resql) {
7240 $tmpobj = $this->db->fetch_object($resql);
7241 if ($tmpobj) {
7242 $linealreadyfound = $tmpobj->nb;
7243 }
7244 }
7245
7246 // if the extrafields row already exists for the object, we update it
7247 if ($linealreadyfound) {
7248 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7249 if (empty($sqlColumnValues)) {
7250 // No column of the target element to update. This happens when the object was created from
7251 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7252 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7253 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7254 // that would produce an invalid SQL statement.
7255 $this->db->commit();
7256 return 1;
7257 }
7258 $sqlColumnValueString = implode(
7259 ',',
7264 array_map(function ($key) use ($sqlColumnValues) {
7265 return "{$key} = {$sqlColumnValues[$key]}";
7266 }, array_keys($sqlColumnValues))
7267 );
7268 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7269 } else {
7270 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7271 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7272 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7273 if (isset($sqlColumnValues[$key])) {
7274 // extrafield value already provided: no need to add it
7275 continue;
7276 }
7277 // default value: empty string, except for 'int', 'double' and 'price'
7278 $sqlColumnValues[$key] = "''";
7279 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7280 $sqlColumnValues[$key] = 0;
7281 }
7282 }
7283 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7284 $sqlValues = implode(',', array_values($sqlColumnValues));
7285 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7286 }
7287
7288 $resql = $this->db->query($sql);
7289 if (!$resql) {
7290 $this->error = $this->db->lasterror();
7291 $error++;
7292 }
7293
7294 // Update also the user of last modification in parent table
7295 if (!$error && !empty($this->fields['fk_user_modif'])) { // @phan-suppress-current-line PhanTypeMismatchProperty
7296 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7297 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7298 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7299 $this->db->query($sql);
7300 }
7301
7302 if (!$error && $trigger) {
7303 // Call trigger
7304 $this->context = array('extrafieldaddupdate' => 1);
7305 $result = $this->call_trigger($trigger, $userused);
7306 if ($result < 0) {
7307 $error++;
7308 }
7309 // End call trigger
7310 }
7311
7312 if ($error) {
7313 $this->db->rollback();
7314 return -1;
7315 } else {
7316 $this->db->commit();
7317 return 1;
7318 }
7319 }
7320
7332 public function insertExtraLanguages($trigger = '', $userused = null)
7333 {
7334 global $langs, $user;
7335
7336 if (empty($userused)) {
7337 $userused = $user;
7338 }
7339
7340 $error = 0;
7341
7342 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7343 return 0; // For avoid conflicts if trigger used
7344 }
7345
7346 if (is_array($this->array_languages)) {
7347 $new_array_languages = $this->array_languages;
7348
7349 foreach ($new_array_languages as $key => $value) {
7350 $attributeKey = $key;
7351 $attributeType = $this->fields[$attributeKey]['type'];
7352 $attributeLabel = $this->fields[$attributeKey]['label'];
7353
7354 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7355 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7356
7357 switch ($attributeType) {
7358 case 'int':
7359 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7360 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7361 return -1;
7362 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7363 $new_array_languages[$key] = null;
7364 }
7365 break;
7366 case 'double':
7367 $value = price2num((string) $value);
7368 if (!is_numeric($value) && $value != '') {
7369 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7370 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7371 return -1;
7372 } elseif ($value == '') {
7373 $new_array_languages[$key] = null;
7374 } else {
7375 $new_array_languages[$key] = $value;
7376 }
7377 break;
7378 /*case 'select': // Not required, we chose value='0' for undefined values
7379 if ($value=='-1')
7380 {
7381 $this->array_options[$key] = null;
7382 }
7383 break;*/
7384 }
7385 }
7386
7387 $this->db->begin();
7388
7389 $table_element = $this->table_element;
7390 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7391 $table_element = 'categories'; // For compatibility
7392 }
7393
7394 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7395
7396 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7397 foreach ($langcodearray as $langcode => $value) {
7398 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7399 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7400 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7401 $this->db->query($sql_del);
7402
7403 if ($value !== '') {
7404 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7405 $sql .= ") VALUES (".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7406 $sql .= ")";
7407
7408 $resql = $this->db->query($sql);
7409 if (!$resql) {
7410 $this->error = $this->db->lasterror();
7411 $error++;
7412 break;
7413 }
7414 }
7415 }
7416 }
7417
7418 if (!$error && $trigger) {
7419 // Call trigger
7420 $this->context = array('extralanguagesaddupdate' => 1);
7421 $result = $this->call_trigger($trigger, $userused);
7422 if ($result < 0) {
7423 $error++;
7424 }
7425 // End call trigger
7426 }
7427
7428 if ($error) {
7429 $this->db->rollback();
7430 return -1;
7431 } else {
7432 $this->db->commit();
7433 return 1;
7434 }
7435 } else {
7436 return 0;
7437 }
7438 }
7439
7450 public function updateExtraField($key, $trigger = null, $userused = null)
7451 {
7452 global $langs, $user, $extrafields, $hookmanager;
7453
7454 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7455 return 0;
7456 }
7457
7458 if (empty($userused)) {
7459 $userused = $user;
7460 }
7461
7462 $error = 0;
7463
7464 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7465 // Check parameters
7466 $langs->load('admin');
7467 if (!isset($extrafields) || !is_object($extrafields)) {
7468 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7469 $extrafields = new ExtraFields($this->db);
7470 }
7471 $extrafields->fetch_name_optionals_label($this->table_element);
7472 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7473 // Extrafield not defined for this object type: nothing to update
7474 return 0;
7475 }
7476 $value = $this->array_options["options_".$key];
7477
7478 $attributeKey = $key;
7479 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7480 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7481 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7482 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7483 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7484 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7485 //$attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7486
7487 // Similar code than into insertExtraFields
7488 if ($attributeRequired) { // If attribute is "Mandatory", then it is also in database as "Not null", so we must check even if attribute is not 'enabled'.
7489 $mandatorypb = false;
7490 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7491 $mandatorypb = true;
7492 }
7493 if ($this->array_options["options_".$key] === '') {
7494 $mandatorypb = true;
7495 }
7496 if ($mandatorypb) {
7497 $langs->load("errors");
7498 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7499 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7500 return -1;
7501 }
7502 }
7503
7504 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7505 $new_array_options = $this->array_options;
7506
7507 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7508 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7509 if (!empty($attrfieldcomputed)) {
7510 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7511 global $objectoffield; // We set a global variable to $objectoffield so
7512 $objectoffield = $this; // we can use it inside the computed formula
7513 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7514 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7515
7516 $new_array_options["options_".$key] = $value;
7517
7518 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7519 } else {
7520 $new_array_options["options_".$key] = null;
7521
7522 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7523 }
7524 }
7525
7526 switch ($attributeType) {
7527 case 'int':
7528 if (!is_numeric($value) && $value != '') {
7529 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7530 return -1;
7531 } elseif ($value === '') {
7532 $new_array_options["options_".$key] = null;
7533
7534 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7535 }
7536 break;
7537 case 'price':
7538 case 'double':
7539 $value = price2num($value);
7540 if (!is_numeric($value) && $value != '') {
7541 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7542 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7543 return -1;
7544 } elseif ($value === '') {
7545 $value = null;
7546 }
7547 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7548 $new_array_options["options_".$key] = $value;
7549
7550 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7551 break;
7552 /*case 'select': // Not required, we chose value='0' for undefined values
7553 if ($value=='-1')
7554 {
7555 $new_array_options["options_".$key] = $value;
7556
7557 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7558 }
7559 break;*/
7560 case 'password':
7561 $algo = '';
7562 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7563 // If there is an encryption choice, we use it to encrypt data before insert
7564 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7565 $algo = reset($tmparrays);
7566 if ($algo != '') {
7567 //global $action; // $action may be 'create', 'update', 'update_extras'...
7568 //var_dump($action);
7569 //var_dump($this->oldcopy);exit;
7570 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7571 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
7572 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7573 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.
7574 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7575 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7576 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7577 } else {
7578 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7579 }
7580 } else {
7581 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7582 }
7583 } else {
7584 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7585 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7586 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7587 } else {
7588 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7589 }
7590 } else {
7591 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7592 }
7593 }
7594 } else {
7595 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7596 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7597 } else {
7598 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7599 }
7600 }
7601 } else {
7602 // No encryption
7603 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7604 }
7605 } else { // Common usage
7606 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7607 }
7608
7609 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7610 break;
7611 case 'date':
7612 case 'datetime':
7613 if (empty($this->array_options["options_".$key])) {
7614 $new_array_options["options_".$key] = null;
7615
7616 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7617 } else {
7618 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7619 }
7620 break;
7621 case 'datetimegmt':
7622 if (empty($this->array_options["options_".$key])) {
7623 $new_array_options["options_".$key] = null;
7624
7625 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7626 } else {
7627 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7628 }
7629 break;
7630 case 'boolean':
7631 if (empty($this->array_options["options_".$key])) {
7632 $new_array_options["options_".$key] = null;
7633
7634 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7635 }
7636 break;
7637 case 'link':
7638 if ($this->array_options["options_".$key] === '') {
7639 $new_array_options["options_".$key] = null;
7640
7641 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7642 }
7643 break;
7644 /*
7645 case 'link':
7646 $param_list = array_keys($attributeParam['options']);
7647 // 0 : ObjectName
7648 // 1 : classPath
7649 $InfoFieldList = explode(":", $param_list[0]);
7650 dol_include_once($InfoFieldList[1]);
7651 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7652 {
7653 if ($value == '-1') // -1 is key for no defined in combo list of objects
7654 {
7655 $new_array_options[$key] = '';
7656 } elseif ($value) {
7657 $object = new $InfoFieldList[0]($this->db);
7658 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7659 else $res = $object->fetch(0, $value); // For compatibility
7660
7661 if ($res > 0) $new_array_options[$key] = $object->id;
7662 else {
7663 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7664 $this->db->rollback();
7665 return -1;
7666 }
7667 }
7668 } else {
7669 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7670 }
7671 break;
7672 */
7673 case 'checkbox':
7674 case 'chkbxlst':
7675 $new_array_options = array();
7676 if (is_array($this->array_options["options_".$key])) {
7677 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7678 } else {
7679 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7680 }
7681
7682 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7683 break;
7684 }
7685
7686 $this->db->begin();
7687
7688 $linealreadyfound = 0;
7689
7690 // 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)
7691 $table_element = $this->table_element;
7692 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7693 $table_element = 'categories'; // For compatibility
7694 }
7695
7696 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7697 $resql = $this->db->query($sql);
7698 if ($resql) {
7699 $tmpobj = $this->db->fetch_object($resql);
7700 if ($tmpobj) {
7701 $linealreadyfound = $tmpobj->nb;
7702 }
7703 }
7704
7705 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7706 if ($linealreadyfound) {
7707 $sanitizedGeoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null; // @phan-suppress-current-line SqlInjection
7708 if ($this->array_options["options_".$key] === null) {
7709 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7710 $sql .= " SET ".$this->db->sanitize($key)." = null";
7711 } elseif (!empty($sanitizedGeoDataType['ST_Function'])) {
7712 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7713 $sql .= " SET ".$this->db->sanitize($key)." = ".$this->db->sanitize($sanitizedGeoDataType['ST_Function'])."('".$this->db->escape($this->array_options["options_".$key])."')";
7714 } else {
7715 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7716 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7717 $sql .= " SET ".$this->db->sanitize($key)." = '".$this->db->escape($new_array_options["options_".$key])."'";
7718 }
7719 $sql .= " WHERE fk_object = ".((int) $this->id);
7720
7721 $resql = $this->db->query($sql);
7722 if (!$resql) {
7723 $error++;
7724 $this->error = $this->db->lasterror();
7725 }
7726 } else {
7727 $result = $this->insertExtraFields('', $user);
7728 if ($result < 0) {
7729 $error++;
7730 }
7731 }
7732
7733 // Update also the user of last modification in parent table
7734 if (!$error && !empty($this->fields['fk_user_modif'])) {
7735 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7736 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7737 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7738 $this->db->query($sql);
7739 }
7740
7741 if (!$error) {
7742 $parameters = array('key' => $key);
7743 global $action;
7744 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7745 if ($reshook < 0) {
7746 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7747 }
7748 }
7749
7750 if (!$error && $trigger) {
7751 // Call trigger
7752 $this->context = array('extrafieldupdate' => 1);
7753 $result = $this->call_trigger($trigger, $userused);
7754 if ($result < 0) {
7755 $error++;
7756 }
7757 // End call trigger
7758 }
7759
7760 if ($error) {
7761 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7762 $this->db->rollback();
7763 return -1;
7764 } else {
7765 $this->db->commit();
7766 return 1;
7767 }
7768 } else {
7769 return 0;
7770 }
7771 }
7772
7779 public function getExtraField($key)
7780 {
7781 return $this->array_options['options_'.$key] ?? null;
7782 }
7783
7791 public function setExtraField($key, $value)
7792 {
7793 $this->array_options['options_'.$key] = $value;
7794 }
7795
7806 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7807 {
7808 global $user;
7809
7810 if (empty($userused)) {
7811 $userused = $user;
7812 }
7813
7814 //$error = 0;
7815
7816 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7817 return 0; // For avoid conflicts if trigger used
7818 }
7819
7820 return 0;
7821 }
7822
7823
7838 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7839 {
7840 global $conf, $langs, $form;
7841
7842
7843 // 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...)
7844
7845 if (!is_object($form)) {
7846 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7847 $form = new Form($this->db);
7848 }
7849
7850 if (!empty($this->fields)) {
7851 $val = $this->fields[$key];
7852 }
7853
7854 // Validation tests and output
7855 $fieldValidationErrorMsg = '';
7856 $validationClass = '';
7857 $fieldValidationErrorMsg = $this->getFieldError($key);
7858 if (!empty($fieldValidationErrorMsg)) {
7859 $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
7860 } else {
7861 $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
7862 }
7863
7864 //$valuemultiselectinput = array();
7865 $out = '';
7866 $type = '';
7867 $isDependList = 0;
7868 $param = array();
7869 $param['options'] = array();
7870 $reg = array();
7871 // @phan-suppress-next-line PhanTypeMismatchProperty
7872 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7873 // Because we work on extrafields
7874 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7875 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7876 $type = 'link';
7877 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7878 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7879 $type = 'link';
7880 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7881 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7882 $type = 'link';
7883 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7884 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7885 $type = 'sellist';
7886 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7887 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7888 $type = 'sellist';
7889 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7890 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7891 $type = 'sellist';
7892 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7893 $param['options'] = array($reg[1] => 'N');
7894 $type = 'chkbxlst';
7895 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7896 $param['options'] = array();
7897 $type = 'varchar';
7898 $size = $reg[1];
7899 } elseif (preg_match('/varchar/', $val['type'])) {
7900 $param['options'] = array();
7901 $type = 'varchar';
7902 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7903 $param['options'] = array();
7904 $type = 'stars';
7905 $size = $reg[1];
7906 } else {
7907 $param['options'] = array();
7908 $type = $this->fields[$key]['type'];
7909 }
7910 //var_dump($type); var_dump($param['options']);
7911
7912 // Special case that force options and type ($type can be integer, varchar, ...)
7913 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7914 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7915 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7916 if (empty($this->fields[$key]['multiinput'])) {
7917 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7918 }
7919 }
7920
7921 $label = $this->fields[$key]['label'];
7922 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7923 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7924 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7925 // @phan-suppress-next-line PhanTypeMismatchProperty
7926 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7927 // @phan-suppress-next-line PhanTypeMismatchProperty
7928 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7929 // @phan-suppress-next-line PhanTypeMismatchProperty
7930 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7931 // @phan-suppress-next-line PhanTypeMismatchProperty
7932 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7933 // @phan-suppress-next-line PhanTypeMismatchProperty
7934 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7935
7936 // @phan-suppress-next-line PhanTypeMismatchProperty
7937 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7938 // @phan-suppress-next-line PhanTypeMismatchProperty
7939 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7940 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7941
7942 $objectid = $this->id;
7943
7944 if ($computed) {
7945 if (!preg_match('/^search_/', $keyprefix)) {
7946 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7947 } else {
7948 return '';
7949 }
7950 }
7951
7952 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7953 if (empty($morecss) && !empty($val['css'])) {
7954 $morecss = $val['css'];
7955 } elseif (empty($morecss)) {
7956 if ($type == 'date') {
7957 $morecss = 'minwidth100imp';
7958 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7959 $morecss = 'minwidth200imp';
7960 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7961 $morecss = 'maxwidth75';
7962 } elseif ($type == 'url') {
7963 $morecss = 'minwidth400';
7964 } elseif ($type == 'boolean') {
7965 $morecss = '';
7966 } else {
7967 if (is_numeric($size) && round((float) $size) < 12) {
7968 $morecss = 'minwidth100';
7969 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7970 $morecss = 'minwidth200';
7971 } else {
7972 $morecss = 'minwidth400';
7973 }
7974 }
7975 }
7976
7977 // Add validation state class
7978 if (!empty($validationClass)) {
7979 $morecss .= $validationClass;
7980 }
7981
7982 if (in_array($type, array('date'))) {
7983 $tmp = explode(',', $size);
7984 $newsize = $tmp[0];
7985 $showtime = 0;
7986
7987 // Do not show current date when field not required (see selectDate() method)
7988 if (!$required && $value == '') {
7989 $value = '-1';
7990 }
7991
7992 // TODO Must also support $moreparam
7993 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7994 } elseif (in_array($type, array('datetime'))) {
7995 $tmp = explode(',', $size);
7996 $newsize = $tmp[0];
7997 $showtime = 1;
7998
7999 // Do not show current date when field not required (see selectDate() method)
8000 if (!$required && $value == '') {
8001 $value = '-1';
8002 }
8003
8004 // TODO Must also support $moreparam
8005 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
8006 } elseif (in_array($type, array('duration'))) {
8007 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
8008 } elseif (in_array($type, array('int', 'integer'))) {
8009 $tmp = explode(',', $size);
8010 $newsize = $tmp[0];
8011 $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' : '').'>';
8012 } elseif (in_array($type, array('real'))) {
8013 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8014 } elseif (preg_match('/varchar/', (string) $type)) {
8015 $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' : '').'>';
8016 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
8017 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8018 } elseif (preg_match('/^text/', (string) $type)) {
8019 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8020 if (!empty($param['options'])) {
8021 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
8022 //var_dump($param['options']);
8023 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
8024 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
8025 $out .= "<script>";
8026 $out .= '
8027 function handlemultiinputdisabling(htmlname){
8028 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
8029 multiinput = $("#"+htmlname+"_multiinput");
8030 multiinput.find("option").each(function(){
8031 tmpval = $("#"+htmlname).val();
8032 tmpvalarray = tmpval.split("\n");
8033 valtotest = $(this).val();
8034 if(tmpvalarray.includes(valtotest)){
8035 $(this).prop("disabled",true);
8036 } else {
8037 if($(this).prop("disabled") == true){
8038 console.log(valtotest)
8039 $(this).prop("disabled", false);
8040 }
8041 }
8042 });
8043 }
8044
8045 $(document).ready(function () {
8046 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
8047 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
8048 tmpvalarray = tmpval.split(",");
8049 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
8050 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8051 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
8052 if(tmpval == ""){
8053 tmpval = valtotest;
8054 } else {
8055 tmpval = tmpval + "\n" + valtotest;
8056 }
8057 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
8058 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8059 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
8060 } else {
8061 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
8062 }
8063 });
8064 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
8065 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8066 });
8067 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8068 })';
8069 $out .= "</script>";
8070 $value = str_replace(',', "\n", $value);
8071 }
8072 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8073 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8074 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
8075 } else {
8076 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8077 }
8078 } elseif (preg_match('/^html/', (string) $type)) {
8079 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8080 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8081 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8082 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8083 } else {
8084 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8085 }
8086 } elseif ($type == 'boolean') {
8087 $checked = '';
8088 if (!empty($value)) {
8089 $checked = ' checked value="1" ';
8090 } else {
8091 $checked = ' value="1" ';
8092 }
8093 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
8094 } elseif ($type == 'price') {
8095 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8096 $value = price($value);
8097 }
8098 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> <span class="opacitymedium">'.$langs->getCurrencySymbol($conf->currency).'</span>';
8099 } elseif ($type == 'stars') {
8100 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8101 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8102 $i = 1;
8103 while ($i <= $size) {
8104 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8105 $i++;
8106 }
8107 $out .= '</div>';
8108 $out .= '<script>
8109 jQuery(function($) { /* commonobject.class.php 1 */
8110 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8111 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8112 container.find(".star").each(function() {
8113 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8114 });
8115 container.find(".star").on("mouseover", function() {
8116 let selectedStar = $(this).data("value");
8117 container.find(".star").each(function() {
8118 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8119 });
8120 });
8121 container.on("mouseout", function() {
8122 container.find(".star").each(function() {
8123 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8124 });
8125 });
8126 container.find(".star").off("click").on("click", function() {
8127 selectedStars = $(this).data("value");
8128 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8129 selectedStars = 0;
8130 }
8131 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8132 container.find(".star").each(function() {
8133 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8134 });
8135 });
8136 });
8137 </script>';
8138 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8139 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8140 $value = price($value);
8141 }
8142 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8143 } elseif ($type == 'select') { // combo list
8144 $out = '';
8145 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8146 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8147 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8148 }
8149
8150 $tmpselect = '';
8151 $nbchoice = 0;
8152
8153 foreach ($param['options'] as $keyb => $valb) {
8154 if ((string) $keyb == '') {
8155 continue;
8156 }
8157 if (strpos($valb, "|") !== false) {
8158 list($valb, $parent) = explode('|', $valb);
8159 }
8160 $nbchoice++;
8161 $tmpselect .= '<option value="'.$keyb.'"';
8162 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8163 if (!empty($parent)) {
8164 $isDependList = 1;
8165 }
8166 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8167 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8168 }
8169
8170 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8171 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8172 $out .= '<option value="0">&nbsp;</option>';
8173 }
8174 $out .= $tmpselect;
8175 $out .= '</select>';
8176 } elseif ($type == 'sellist') {
8177 $out = '';
8178 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8179 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8180 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8181 }
8182
8183 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8184 if (is_array($param['options'])) {
8185 $tmpparamoptions = array_keys($param['options']);
8186 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8187
8188 $InfoFieldList = explode(":", $paramoptions[0], 5);
8189 // 0 : tableName
8190 // 1 : label field name
8191 // 2 : key fields name (if different of rowid)
8192 // optional parameters...
8193 // 3 : key field parent (for dependent lists). How this is used ?
8194 // 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".
8195 // 5 : string category type. This replace the filter.
8196 // 6 : ids categories list separated by comma for category root. This replace the filter.
8197 // 7 : sort field
8198
8199 // If there is filter
8200 if (! empty($InfoFieldList[4])) {
8201 $pos = 0;
8202 $parenthesisopen = 0;
8203 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8204 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8205 $parenthesisopen++;
8206 }
8207 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8208 $parenthesisopen--;
8209 }
8210 $pos++;
8211 }
8212 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8213 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8214 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8215 $InfoFieldList[4] = $tmpbefore;
8216 if ($tmpafter !== '') {
8217 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8218 }
8219 //var_dump($InfoFieldList);
8220
8221 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8222 $reg = array();
8223 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8224 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8225 }
8226
8227 //var_dump($InfoFieldList);
8228 }
8229
8230 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8231
8232 $parentName = '';
8233 $parentField = '';
8234
8235 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8236
8237 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8238 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8239 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8240 } else {
8241 $keyList = $InfoFieldList[2].' as rowid';
8242 }
8243 }
8244 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8245 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8246 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8247 $keyList .= ', main.'.$parentField;
8248 } else {
8249 $keyList .= ', '.$parentField;
8250 }
8251 }
8252
8253 $filter_categorie = false;
8254 if (count($InfoFieldList) > 5) {
8255 if ($InfoFieldList[0] == 'categorie') {
8256 $filter_categorie = true;
8257 }
8258 }
8259
8260 if (!$filter_categorie) {
8261 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8262 if (!empty($fields_label)) {
8263 $keyList .= ', ';
8264 $keyList .= implode(', ', $fields_label);
8265 }
8266
8267 $sqlwhere = '';
8268 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8269 $sql .= " FROM " . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8270
8271 if (!empty($InfoFieldList[4])) {
8272 // can use SELECT request
8273 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8274 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8275 }
8276
8277 // current object id can be use into filter
8278 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8279 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8280 } else {
8281 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8282 }
8283
8284 // We have to join on extrafield table
8285 $errstr = '';
8286 if (strpos($InfoFieldList[4], 'extra') !== false) {
8287 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8288 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8289 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8290 } else {
8291 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8292 }
8293 } else {
8294 $sqlwhere .= ' WHERE 1=1';
8295 }
8296
8297 // Add Usf filter on second line
8298 /*
8299 if ($Usf) {
8300 $errorstr = '';
8301 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8302 if (!$errorstr) {
8303 $sqlwhere .= $sqlusf;
8304 } else {
8305 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8306 }
8307 }
8308 */
8309
8310 // Some tables may have field, some other not. For the moment we disable it.
8311 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8312 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8313 }
8314 $sql .= $sqlwhere;
8315
8316 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8317 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8318 $sql .= " ORDER BY ".$this->db->sanitize($InfoFieldList[7]);
8319 } else {
8320 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8321 }
8322 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8323 // print $sql;
8324
8325 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8326 $resql = $this->db->query($sql);
8327 if ($resql) {
8328 $out .= '<option value="0">&nbsp;</option>';
8329 $num = $this->db->num_rows($resql);
8330 $i = 0;
8331 while ($i < $num) {
8332 $labeltoshow = '';
8333 $obj = $this->db->fetch_object($resql);
8334
8335 // Several field into label (eq table:code|libelle:rowid)
8336 $notrans = false;
8337 $fields_label = explode('|', $InfoFieldList[1]);
8338 if (count($fields_label) > 1) {
8339 $notrans = true;
8340 foreach ($fields_label as $field_toshow) {
8341 $labeltoshow .= $obj->$field_toshow . ' ';
8342 }
8343 } else {
8344 $labeltoshow = $obj->{$InfoFieldList[1]};
8345 }
8346 $labeltoshow = dol_trunc($labeltoshow, 45);
8347
8348 if ($value == $obj->rowid) {
8349 foreach ($fields_label as $field_toshow) {
8350 $translabel = $langs->trans($obj->$field_toshow);
8351 if ($translabel != $obj->$field_toshow) {
8352 $labeltoshow = dol_trunc($translabel) . ' ';
8353 } else {
8354 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8355 }
8356 }
8357 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8358 } else {
8359 if (!$notrans) {
8360 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8361 if ($translabel != $obj->{$InfoFieldList[1]}) {
8362 $labeltoshow = dol_trunc($translabel, 18);
8363 } else {
8364 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8365 }
8366 }
8367 if (empty($labeltoshow)) {
8368 $labeltoshow = '(not defined)';
8369 }
8370 if ($value == $obj->rowid) {
8371 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8372 }
8373
8374 if (!empty($InfoFieldList[3]) && $parentField) {
8375 $parent = $parentName . ':' . $obj->{$parentField};
8376 $isDependList = 1;
8377 }
8378
8379 $out .= '<option value="' . $obj->rowid . '"';
8380 $out .= ($value == $obj->rowid ? ' selected' : '');
8381 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8382 $out .= '>' . $labeltoshow . '</option>';
8383 }
8384
8385 $i++;
8386 }
8387 $this->db->free($resql);
8388 } else {
8389 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8390 }
8391 } else {
8392 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8393 $categcode = $InfoFieldList[5];
8394 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8395 $tmpcategory = new Categorie($this->db);
8396 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8397 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8398 }
8399
8400 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8401 $out .= '<option value="0">&nbsp;</option>';
8402 foreach ($data as $data_key => $data_value) {
8403 $out .= '<option value="' . $data_key . '"';
8404 $out .= ($value == $data_key ? ' selected' : '');
8405 $out .= '>' . $data_value . '</option>';
8406 }
8407 }
8408 }
8409 $out .= '</select>';
8410 } elseif ($type == 'checkbox') {
8411 $value_arr = explode(',', $value);
8412 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8413 } elseif ($type == 'radio') {
8414 $out = '';
8415 foreach ($param['options'] as $keyopt => $valopt) {
8416 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8417 $out .= ' value="'.$keyopt.'"';
8418 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8419 $out .= ($value == $keyopt ? 'checked' : '');
8420 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8421 }
8422 } elseif ($type == 'chkbxlst') {
8423 if (is_array($value)) {
8424 $value_arr = $value;
8425 } else {
8426 $value_arr = explode(',', $value);
8427 }
8428
8429 if (is_array($param['options'])) {
8430 $tmpparamoptions = array_keys($param['options']);
8431 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8432
8433 $InfoFieldList = explode(":", $paramoptions[0], 5);
8434 // 0 : tableName
8435 // 1 : label field name
8436 // 2 : key fields name (if different of rowid)
8437 // optional parameters...
8438 // 3 : key field parent (for dependent lists). How this is used ?
8439 // 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".
8440 // 5 : string category type. This replace the filter.
8441 // 6 : ids categories list separated by comma for category root. This replace the filter.
8442 // 7 : sort field
8443
8444 // If there is a filter
8445 if (! empty($InfoFieldList[4])) {
8446 $pos = 0;
8447 $parenthesisopen = 0;
8448 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8449 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8450 $parenthesisopen++;
8451 }
8452 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8453 $parenthesisopen--;
8454 }
8455 $pos++;
8456 }
8457 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8458 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8459 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8460 $InfoFieldList[4] = $tmpbefore;
8461 if ($tmpafter !== '') {
8462 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8463 }
8464
8465 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8466 $reg = array();
8467 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8468 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8469 }
8470
8471 //var_dump($InfoFieldList);
8472 }
8473
8474 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8475
8476 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8477
8478 $parentName = '';
8479 $parentField = '';
8480
8481 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8482
8483 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8484 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8485 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8486 } else {
8487 $keyList = $InfoFieldList[2].' as rowid';
8488 }
8489 }
8490 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8491 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8492 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8493 $keyList .= ', main.'.$parentField;
8494 } else {
8495 $keyList .= ', '.$parentField;
8496 }
8497 }
8498
8499 $filter_categorie = false;
8500 if (count($InfoFieldList) > 5) {
8501 if ($InfoFieldList[0] == 'categorie') {
8502 $filter_categorie = true;
8503 }
8504 }
8505
8506 // Common filter
8507 if (!$filter_categorie) {
8508 $fields_label = explode('|', $InfoFieldList[1]);
8509 if (is_array($fields_label)) {
8510 $keyList .= ', ';
8511 $keyList .= implode(', ', $fields_label);
8512 }
8513
8514 $sqlwhere = '';
8515 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8516 $sql .= ' FROM ' . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8517
8518 if (!empty($InfoFieldList[4])) {
8519 // can use SELECT request
8520 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8521 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8522 }
8523
8524 // current object id can be use into filter
8525 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8526 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8527 } else {
8528 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8529 }
8530
8531 // We have to join on extrafield table
8532 $errstr = '';
8533 if (strpos($InfoFieldList[4], 'extra') !== false) {
8534 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8535 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8536 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8537 } else {
8538 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8539 }
8540 } else {
8541 $sqlwhere .= ' WHERE 1=1';
8542 }
8543
8544 // Add Usf filter on second line
8545 /*
8546 if ($Usf) {
8547 $errorstr = '';
8548 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8549 if (!$errorstr) {
8550 $sqlwhere .= $sqlusf;
8551 } else {
8552 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8553 }
8554 }
8555 */
8556
8557 // Some tables may have field, some other not. For the moment we disable it.
8558 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8559 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8560 }
8561 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8562 // print $sql;
8563
8564 $sql .= $sqlwhere;
8565
8566 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8567
8568 $resql = $this->db->query($sql);
8569 if ($resql) {
8570 $num = $this->db->num_rows($resql);
8571 $i = 0;
8572
8573 $data = array();
8574
8575 while ($i < $num) {
8576 $labeltoshow = '';
8577 $obj = $this->db->fetch_object($resql);
8578
8579 $notrans = false;
8580 // Several field into label (eq table:code|libelle:rowid)
8581 $fields_label = explode('|', $InfoFieldList[1]);
8582 if (count($fields_label) > 1) {
8583 $notrans = true;
8584 foreach ($fields_label as $field_toshow) {
8585 $labeltoshow .= $obj->$field_toshow . ' ';
8586 }
8587 } else {
8588 $labeltoshow = $obj->{$InfoFieldList[1]};
8589 }
8590 $labeltoshow = dol_trunc($labeltoshow, 45);
8591
8592 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8593 foreach ($fields_label as $field_toshow) {
8594 $translabel = $langs->trans($obj->$field_toshow);
8595 if ($translabel != $obj->$field_toshow) {
8596 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8597 } else {
8598 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8599 }
8600 }
8601
8602 $data[$obj->rowid] = $labeltoshow;
8603 } else {
8604 if (!$notrans) {
8605 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8606 if ($translabel != $obj->{$InfoFieldList[1]}) {
8607 $labeltoshow = dol_trunc($translabel, 18);
8608 } else {
8609 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8610 }
8611 }
8612 if (empty($labeltoshow)) {
8613 $labeltoshow = '(not defined)';
8614 }
8615
8616 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8617 $data[$obj->rowid] = $labeltoshow;
8618 }
8619
8620 if (!empty($InfoFieldList[3]) && $parentField) {
8621 $parent = $parentName . ':' . $obj->{$parentField};
8622 $isDependList = 1;
8623 }
8624
8625 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8626 }
8627
8628 $i++;
8629 }
8630 $this->db->free($resql);
8631
8632 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8633 } else {
8634 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8635 }
8636 } else {
8637 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8638 $categcode = $InfoFieldList[5];
8639 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8640 $tmpcategory = new Categorie($this->db);
8641 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8642 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8643 }
8644
8645 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8646 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8647 }
8648 }
8649 } elseif ($type == 'link') {
8650 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8651 // Filter can contains some ':' inside.
8652 $param_list = array_keys($param['options']);
8653 $param_list_array = explode(':', $param_list[0], 4);
8654
8655 $showempty = (($required && $default != '') ? 0 : 1);
8656
8657 if (!preg_match('/search_/', $keyprefix)) {
8658 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8659 // @phan-suppress-next-line PhanTypeMismatchProperty
8660 if (!empty($this->fields[$key]['picto'])) {
8661 $morecss .= ' widthcentpercentminusxx';
8662 } else {
8663 $morecss .= ' widthcentpercentminusx';
8664 }
8665 } else {
8666 // @phan-suppress-next-line PhanTypeMismatchProperty
8667 if (!empty($this->fields[$key]['picto'])) {
8668 $morecss .= ' widthcentpercentminusx';
8669 }
8670 }
8671 }
8672
8673 // $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.
8674
8675 // $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.
8676
8677 $objectfield = $val;
8678
8679 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8680 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8681
8682 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8683 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8684 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8685 list($class, $classfile) = explode(':', $param_list[0]);
8686 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8687 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8688 } else {
8689 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8690 }
8691 $paramforthenewlink = '';
8692 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8693 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8694 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8695 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8696 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8697 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8698 $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>';
8699 }
8700 }
8701 } elseif ($type == 'password') {
8702 // If prefix is 'search_', field is used as a filter, we use a common text field.
8703 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8704 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8705 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8706 } else {
8707 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8708 }
8709 } elseif ($type == 'array') {
8710 $newval = $val;
8711 $newval['type'] = 'varchar(256)';
8712
8713 $out = '';
8714 if (!empty($value)) {
8715 foreach ($value as $option) {
8716 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8717 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8718 }
8719 }
8720 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8721
8722 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8723 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8724
8725 if (!empty($conf->use_javascript_ajax)) {
8726 $out .= '
8727 <script nonce="'.getNonce().'">
8728 $(document).ready(function() {
8729 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8730 $("'.dol_escape_js($newInput).'").insertBefore(this);
8731 });
8732
8733 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8734 $(this).parent().remove();
8735 });
8736 });
8737 </script>';
8738 }
8739 }
8740 if (!empty($hidden)) {
8741 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8742 }
8743
8744 if ($isDependList == 1) {
8745 $out .= $this->getJSListDependancies('_common');
8746 }
8747 /* Add comments
8748 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8749 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8750 */
8751
8752 // Display error message for field
8753 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8754 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8755 }
8756
8757 return $out;
8758 }
8759
8773 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8774 {
8775 global $conf, $langs, $form;
8776
8777 // 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...)
8778
8779 if (!is_object($form)) {
8780 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8781 $form = new Form($this->db);
8782 }
8783
8784 //$label = empty($val['label']) ? '' : $val['label'];
8785 $type = empty($val['type']) ? '' : $val['type'];
8786 $size = empty($val['css']) ? '' : $val['css'];
8787
8788 $reg = array();
8789
8790 // Convert var to be able to share same code than showOutputField of extrafields
8791 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8792 $type = 'varchar'; // convert varchar(xx) int varchar
8793 $size = $reg[1];
8794 } elseif (preg_match('/varchar/', $type)) {
8795 $type = 'varchar'; // convert varchar(xx) int varchar
8796 }
8797 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8798 // @phan-suppress-next-line PhanTypeMismatchProperty
8799 if (empty($this->fields[$key]['multiinput'])) {
8800 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8801 }
8802 }
8803 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8804 $type = 'link';
8805 }
8806
8807 $default = empty($val['default']) ? '' : $val['default'];
8808 $computed = empty($val['computed']) ? '' : $val['computed'];
8809 $unique = empty($val['unique']) ? '' : $val['unique'];
8810 $required = empty($val['required']) ? '' : $val['required'];
8811 $param = array();
8812 $param['options'] = array();
8813
8814 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8815 $param['options'] = $val['arrayofkeyval'];
8816 }
8817 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8818 $type = 'link';
8819 $stringforoptions = $reg[1].':'.$reg[2];
8820 // Special case: Force addition of getnomurlparam1 to -1 for users
8821 if ($reg[1] == 'User') {
8822 $stringforoptions .= ':#getnomurlparam1=-1';
8823 }
8824 $param['options'] = array($stringforoptions => $stringforoptions);
8825 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8826 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8827 $type = 'sellist';
8828 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8829 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8830 $type = 'sellist';
8831 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8832 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8833 $type = 'sellist';
8834 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8835 $param['options'] = array($reg[1] => 'N');
8836 $type = 'chkbxlst';
8837 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8838 $param['options'] = array();
8839 $type = 'stars';
8840 $size = $reg[1];
8841 }
8842
8843 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8844 $list = (empty($val['list']) ? '' : $val['list']);
8845 $help = (empty($val['help']) ? '' : $val['help']);
8846 $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)
8847
8848 if ($hidden) {
8849 return '';
8850 }
8851
8852 // If field is a computed field, value must become result of compute
8853 if ($computed) {
8854 // Make the eval of compute string
8855 //var_dump($computed);
8856 $value = dol_eval((string) $computed, 1, 0, '2');
8857 }
8858
8859 if (empty($morecss)) {
8860 if ($type == 'date') {
8861 $morecss = 'minwidth100imp';
8862 } elseif ($type == 'datetime' || $type == 'timestamp') {
8863 $morecss = 'minwidth200imp';
8864 } elseif (in_array($type, array('int', 'double', 'price'))) {
8865 $morecss = 'maxwidth75';
8866 } elseif ($type == 'url') {
8867 $morecss = 'minwidth400';
8868 } elseif ($type == 'boolean') {
8869 $morecss = '';
8870 } else {
8871 if (is_numeric($size) && round((float) $size) < 12) {
8872 $morecss = 'minwidth100';
8873 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8874 $morecss = 'minwidth200';
8875 } else {
8876 $morecss = 'minwidth400';
8877 }
8878 }
8879 }
8880
8881 // Format output value differently according to properties of field
8882 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8883 // @phan-suppress-next-line PhanTypeMismatchProperty
8884 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.
8885 $value = $this->getNomUrl(1, '', 0, '', 1);
8886 }
8887 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8888 $value = $this->getLibStatut(3);
8889 } elseif ($type == 'date') {
8890 if (!empty($value)) {
8891 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8892 } else {
8893 $value = '';
8894 }
8895 } elseif ($type == 'datetime' || $type == 'timestamp') {
8896 if (!empty($value)) {
8897 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8898 } else {
8899 $value = '';
8900 }
8901 } elseif ($type == 'duration') {
8902 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8903 if (!is_null($value) && $value !== '') {
8904 $value = convertSecondToTime((int) $value, 'allhourmin');
8905 }
8906 } elseif ($type == 'double' || $type == 'real') {
8907 if (!is_null($value) && $value !== '') {
8908 $value = price($value);
8909 }
8910 } elseif ($type == 'boolean') {
8911 $checked = '';
8912 if (!empty($value)) {
8913 $checked = ' checked ';
8914 }
8915 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8916 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8917 } else {
8918 $value = yn($value ? 1 : 0);
8919 }
8920 } elseif ($type == 'mail' || $type == 'email') {
8921 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8922 } elseif ($type == 'url') {
8923 $value = dol_print_url((string) $value, '_blank', 32, 1);
8924 } elseif ($type == 'phone') {
8925 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8926 } elseif ($type == 'ip') {
8927 $value = dol_print_ip((string) $value, 0);
8928 } elseif ($type == 'stars') {
8929 $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 : '').'>';
8930 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8931 $i = 1;
8932 while ($i <= $size) {
8933 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8934 $i++;
8935 }
8936 $value .= '</div>';
8937 $value .= '<script>
8938 $(document).ready(function() { /* commonobject.class.php 2 */
8939 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8940 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8941 container.find(".star").each(function() {
8942 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8943 });
8944 container.find(".star").on("mouseover", function() {
8945 let selectedStar = $(this).data("value");
8946 container.find(".star").each(function() {
8947 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8948 });
8949 });
8950 container.on("mouseout", function() {
8951 container.find(".star").each(function() {
8952 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8953 });
8954 });
8955 container.find(".star").off("click").on("click", function() {
8956 selectedStars = $(this).data("value");
8957 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8958 selectedStars = 0;
8959 }
8960 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8961 container.find(".star").each(function() {
8962 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8963 });
8964 $.ajax({
8965 url: "ajax/'.$this->element.'.php",
8966 method: "POST",
8967 data: {
8968 objectId: "'.$this->id.'",
8969 field: "'.$keyprefix.$key.$keysuffix.'",
8970 value: selectedStars,
8971 token: "'.newToken().'"
8972 },
8973 success: function(response) {
8974 var res = JSON.parse(response);
8975 console[res.status === "success" ? "log" : "error"](res.message);
8976 },
8977 error: function(xhr, status, error) {
8978 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8979 }
8980 });
8981 });
8982 });
8983 </script>';
8984 } elseif ($type == 'price') {
8985 if (!is_null($value) && $value !== '') {
8986 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8987 }
8988 } elseif ($type == 'select') {
8989 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8990 if (strpos($value, "|") !== false) {
8991 $value = $langs->trans(explode('|', $value)[0]);
8992 } elseif (! is_numeric($value)) {
8993 $value = $langs->trans($value);
8994 }
8995 } elseif ($type == 'sellist') {
8996 $param_list = array_keys($param['options']);
8997 $InfoFieldList = explode(":", $param_list[0]);
8998
8999 $selectkey = "rowid";
9000 $keyList = 'rowid';
9001
9002 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
9003 $selectkey = $InfoFieldList[2];
9004 $keyList = $InfoFieldList[2].' as rowid';
9005 }
9006
9007 $fields_label = explode('|', $InfoFieldList[1]);
9008 if (is_array($fields_label)) {
9009 $keyList .= ', ';
9010 $keyList .= implode(', ', $fields_label);
9011 }
9012
9013 $filter_categorie = false;
9014 if (count($InfoFieldList) > 5) {
9015 if ($InfoFieldList[0] == 'categorie') {
9016 $filter_categorie = true;
9017 }
9018 }
9019
9020 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9021 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9022 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9023 $sql .= ' as main';
9024 }
9025 if ($selectkey == 'rowid' && empty($value)) {
9026 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = 0";
9027 } elseif ($selectkey == 'rowid') {
9028 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = ".((int) $value);
9029 } else {
9030 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = '".$this->db->escape((string) $value)."'";
9031 }
9032
9033 //$sql.= ' AND entity = '.((int) $conf->entity);
9034
9035 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
9036 $resql = $this->db->query($sql);
9037 if ($resql) {
9038 if (!$filter_categorie) {
9039 $value = ''; // value was used, so now we reset it to use it to build final output
9040 $numrows = $this->db->num_rows($resql);
9041 if ($numrows) {
9042 $obj = $this->db->fetch_object($resql);
9043
9044 // Several field into label (eq table:code|libelle:rowid)
9045 $fields_label = explode('|', $InfoFieldList[1]);
9046
9047 if (is_array($fields_label) && count($fields_label) > 1) {
9048 foreach ($fields_label as $field_toshow) {
9049 $translabel = '';
9050 if (!empty($obj->$field_toshow)) {
9051 $translabel = $langs->trans($obj->$field_toshow);
9052 }
9053 if ($translabel != $field_toshow) {
9054 $value .= dol_trunc($translabel, 18) . ' ';
9055 } else {
9056 $value .= $obj->$field_toshow . ' ';
9057 }
9058 }
9059 } else {
9060 $translabel = '';
9061 if (!empty($obj->{$InfoFieldList[1]})) {
9062 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9063 }
9064 if ($translabel != $obj->{$InfoFieldList[1]}) {
9065 $value = dol_trunc($translabel, 18);
9066 } else {
9067 $value = $obj->{$InfoFieldList[1]};
9068 }
9069 }
9070 }
9071 } else {
9072 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9073
9074 $toprint = array();
9075 $obj = $this->db->fetch_object($resql);
9076 $c = new Categorie($this->db);
9077 $c->fetch($obj->rowid);
9078 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9079 foreach ($ways as $way) {
9080 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9081 }
9082 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9083 }
9084 } else {
9085 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9086 }
9087 } elseif ($type == 'radio') {
9088 $value = $param['options'][(string) $value];
9089 } elseif ($type == 'checkbox') {
9090 $value_arr = explode(',', (string) $value);
9091 $value = '';
9092 if (is_array($value_arr) && count($value_arr) > 0) {
9093 $toprint = array();
9094 foreach ($value_arr as $keyval => $valueval) {
9095 if (!empty($valueval)) {
9096 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9097 }
9098 }
9099 if (!empty($toprint)) {
9100 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9101 }
9102 }
9103 } elseif ($type == 'chkbxlst') {
9104 $value_arr = (isset($value) ? explode(',', $value) : array());
9105
9106 $param_list = array_keys($param['options']);
9107 $InfoFieldList = explode(":", $param_list[0]);
9108
9109 $selectkey = "rowid";
9110 $keyList = 'rowid';
9111
9112 if (count($InfoFieldList) >= 3) {
9113 $selectkey = $InfoFieldList[2];
9114 $keyList = $InfoFieldList[2].' as rowid';
9115 }
9116
9117 $fields_label = explode('|', $InfoFieldList[1]);
9118 if (is_array($fields_label)) {
9119 $keyList .= ', ';
9120 $keyList .= implode(', ', $fields_label);
9121 }
9122
9123 $filter_categorie = false;
9124 if (count($InfoFieldList) > 5) {
9125 if ($InfoFieldList[0] == 'categorie') {
9126 $filter_categorie = true;
9127 }
9128 }
9129
9130 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9131 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9132 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9133 $sql .= ' as main';
9134 }
9135 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9136 // $sql.= ' AND entity = '.((int) $conf->entity);
9137
9138 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9139 $resql = $this->db->query($sql);
9140 if ($resql) {
9141 if (!$filter_categorie) {
9142 $value = ''; // value was used, so now we reset it to use it to build final output
9143 $toprint = array();
9144 while ($obj = $this->db->fetch_object($resql)) {
9145 // Several field into label (eq table:code|libelle:rowid)
9146 $fields_label = explode('|', $InfoFieldList[1]);
9147 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9148 if (is_array($fields_label) && count($fields_label) > 1) {
9149 foreach ($fields_label as $field_toshow) {
9150 $translabel = '';
9151 if (!empty($obj->$field_toshow)) {
9152 $translabel = $langs->trans($obj->$field_toshow);
9153 }
9154 if ($translabel != $field_toshow) {
9155 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9156 } else {
9157 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9158 }
9159 }
9160 } else {
9161 $translabel = '';
9162 if (!empty($obj->{$InfoFieldList[1]})) {
9163 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9164 }
9165 if ($translabel != $obj->{$InfoFieldList[1]}) {
9166 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9167 } else {
9168 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9169 }
9170 }
9171 }
9172 }
9173 } else {
9174 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9175
9176 $toprint = array();
9177 while ($obj = $this->db->fetch_object($resql)) {
9178 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9179 $c = new Categorie($this->db);
9180 $c->fetch($obj->rowid);
9181 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9182 foreach ($ways as $way) {
9183 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9184 }
9185 }
9186 }
9187 }
9188 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9189 } else {
9190 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9191 }
9192 } elseif ($type == 'link') {
9193 $out = '';
9194
9195 // only if something to display (perf)
9196 if ($value) {
9197 $param_list = array_keys($param['options']);
9198 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9199 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9200
9201 $InfoFieldList = explode(":", $param_list[0]);
9202
9203 $classname = $InfoFieldList[0];
9204 $classpath = $InfoFieldList[1];
9205
9206 // Set $getnomurlparam1 et getnomurlparam2
9207 $getnomurlparam = 3;
9208 $getnomurlparam2 = '';
9209 $regtmp = array();
9210 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9211 $getnomurlparam = $regtmp[1];
9212 }
9213 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9214 $getnomurlparam2 = $regtmp[1];
9215 }
9216
9217 if (!empty($classpath)) {
9218 dol_include_once($InfoFieldList[1]);
9219
9220 if ($classname && !class_exists($classname)) {
9221 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9222 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9223 $element_prop = getElementProperties($classname);
9224 if ($element_prop) {
9225 $classname = $element_prop['classname'];
9226 }
9227 }
9228
9229
9230 if ($classname && class_exists($classname)) {
9231 $object = new $classname($this->db);
9232 '@phan-var-force CommonObject $object';
9233 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9234 '@phan-var-force Product $object';
9235 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9236 } else {
9237 $result = $object->fetch($value);
9238 }
9239 if ($result > 0) {
9240 if ($object->element === 'product') {
9241 '@phan-var-force Product $object';
9242 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9243 if (isset($val['get_name_url_params'])) {
9244 $get_name_url_params = explode(':', $val['get_name_url_params']);
9245 if (!empty($get_name_url_params)) {
9246 $param_num_max = count($get_name_url_param_arr) - 1;
9247 foreach ($get_name_url_params as $param_num => $param_value) {
9248 if ($param_num > $param_num_max) {
9249 break;
9250 }
9251 $get_name_url_param_arr[$param_num] = $param_value;
9252 }
9253 }
9254 }
9255
9259 $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]);
9260 } else {
9261 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9262 }
9263 } else {
9264 $value = '';
9265 }
9266 }
9267 } else {
9268 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9269 return 'Error bad setup of extrafield';
9270 }
9271 } else {
9272 $value = '';
9273 }
9274 } elseif ($type == 'password') {
9275 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9276 //$value = preg_replace('/./i', '*', $value);
9277 } elseif ($type == 'array') {
9278 if (is_array($value)) {
9279 $value = implode('<br>', $value);
9280 } else {
9281 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9282 return 'Error unexpected result from code evaluation';
9283 }
9284 } else { // text|html|varchar
9285 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9286 $value = str_replace(',', "\n", $value);
9287 }
9288 $value = dol_htmlentitiesbr((string) $value);
9289 }
9290
9291 //print $type.'-'.$size.'-'.$value;
9292 $out = $value;
9293
9294 return is_null($out) ? '' : $out;
9295 }
9296
9303 public function clearFieldError($fieldKey)
9304 {
9305 $this->error = '';
9306 unset($this->validateFieldsErrors[$fieldKey]);
9307 }
9308
9316 public function setFieldError($fieldKey, $msg = '')
9317 {
9318 global $langs;
9319 if (empty($msg)) {
9320 $msg = $langs->trans("UnknownError");
9321 }
9322
9323 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9324 }
9325
9332 public function getFieldError($fieldKey)
9333 {
9334 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9335 return $this->validateFieldsErrors[$fieldKey];
9336 }
9337 return '';
9338 }
9339
9348 public function validateField($fields, $fieldKey, $fieldValue)
9349 {
9350 global $langs;
9351
9352 if (!class_exists('Validate')) {
9353 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9354 }
9355
9356 $this->clearFieldError($fieldKey);
9357
9358 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9359 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9360 return false;
9361 }
9362
9363 $val = $fields[$fieldKey];
9364
9365 $param = array();
9366 $param['options'] = array();
9367 $type = $val['type'];
9368
9369 $required = false;
9370 if (isset($val['notnull']) && $val['notnull'] === 1) {
9371 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9372 $required = true;
9373 }
9374
9375 $maxSize = 0;
9376 $minSize = 0;
9377
9378 //
9379 // PREPARE Elements
9380 //
9381 $reg = array();
9382
9383 // Convert var to be able to share same code than showOutputField of extrafields
9384 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9385 $type = 'varchar'; // convert varchar(xx) int varchar
9386 $maxSize = (int) $reg[1];
9387 } elseif (preg_match('/varchar/', $type)) {
9388 $type = 'varchar'; // convert varchar(xx) int varchar
9389 }
9390
9391 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9392 $type = 'select';
9393 }
9394
9395 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9396 $type = 'link';
9397 }
9398
9399 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9400 $param['options'] = $val['arrayofkeyval'];
9401 }
9402
9403 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9404 $type = 'link';
9405 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9406 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9407 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9408 $type = 'sellist';
9409 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9410 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9411 $type = 'sellist';
9412 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9413 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9414 $type = 'sellist';
9415 }
9416
9417 //
9418 // TEST Value
9419 //
9420
9421 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9422 $validate = new Validate($this->db, $langs);
9423
9424
9425 // little trick : to perform tests with good performances sort tests by quick to low
9426
9427 //
9428 // COMMON TESTS
9429 //
9430
9431 // Required test and empty value
9432 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9433 $this->setFieldError($fieldKey, $validate->error);
9434 return false;
9435 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9436 // if no value sent and the field is not mandatory, no need to perform tests
9437 return true;
9438 }
9439
9440 // MAX Size test
9441 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9442 $this->setFieldError($fieldKey, $validate->error);
9443 return false;
9444 }
9445
9446 // MIN Size test
9447 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9448 $this->setFieldError($fieldKey, $validate->error);
9449 return false;
9450 }
9451
9452 //
9453 // TESTS for TYPE
9454 //
9455
9456 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9457 if (!$validate->isTimestamp($fieldValue)) {
9458 $this->setFieldError($fieldKey, $validate->error);
9459 return false;
9460 } else {
9461 return true;
9462 }
9463 } elseif ($type == 'duration') {
9464 if (!$validate->isDuration($fieldValue)) {
9465 $this->setFieldError($fieldKey, $validate->error);
9466 return false;
9467 } else {
9468 return true;
9469 }
9470 } elseif (in_array($type, array('double', 'real', 'price'))) {
9471 // is numeric
9472 if (!$validate->isNumeric($fieldValue)) {
9473 $this->setFieldError($fieldKey, $validate->error);
9474 return false;
9475 } else {
9476 return true;
9477 }
9478 } elseif ($type == 'boolean') {
9479 if (!$validate->isBool($fieldValue)) {
9480 $this->setFieldError($fieldKey, $validate->error);
9481 return false;
9482 } else {
9483 return true;
9484 }
9485 } elseif ($type == 'mail' || $type == 'email') {
9486 if (!$validate->isEmail($fieldValue)) {
9487 $this->setFieldError($fieldKey, $validate->error);
9488 return false;
9489 }
9490 } elseif ($type == 'url') {
9491 if (!$validate->isUrl($fieldValue)) {
9492 $this->setFieldError($fieldKey, $validate->error);
9493 return false;
9494 } else {
9495 return true;
9496 }
9497 } elseif ($type == 'phone') {
9498 if (!$validate->isPhone($fieldValue)) {
9499 $this->setFieldError($fieldKey, $validate->error);
9500 return false;
9501 } else {
9502 return true;
9503 }
9504 } elseif ($type == 'select' || $type == 'radio') {
9505 if (!isset($param['options'][$fieldValue])) {
9506 $this->error = $langs->trans('RequireValidValue');
9507 return false;
9508 } else {
9509 return true;
9510 }
9511 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9512 $param_list = array_keys($param['options']);
9513 $InfoFieldList = explode(":", $param_list[0]);
9514 $value_arr = explode(',', $fieldValue);
9515
9516 $selectkey = "rowid";
9517 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9518 $selectkey = $InfoFieldList[2];
9519 }
9520
9521 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9522 $this->setFieldError($fieldKey, $validate->error);
9523 return false;
9524 } else {
9525 return true;
9526 }
9527 } elseif ($type == 'link') {
9528 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9529 $InfoFieldList = explode(":", $param_list[0]);
9530 $classname = $InfoFieldList[0];
9531 $classpath = $InfoFieldList[1];
9532 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9533 $lastIsFetchableError = $validate->error;
9534
9535 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9536 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9537 return true;
9538 }
9539
9540 $this->setFieldError($fieldKey, $lastIsFetchableError);
9541 return false;
9542 } else {
9543 return true;
9544 }
9545 }
9546
9547 // if no test failed all is ok
9548 return true;
9549 }
9550
9564 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9565 {
9566 global $db, $conf, $langs, $action, $form, $hookmanager;
9567 global $objectoffield;
9568
9569 if (!is_object($form)) {
9570 $form = new Form($db);
9571 }
9572 if (!is_object($extrafields)) {
9573 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9574 return 'Bad parameter extrafields for showOptionals';
9575 }
9576 if (!is_array($extrafields->attributes[$this->table_element])) {
9577 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9578 }
9579
9580 // Ensure $objectoffield is available for dol_eval visibility formulas.
9581 $objectoffield = $this;
9582
9583 $out = '';
9584
9585 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9586 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9587 $hookResPrint = $hookmanager->resPrint;
9588
9589 if (empty($reshook)) {
9590 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) {
9591 $out .= "\n";
9592 $out .= '<!-- commonobject:showOptionals --> ';
9593 $out .= "\n";
9594
9595 $nbofextrafieldsshown = 0;
9596 $e = 0; // var to manage the modulo (odd/even)
9597
9598 $lastseparatorkeyfound = '';
9599 $extrafields_collapse_num = '';
9600 $extrafields_collapse_num_old = '';
9601 $i = 0;
9602
9603 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9604 $i++;
9605
9606 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9607 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9608 continue;
9609 }
9610
9611 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9612 $enabled = 1;
9613 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9614 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9615 }
9616 if (empty($enabled)) {
9617 continue;
9618 }
9619
9620 $visibility = 1;
9621 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9622 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9623 }
9624
9625 $perms = 1;
9626 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9627 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9628 }
9629
9630 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9631 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9632 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9633 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9634 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9635 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9636 $ef_name = 'options_' . $key;
9637 $ef_value = $this->array_options[$ef_name] ?? '';
9638 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . dolPrintHTMLForAttribute($ef_value) . '" />' . "\n"; // If trouble to preserve content, we can try dol_htmlentities() instead, but real solution is to remove completely the hidden field (see previous TODO).
9639 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9640 } elseif ($mode == 'view' && empty($visibility)) {
9641 continue;
9642 }
9643 if (empty($perms)) {
9644 continue;
9645 }
9646
9647 // Load language if required
9648 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9649 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9650 }
9651
9652 $colspan = 0;
9653 $value = null;
9654 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9655 if (array_key_exists('cols', $params)) {
9656 $colspan = $params['cols'];
9657 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9658 $reg = array();
9659 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9660 $colspan = $reg[1];
9661 } else {
9662 $colspan = $params['colspan'];
9663 }
9664 }
9665 }
9666 $colspan = intval($colspan);
9667
9668 switch ($mode) {
9669 case "view":
9670 $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
9671 break;
9672 case "create":
9673 case "edit":
9674 // We get the value of property found with GETPOST so it takes into account:
9675 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9676 $check = 'alphanohtml';
9677 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9678 $check = 'restricthtml';
9679 }
9680 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9681 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9682 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9683 if (is_array($getposttemp)) {
9684 // $getposttemp is an array but following code expects a comma separated string
9685 $value = implode(",", $getposttemp);
9686 } else {
9687 $value = $getposttemp;
9688 }
9689 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9690 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9691 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('varchar', 'char'))) {
9692 $value = (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] !== '') ? $this->array_options["options_".$key] : '';
9693 } else {
9694 $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.
9695 }
9696 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9697 break;
9698 }
9699
9700 $nbofextrafieldsshown++;
9701
9702 // Output value of the current field
9703 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9704 $extrafields_collapse_num = $key;
9705 /*
9706 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9707 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9708 $extrafield_param_list = array_keys($extrafield_param['options']);
9709
9710 if (count($extrafield_param_list) > 0) {
9711 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9712
9713 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9714 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9715 $extrafields_collapse_num = $key;
9716 }
9717 }
9718 }
9719 */
9720
9721 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9722 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9723
9724 $lastseparatorkeyfound = $key;
9725 } else {
9726 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9727
9728 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9729 $csstyle = '';
9730 if (is_array($params) && count($params) > 0) {
9731 if (array_key_exists('class', $params)) {
9732 $class .= $params['class'].' ';
9733 }
9734 if (array_key_exists('style', $params)) {
9735 $csstyle = $params['style'];
9736 }
9737 }
9738
9739 // add html5 elements
9740 $domData = ' data-element="extrafield"';
9741 $domData .= ' data-targetelement="'.$this->element.'"';
9742 $domData .= ' data-targetid="'.$this->id.'"';
9743
9744 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9745 if ($display_type == 'card') {
9746 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9747 $colspan = 0;
9748 }
9749
9750 if ($action == 'selectlines') {
9751 $colspan++;
9752 }
9753 }
9754
9755 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9756 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9757 $force_values_on_change_company = (
9758 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9759 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9760 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9761 );
9762 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9763 $value = $this->array_options['options_'.$key] ?? $value;
9764 }
9765
9766 // Convert date into timestamp format (value in memory must be a timestamp)
9767 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9768 $datenotinstring = null;
9769 if (array_key_exists('options_'.$key, $this->array_options)) {
9770 $datenotinstring = $this->array_options['options_'.$key];
9771 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9772 $datenotinstring = $this->db->jdate($datenotinstring);
9773 }
9774 }
9775 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9776 $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;
9777 }
9778 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9779 $datenotinstring = null;
9780 if (array_key_exists('options_'.$key, $this->array_options)) {
9781 $datenotinstring = $this->array_options['options_'.$key];
9782 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9783 $datenotinstring = $this->db->jdate($datenotinstring);
9784 }
9785 }
9786 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9787 $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;
9788 }
9789 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9790 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9791 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9792 $value = price2num($value);
9793 } elseif (isset($this->array_options['options_'.$key])) {
9794 $value = $this->array_options['options_'.$key];
9795 }
9796 }
9797
9798 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9799 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9800 if ($action == 'create' || $mode == 'create') {
9801 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9802 }
9803 }
9804
9805 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9806 if ($action == 'create') {
9807 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9808 }
9809 }
9810
9811
9812 $labeltoshow = $langs->trans($label);
9813 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9814 if ($display_type == 'card') {
9815 $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.' >';
9816 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9817 $out .= '<td></td>';
9818 }
9819 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9820 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9821 $out .= ' tdtop';
9822 }
9823 } elseif ($display_type == 'line') {
9824 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9825 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9826 }
9827 //$out .= "titlefield";
9828 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9829 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9830 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9831 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9832 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9833 $out .= ' fieldrequired';
9834 }
9835 }
9836 $out .= '">';
9837 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9838 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9839 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9840 } else {
9841 $out .= $labeltoshow;
9842 }
9843 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9844 $out .= '&nbsp;<span style="color: red">*</span>';
9845 }
9846 } else {
9847 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9848 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9849 } else {
9850 $out .= $labeltoshow;
9851 }
9852 }
9853
9854 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9855
9856 // Second column
9857 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9858 if ($display_type == 'card') {
9859 // 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
9860 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9861 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9862 $out .= '>';
9863 } elseif ($display_type == 'line') {
9864 $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].'">';
9865 }
9866
9867 switch ($mode) {
9868 case "view":
9869 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9870 break;
9871 case "create":
9872 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9873 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9874 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9875 }
9876 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9877 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9878 break;
9879 case "edit":
9880 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9881 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9882 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9883 }
9884 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9885 break;
9886 }
9887
9888 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9889 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9890 $e++;
9891 }
9892 }
9893 $out .= "\n";
9894 // Add code to manage list depending on others
9895 if (!empty($conf->use_javascript_ajax)) {
9896 $out .= $this->getJSListDependancies();
9897 }
9898
9899 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9900
9901 if (empty($nbofextrafieldsshown)) {
9902 $out = '';
9903 }
9904 }
9905 }
9906
9907 $out .= $hookResPrint;
9908
9909 return $out;
9910 }
9911
9916 public function getJSListDependancies($type = '_extra')
9917 {
9918 $out = '
9919 <script nonce="'.getNonce().'">
9920 jQuery(document).ready(function() {
9921 function showOptions'.$type.'(child_list, parent_list, orig_select)
9922 {
9923 var val = $("select[name=\""+parent_list+"\"]").val();
9924 var parentVal = parent_list + ":" + val;
9925 if(typeof val == "string"){
9926 if(val != "") {
9927 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9928 $("select[name=\""+child_list+"\"] option[parent]").remove();
9929 $("select[name=\""+child_list+"\"]").append(options);
9930 } else {
9931 var options = orig_select.find("option[parent]").clone();
9932 $("select[name=\""+child_list+"\"] option[parent]").remove();
9933 $("select[name=\""+child_list+"\"]").append(options);
9934 }
9935 } else if(val > 0) {
9936 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9937 $("select[name=\""+child_list+"\"] option[parent]").remove();
9938 $("select[name=\""+child_list+"\"]").append(options);
9939 } else {
9940 var options = orig_select.find("option[parent]").clone();
9941 $("select[name=\""+child_list+"\"] option[parent]").remove();
9942 $("select[name=\""+child_list+"\"]").append(options);
9943 }
9944 }
9945 function setListDependencies'.$type.'() {
9946 jQuery("select option[parent]").parent().each(function() {
9947 var orig_select = {};
9948 var child_list = $(this).attr("name");
9949 orig_select[child_list] = $(this).clone();
9950 var parent = $(this).find("option[parent]:first").attr("parent");
9951 var infos = parent.split(":");
9952 var parent_list = infos[0];
9953
9954 //Hide daughters lists
9955 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9956 $("#"+child_list).hide();
9957 //Show mother lists
9958 } else if ($("#"+parent_list).val() != 0){
9959 $("#"+parent_list).show();
9960 }
9961 //Show the child list if the parent list value is selected
9962 $("select[name=\""+parent_list+"\"]").click(function() {
9963 if ($(this).val() != 0){
9964 $("#"+child_list).show()
9965 }
9966 });
9967
9968 //When we change parent list
9969 $("select[name=\""+parent_list+"\"]").change(function() {
9970 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9971 //Select the value 0 on child list after a change on the parent list
9972 $("#"+child_list).val(0).trigger("change");
9973 //Hide child lists if the parent value is set to 0
9974 if ($(this).val() == 0){
9975 $("#"+child_list).hide();
9976 }
9977 });
9978 });
9979 }
9980
9981 setListDependencies'.$type.'();
9982 });
9983 </script>'."\n";
9984 return $out;
9985 }
9986
9992 public function getRights()
9993 {
9994 global $user;
9995
9996 $module = empty($this->module) ? '' : $this->module;
9997 $element = $this->element;
9998
9999 if ($element == 'facturerec') {
10000 $element = 'facture';
10001 } elseif ($element == 'invoice_supplier_rec') {
10002 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
10003 } elseif ($module && $user->hasRight($module, $element)) {
10004 // for modules built with ModuleBuilder
10005 return $user->hasRight($module, $element);
10006 }
10007
10008 return isset($user->rights->$element) ? $user->rights->$element : null;
10009 }
10010
10023 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10024 {
10025 global $hookmanager;
10026
10027 $parameters = array(
10028 'origin_id' => $origin_id,
10029 'dest_id' => $dest_id,
10030 'tables' => $tables,
10031 );
10032 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
10033 if ($reshook) {
10034 return true; // replacement code
10035 } elseif ($reshook < 0) {
10036 return $ignoreerrors === 1; // failure
10037 } // reshook = 0 => execute normal code
10038
10039 foreach ($tables as $table) {
10040 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
10041
10042 if (!$dbs->query($sql)) {
10043 if ($ignoreerrors) {
10044 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.
10045 }
10046 //$this->errors = $db->lasterror();
10047 return false;
10048 }
10049 }
10050
10051 return true;
10052 }
10053
10066 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10067 {
10068 foreach ($tables as $table) {
10069 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
10070
10071 if (!$dbs->query($sql)) {
10072 if ($ignoreerrors) {
10073 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.
10074 }
10075 //$this->errors = $db->lasterror();
10076 return false;
10077 }
10078 }
10079
10080 return true;
10081 }
10082
10095 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
10096 {
10097 global $conf;
10098
10099 $buyPrice = 0;
10100
10101 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10102 // When ForceBuyingPriceIfNull is set
10103 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10104 } else {
10105 // Get cost price for margin calculation
10106 if (!empty($fk_product) && $fk_product > 0) {
10107 $result = 0;
10108 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10109 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10110 $product = new Product($this->db);
10111 $result = $product->fetch($fk_product);
10112 if ($result <= 0) {
10113 $this->errors[] = 'ErrorProductIdDoesNotExists';
10114 return -1;
10115 }
10116 if ($product->cost_price > 0) {
10117 $buyPrice = $product->cost_price;
10118 } elseif ($product->pmp > 0) {
10119 $buyPrice = $product->pmp;
10120 }
10121 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10122 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10123 $product = new Product($this->db);
10124 $result = $product->fetch($fk_product);
10125 if ($result <= 0) {
10126 $this->errors[] = 'ErrorProductIdDoesNotExists';
10127 return -1;
10128 }
10129 if ($product->pmp > 0) {
10130 $buyPrice = $product->pmp;
10131 }
10132 }
10133
10134 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10135 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10136 $productFournisseur = new ProductFournisseur($this->db);
10137 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
10138 $buyPrice = $productFournisseur->fourn_unitprice;
10139 } elseif ($result < 0) {
10140 $this->errors[] = $productFournisseur->error;
10141 return -2;
10142 }
10143 }
10144 }
10145 }
10146
10147 return (float) $buyPrice;
10148 }
10149
10157 public function getDataToShowPhoto($modulepart, $imagesize)
10158 {
10159 // See getDataToShowPhoto() implemented by Product for example.
10160 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10161 }
10162
10163
10164 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10184 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')
10185 {
10186 // phpcs:enable
10187 global $user, $langs;
10188
10189 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10190 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10191 include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
10192
10193 $sortfield = 'position_name';
10194 $sortorder = 'asc';
10195
10196 $dir = $sdir.'/';
10197 $pdir = '/';
10198
10199 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10200 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10201
10202 // For backward compatibility
10203 if ($modulepart == 'product') {
10204 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10205 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10206 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10207 }
10208 }
10209 if ($modulepart == 'category') {
10210 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10211 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10212 }
10213
10214 // Defined relative dir to DOL_DATA_ROOT
10215 $relativedir = '';
10216 if ($dir) {
10217 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10218 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10219 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10220 }
10221
10222 $dirthumb = $dir.'thumbs/';
10223 $pdirthumb = $pdir.'thumbs/';
10224
10225 $return = '<!-- Photo -->'."\n";
10226 $nbphoto = 0;
10227
10228 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10229
10230 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10231 {
10232 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10233 $filearray=array_merge($filearray, $filearrayold);
10234 }*/
10235
10236 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10237 '@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';
10238
10239 $useLinkPathPhoto = false;
10240
10241 if (getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO')) {
10242 $link = new Link($this->db);
10243 $links = array();
10244 $link->fetchAll($links, 'product', $this->id);
10245
10246 if (count($links) > 0) {
10247 $useLinkPathPhoto = true;
10248 }
10249
10250 if ($useLinkPathPhoto) {
10251 // Start table or div based on nbbyrow
10252 if ($nbbyrow > 0) {
10253 $return .= '<table class="valigntop center centpercent" style="border:0; padding:2px; border-spacing:2px; border-collapse: separate;">';
10254 }
10255
10256 $i = 0;
10257 foreach ($links as $link) {
10258 if (!empty($link->url) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $link->url)) {
10259 $url = dol_escape_htmltag($link->url);
10260 $i++;
10261 $nbphoto++;
10262
10263 if ($nbbyrow > 0 && ($i % $nbbyrow == 1)) {
10264 $return .= '<tr class="center valignmiddle">';
10265 }
10266
10267 if ($nbbyrow > 0) {
10268 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%" class="photo">';
10269 } else {
10270 $return .= '<div class="inline-block">';
10271 }
10272
10273 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.$url.'" title="External image">';
10274
10275 if ($nbbyrow > 0) {
10276 $return .= '</td>';
10277 if ($i % $nbbyrow == 0) {
10278 $return .= '</tr>';
10279 }
10280 } else {
10281 $return .= '</div>';
10282 }
10283
10284 if ($nbmax && $nbphoto >= $nbmax) {
10285 break;
10286 }
10287 }
10288 }
10289
10290 if ($nbbyrow > 0) {
10291 while ($i % $nbbyrow) {
10292 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10293 $i++;
10294 }
10295 if ($nbphoto) {
10296 $return .= '</table>';
10297 }
10298 }
10299 }
10300 }
10301
10302 if (count($filearray)) {
10303 if ($sortfield && $sortorder) {
10304 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10305 }
10306
10307 foreach ($filearray as $key => $val) {
10308 $photo = '';
10309 $file = $val['name'];
10310
10311 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10312 if (image_format_supported($file) >= 0) {
10313 $nbphoto++;
10314 $photo = $file;
10315 $viewfilename = $file;
10316
10317 if ($size == 1 || $size == 'small') { // Format vignette
10318 // Find name of thumb file
10319 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10320 if (!dol_is_file($dirthumb.$photo_vignette)) {
10321 // The thumb does not exists, so we will use the original file
10322 $dirthumb = $dir;
10323 $pdirthumb = $pdir;
10324 $photo_vignette = basename($file);
10325 }
10326
10327 // Get filesize of original file
10328 $imgarray = dol_getImageSize($dir.$photo);
10329
10330 if ($nbbyrow > 0) {
10331 if ($nbphoto == 1) {
10332 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10333 }
10334
10335 if ($nbphoto % $nbbyrow == 1) {
10336 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10337 }
10338 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10339 } elseif ($nbbyrow < 0) {
10340 $return .= '<div class="inline-block">'."\n";
10341 }
10342
10343 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10344 if (empty($nolink)) {
10345 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10346 if ($urladvanced) {
10347 $return .= '<a href="'.$urladvanced.'">';
10348 } else {
10349 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10350 }
10351 }
10352
10353 // Show image (width height=$maxHeight)
10354 // If thumb file available and image source is too large, we use the thumb, otherwise we use the original photo
10355 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10356 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10357 if ($overwritetitle) {
10358 if (is_numeric($overwritetitle)) {
10359 $alt = '';
10360 } else {
10361 $alt = $overwritetitle;
10362 }
10363 }
10364 if (empty($cache) && !empty($val['label'])) {
10365 // label is md5 of file
10366 // use it in url to say we want to cache this version of the file
10367 $cache = $val['label'];
10368 }
10369 if ($usesharelink) {
10370 if (array_key_exists('share', $val) && $val['share']) {
10371 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10372 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10373 $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).'">';
10374 } else {
10375 $return .= '<!-- Show original file -->';
10376 $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).'">';
10377 }
10378 } else {
10379 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10380 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10381 }
10382 } else {
10383 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10384 $return .= '<!-- Show thumb -->';
10385 $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).'">';
10386 } else {
10387 $return .= '<!-- Show original file -->';
10388 $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).'">';
10389 }
10390 }
10391
10392 if (empty($nolink)) {
10393 $return .= '</a>';
10394 }
10395
10396 if ($showfilename) {
10397 $return .= '<br>'.$viewfilename;
10398 }
10399 if ($showaction) {
10400 $return .= '<br>';
10401 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10402 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10403 $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>';
10404 }
10405 // Special case for product
10406 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10407 // Link to resize
10408 $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; ';
10409
10410 // Link to delete
10411 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10412 $return .= img_delete().'</a>';
10413 }
10414 }
10415 $return .= "\n";
10416
10417 if ($nbbyrow > 0) {
10418 $return .= '</td>';
10419 if (($nbphoto % $nbbyrow) == 0) {
10420 $return .= '</tr>';
10421 }
10422 } elseif ($nbbyrow < 0) {
10423 $return .= '</div>'."\n";
10424 }
10425 }
10426
10427 if (empty($size)) { // Format origine
10428 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10429
10430 if ($showfilename) {
10431 $return .= '<br>'.$viewfilename;
10432 }
10433 if ($showaction) {
10434 // Special case for product
10435 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10436 // Link to resize
10437 $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; ';
10438
10439 // Link to delete
10440 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10441 $return .= img_delete().'</a>';
10442 }
10443 }
10444 }
10445
10446 // On continue ou on arrete de boucler ?
10447 if ($nbmax && $nbphoto >= $nbmax) {
10448 break;
10449 }
10450 }
10451 }
10452
10453 if ($size == 1 || $size == 'small') {
10454 if ($nbbyrow > 0) {
10455 // Ferme tableau
10456 while ($nbphoto % $nbbyrow) {
10457 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10458 $nbphoto++;
10459 }
10460
10461 if ($nbphoto) {
10462 $return .= '</table>';
10463 }
10464 }
10465 }
10466 }
10467
10468 $this->nbphoto = $nbphoto;
10469
10470 return $return;
10471 }
10472
10473
10480 protected function isArray($info)
10481 {
10482 if (is_array($info)) {
10483 if (isset($info['type']) && $info['type'] == 'array') {
10484 return true;
10485 } else {
10486 return false;
10487 }
10488 }
10489 return false;
10490 }
10491
10498 public function isDate($info)
10499 {
10500 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10501 return true;
10502 }
10503 return false;
10504 }
10505
10512 public function isDuration($info)
10513 {
10514 if (is_array($info)) {
10515 if (isset($info['type']) && ($info['type'] == 'duration')) {
10516 return true;
10517 } else {
10518 return false;
10519 }
10520 } else {
10521 return false;
10522 }
10523 }
10524
10531 public function isInt($info)
10532 {
10533 if (is_array($info)) {
10534 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10535 return true;
10536 } else {
10537 return false;
10538 }
10539 } else {
10540 return false;
10541 }
10542 }
10543
10550 public function isFloat($info)
10551 {
10552 if (is_array($info)) {
10553 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10554 return true;
10555 } else {
10556 return false;
10557 }
10558 }
10559 return false;
10560 }
10561
10568 public function isText($info)
10569 {
10570 if (is_array($info)) {
10571 if (isset($info['type']) && $info['type'] == 'text') {
10572 return true;
10573 } else {
10574 return false;
10575 }
10576 }
10577 return false;
10578 }
10579
10586 protected function canBeNull($info)
10587 {
10588 if (is_array($info)) {
10589 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10590 return true;
10591 } else {
10592 return false;
10593 }
10594 }
10595 return true;
10596 }
10597
10604 protected function isForcedToNullIfZero($info)
10605 {
10606 if (is_array($info)) {
10607 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10608 return true;
10609 } else {
10610 return false;
10611 }
10612 }
10613 return false;
10614 }
10615
10622 protected function isIndex($info)
10623 {
10624 if (is_array($info)) {
10625 if (array_key_exists('index', $info) && $info['index'] == true) {
10626 return true;
10627 } else {
10628 return false;
10629 }
10630 }
10631 return false;
10632 }
10633
10634
10644 protected function setSaveQuery()
10645 {
10646 global $conf;
10647
10648 $queryarray = array();
10649 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10650 // Depending on field type ('datetime', ...)
10651 if ($this->isDate($info)) {
10652 if (empty($this->{$field})) {
10653 $queryarray[$field] = null;
10654 } else {
10655 $queryarray[$field] = $this->db->idate($this->{$field});
10656 }
10657 } elseif ($this->isDuration($info)) {
10658 // $this->{$field} may be null, '', 0, '0', 123, '123'
10659 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10660 if (!isset($this->{$field})) {
10661 if (!empty($info['default'])) {
10662 $queryarray[$field] = $info['default']; // From safe source @phan-suppress-current-line SqlInjection
10663 } else {
10664 $queryarray[$field] = 0;
10665 }
10666 } else {
10667 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10668 }
10669 } else {
10670 $queryarray[$field] = null;
10671 }
10672 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10673 if ($field == 'entity' && is_null($this->{$field})) {
10674 $queryarray[$field] = ((int) $conf->entity);
10675 } else {
10676 // $this->{$field} may be null, '', 0, '0', 123, '123'
10677 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10678 if (!isset($this->{$field})) {
10679 $queryarray[$field] = 0;
10680 } elseif ($this->isInt($info)) {
10681 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10682 } elseif ($this->isFloat($info)) {
10683 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10684 }
10685 } else {
10686 $queryarray[$field] = null;
10687 }
10688 }
10689 } else {
10690 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10691 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10692 $queryarray[$field] = $this->{$field}; // @phan-suppress-current-line SqlInjection
10693 }
10694
10695 if (is_array($info) && array_key_exists('type', $info) && !empty($info['type']) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10696 unset($queryarray[$field]);
10697 }
10698 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10699 $queryarray[$field] = null; // May force 0 to null
10700 }
10701 }
10702
10703 return $queryarray;
10704 }
10705
10712 public function setVarsFromFetchObj(&$obj)
10713 {
10714 global $db;
10715
10716 foreach ($this->fields as $field => $info) {
10717 if ($this->isDate($info)) {
10718 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') {
10719 $this->$field = '';
10720 } else {
10721 $this->$field = $db->jdate($obj->$field);
10722 }
10723 } elseif ($this->isInt($info)) {
10724 if ($field == 'rowid') {
10725 $this->id = (int) $obj->$field;
10726 } else {
10727 if ($this->isForcedToNullIfZero($info)) {
10728 if (empty($obj->$field)) {
10729 $this->$field = null;
10730 } else {
10731 $this->$field = (int) $obj->$field;
10732 }
10733 } else {
10734 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10735 $this->$field = (int) $obj->$field;
10736 } else {
10737 $this->$field = null;
10738 }
10739 }
10740 }
10741 } elseif ($this->isFloat($info)) {
10742 if ($this->isForcedToNullIfZero($info)) {
10743 if (empty($obj->$field)) {
10744 $this->$field = null;
10745 } else {
10746 $this->$field = (float) $obj->$field;
10747 }
10748 } else {
10749 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10750 $this->$field = (float) $obj->$field;
10751 } else {
10752 $this->$field = null;
10753 }
10754 }
10755 } else {
10756 $this->$field = isset($obj->$field) ? $obj->$field : null;
10757 }
10758 }
10759
10760 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10761 if (!isset($this->fields['ref']) && isset($this->id)) {
10762 $this->ref = (string) $this->id;
10763 }
10764 }
10765
10770 public function emtpyObjectVars()
10771 {
10772 foreach ($this->fields as $field => $arr) {
10773 $this->$field = null;
10774 }
10775 }
10776
10784 public function getFieldList($alias = '', $excludefields = array())
10785 {
10786 $keys = array_keys($this->fields);
10787 if (!empty($alias)) {
10788 $keys_with_alias = array();
10789 foreach ($keys as $fieldname) {
10790 if (!empty($excludefields)) {
10791 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10792 continue;
10793 }
10794 }
10795 $keys_with_alias[] = $this->db->sanitize($alias . '.' . $fieldname);
10796 }
10797 return implode(', ', $keys_with_alias);
10798 } else {
10799 return implode(', ', $keys);
10800 }
10801 }
10802
10810 protected function quote($value, $fieldsentry)
10811 {
10812 if (is_null($value)) {
10813 return 'NULL';
10814 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10815 return price2num((string) $value);
10816 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10817 return (int) $value;
10818 } elseif ($fieldsentry['type'] == 'boolean') {
10819 if ($value) {
10820 return 'true';
10821 } else {
10822 return 'false';
10823 }
10824 } else {
10825 return "'".$this->db->escape($value)."'";
10826 }
10827 }
10828
10829
10837 public function createCommon(User $user, $notrigger = 0)
10838 {
10839 global $conf;
10840 //global $langs; // Should be able to work with $langs loaded
10841
10842 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10843
10844 $error = 0;
10845
10846 $now = dol_now();
10847
10848 $fieldvalues = $this->setSaveQuery();
10849
10850 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10851
10852 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10853 $fieldvalues['date_creation'] = $this->db->idate($now);
10854 $this->date_creation = $this->db->idate($now);
10855 }
10856 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10857 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10858 $fieldvalues['fk_user_creat'] = $user->id;
10859 $this->fk_user_creat = $user->id;
10860 }
10861 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10862 $fieldvalues['user_creation_id'] = $user->id;
10863 $this->user_creation_id = $user->id;
10864 }
10865 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10866 // @phan-suppress-next-line PhanUndeclaredProperty
10867 $tmparray = dol_hash($this->pass, '0', 0, 1);
10868 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10869 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10870 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10871 }
10872 }
10873 if (array_key_exists('ref', $fieldvalues)) {
10874 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10875 }
10876 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 the deprecated update by database
10877 $fieldvalues['tms'] = $this->db->idate($now);
10878 }
10879
10880 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10881
10882 $sanitized_keys = array();
10883 $sanitized_values = array(); // Array to store string forged for SQL syntax
10884 foreach ($fieldvalues as $k => $v) {
10885 $sanitized_keys[$k] = $k; // Safe source @phan-suppress-current-line SqlInjection
10886 $value = $this->fields[$k];
10887 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10888 $sanitized_values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10889 }
10890
10891 // Clean and check mandatory
10892 foreach ($sanitized_keys as $key) {
10893 if (!isset($this->fields[$key])) {
10894 continue;
10895 }
10896 $key_fields = $this->fields[$key];
10897
10898 // If field is an implicit foreign key field (so type = 'integer:...')
10899 if (preg_match('/^integer:/i', $key_fields['type']) && $sanitized_values[$key] == '-1') {
10900 $sanitized_values[$key] = '';
10901 }
10902 if (!empty($key_fields['foreignkey']) && $sanitized_values[$key] == '-1') {
10903 $sanitized_values[$key] = '';
10904 }
10905
10906 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($sanitized_values[$key]) || $sanitized_values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10907 $error++;
10908 global $langs;
10909 if (empty($langs)) {
10910 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10911 $langs = new Translate('', $conf);
10912 $langs->setDefaultLang();
10913 $langs->load("errors");
10914 }
10915 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10916 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10917 }
10918
10919 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10920 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($sanitized_values[$key]) || $sanitized_values[$key] === 'NULL') && array_key_exists('default', $key_fields) && !is_null($key_fields['default'])) {
10921 $sanitized_values[$key] = $this->quote($key_fields['default'], $key_fields);
10922 }
10923
10924 // If field is an implicit foreign key field (so type = 'integer:...')
10925 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($sanitized_values[$key])) {
10926 if (isset($key_fields['default'])) {
10927 $sanitized_values[$key] = ((int) $key_fields['default']);
10928 } else {
10929 $sanitized_values[$key] = 'null';
10930 }
10931 }
10932 if (!empty($key_fields['foreignkey']) && empty($sanitized_values[$key])) {
10933 $sanitized_values[$key] = 'null';
10934 }
10935 }
10936
10937 if ($error) {
10938 return -1;
10939 }
10940
10941 $this->db->begin();
10942
10943 if (!$error) {
10944 $sql = "INSERT INTO ".$this->db->prefix().$this->db->sanitize($this->table_element);
10945 $sql .= " (".implode(", ", $sanitized_keys).')';
10946 $sql .= " VALUES (".implode(", ", $sanitized_values).")"; // $sanitized_values can contains 'abc' or 123
10947
10948 $res = $this->db->query($sql);
10949 if (!$res) {
10950 $error++;
10951 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10952 $this->errors[] = "ErrorRefAlreadyExists";
10953 } else {
10954 $this->errors[] = $this->db->lasterror();
10955 }
10956 }
10957 }
10958
10959 if (!$error) {
10960 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10961 }
10962
10963 // If we have a field ref with a default value of (PROV)
10964 if (!$error) {
10965 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10966 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)') {
10967 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
10968 $sql .= " SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10969 $resqlupdate = $this->db->query($sql);
10970
10971 if ($resqlupdate === false) {
10972 $error++;
10973 $this->errors[] = $this->db->lasterror();
10974 } else {
10975 $this->ref = '(PROV'.$this->id.')';
10976 }
10977 }
10978 }
10979
10980 // Create extrafields
10981 if (!$error) {
10982 $result = $this->insertExtraFields();
10983 if ($result < 0) {
10984 $error++;
10985 }
10986 }
10987
10988 // Create lines
10989 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10990 foreach ($this->lines as $line) {
10991 $keyforparent = $this->fk_element;
10992 $line->$keyforparent = $this->id;
10993
10994 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10995 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10996 if (!is_object($line)) {
10997 $line = (object) $line;
10998 }
10999
11000 $result = 0;
11001 if (method_exists($line, 'insert')) {
11002 $result = $line->insert($user, 1);
11003 } elseif (method_exists($line, 'create')) {
11004 $result = $line->create($user, 1);
11005 }
11006 if ($result < 0) {
11007 $this->error = $line->error;
11008 $this->db->rollback();
11009 return -1;
11010 }
11011 }
11012 }
11013
11014 // Triggers
11015 if (!$error && !$notrigger) {
11016 // Call triggers
11017 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
11018 if ($result < 0) {
11019 $error++;
11020 }
11021 // End call triggers
11022 }
11023
11024 // Commit or rollback
11025 if ($error) {
11026 $this->db->rollback();
11027 return -1;
11028 } else {
11029 $this->db->commit();
11030 return $this->id;
11031 }
11032 }
11033
11034
11044 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
11045 {
11046 if (empty($id) && empty($ref) && empty($morewhere)) {
11047 return -1;
11048 }
11049
11050 $fieldlist = $this->getFieldList('t');
11051 if (empty($fieldlist)) {
11052 return 0;
11053 }
11054
11055 $sql = "SELECT ".$this->db->sanitize($fieldlist, 0, 0, 1);
11056 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
11057
11058 if (!empty($id)) {
11059 $sql .= ' WHERE t.rowid = '.((int) $id);
11060 } elseif (!empty($ref)) {
11061 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
11062 } else {
11063 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
11064 }
11065 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
11066 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
11067 }
11068 if ($morewhere) {
11069 $sql .= $morewhere;
11070 }
11071 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
11072
11073 $res = $this->db->query($sql);
11074 if ($res) {
11075 $obj = $this->db->fetch_object($res);
11076 if ($obj) {
11077 $this->setVarsFromFetchObj($obj);
11078
11079 // Retrieve all extrafield
11080 // fetch optionals attributes and labels
11081 if (empty($noextrafields)) {
11082 $result = $this->fetch_optionals();
11083 if ($result < 0) {
11084 $this->error = $this->db->lasterror();
11085 $this->errors[] = $this->error;
11086 return -4;
11087 }
11088 }
11089
11090 return $this->id;
11091 } else {
11092 return 0;
11093 }
11094 } else {
11095 $this->error = $this->db->lasterror();
11096 $this->errors[] = $this->error;
11097 return -1;
11098 }
11099 }
11100
11108 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
11109 {
11110 $objectlineclassname = get_class($this).'Line';
11111 if (!class_exists($objectlineclassname)) {
11112 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
11113 return -1;
11114 }
11115
11116 $objectline = new $objectlineclassname($this->db);
11117 '@phan-var-force CommonObjectLine $objectline';
11118
11119 $sql = "SELECT ".$objectline->getFieldList('l');
11120 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($objectline->table_element)." as l";
11121 $sql .= " WHERE l.fk_".$this->db->sanitize($this->element)." = ".((int) $this->id);
11122 if ($morewhere) {
11123 $sql .= $morewhere;
11124 }
11125 if (isset($objectline->fields['position'])) {
11126 $sql .= $this->db->order('position', 'ASC');
11127 }
11128
11129 $resql = $this->db->query($sql);
11130 if ($resql) {
11131 $num_rows = $this->db->num_rows($resql);
11132 $i = 0;
11133 $this->lines = array();
11134 while ($i < $num_rows) {
11135 $obj = $this->db->fetch_object($resql);
11136 if ($obj) {
11137 $newline = new $objectlineclassname($this->db);
11138 '@phan-var-force CommonObjectLine $newline';
11139 $newline->setVarsFromFetchObj($obj);
11140
11141 // Load also extrafields for the line
11142 if (empty($noextrafields)) {
11143 $newline->fetch_optionals();
11144 }
11145
11146 $this->lines[] = $newline;
11147 }
11148 $i++;
11149 }
11150
11151 return 1;
11152 } else {
11153 $this->error = $this->db->lasterror();
11154 $this->errors[] = $this->error;
11155 return -1;
11156 }
11157 }
11158
11166 public function updateCommon(User $user, $notrigger = 0)
11167 {
11168 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11169
11170 $error = 0;
11171
11172 $now = dol_now();
11173
11174 // $this->oldcopy should have been set by the caller of update
11175 //if (empty($this->oldcopy)) {
11176 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11177 // $this->oldcopy = dol_clone($this, 2);
11178 //}
11179
11180 $fieldvalues = $this->setSaveQuery();
11181
11182 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11183
11184 if (array_key_exists('date_modification', $fieldvalues)) {
11185 $fieldvalues['date_modification'] = $this->db->idate($now);
11186 }
11187 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
11188 $fieldvalues['tms'] = $this->db->idate($now);
11189 }
11190 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11191 $fieldvalues['fk_user_modif'] = $user->id;
11192 }
11193 if (array_key_exists('user_modification_id', $fieldvalues)) {
11194 $fieldvalues['user_modification_id'] = $user->id;
11195 }
11196 // @phan-suppress-next-line PhanUndeclaredProperty
11197 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11198 // @phan-suppress-next-line PhanUndeclaredProperty
11199 $tmparray = dol_hash($this->pass, '0', 0, 1);
11200 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11201 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11202 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11203 }
11204 }
11205 if (array_key_exists('ref', $fieldvalues)) {
11206 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11207 }
11208
11209 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11210
11211 // Add quotes and escape on fields with type string
11212 $keys = array();
11213 $values = array();
11214 $sanitized_tmp = array();
11215 foreach ($fieldvalues as $k => $v) {
11216 $keys[$k] = $k;
11217 $value = $this->fields[$k];
11218 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11219 $values[$k] = $this->quote($v, $value);
11220 if ((!empty($value["type"]) && $value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11221 // Clean values for text with selectbox
11222 $v = preg_replace('/\s/', ',', $v);
11223 $v = preg_replace('/,+/', ',', $v);
11224 }
11225 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, SqlInjection
11226 $sanitized_tmp[] = $this->db->sanitize($k).'='.$this->quote($v, $this->fields[$k]);
11227 }
11228
11229 // Clean and check mandatory fields
11230 foreach ($keys as $key) {
11231 if (!empty($this->fields[$key]['type']) && preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11232 $values[$key] = ''; // This is an implicit foreign key field
11233 }
11234 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11235 $values[$key] = ''; // This is an explicit foreign key field
11236 }
11237
11238 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11239 /*
11240 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11241 {
11242 $error++;
11243 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11244 }*/
11245 }
11246
11247 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11248 $sql.= " SET ".implode(', ', $sanitized_tmp);
11249 $sql.= " WHERE rowid = ".((int) $this->id);
11250
11251 $this->db->begin();
11252
11253 if (!$error) {
11254 $res = $this->db->query($sql);
11255 if (!$res) {
11256 $error++;
11257 $this->errors[] = $this->db->lasterror();
11258 }
11259 }
11260
11261 // Update extrafield
11262 if (!$error) {
11263 $result = $this->insertExtraFields(); // This update extrafields
11264 if ($result < 0) {
11265 $error++;
11266 }
11267 }
11268
11269 // Triggers
11270 if (!$error && !$notrigger) {
11271 // Call triggers
11272 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11273 if ($result < 0) {
11274 $error++;
11275 } //Do also here what you must do to rollback action if trigger fail
11276 // End call triggers
11277 }
11278
11279 // Commit or rollback
11280 if ($error) {
11281 $this->db->rollback();
11282 return -1;
11283 } else {
11284 $this->db->commit();
11285 return $this->id;
11286 }
11287 }
11288
11297 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11298 {
11299 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11300
11301 $error = 0;
11302
11303 $this->db->begin();
11304
11305 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11306 foreach ($this->childtables as $table) {
11307 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($table);
11308 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11309 $resql = $this->db->query($sql);
11310 if (!$resql) {
11311 $this->error = $this->db->lasterror();
11312 $this->errors[] = $this->error;
11313 $this->db->rollback();
11314 return -1;
11315 }
11316 }
11317 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11318 $objectisused = $this->isObjectUsed($this->id);
11319 if (!empty($objectisused)) {
11320 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11321 $this->error = 'ErrorRecordHasChildren';
11322 $this->errors[] = $this->error;
11323 $this->db->rollback();
11324 return 0;
11325 }
11326 }
11327
11328 // Delete cascade first
11329 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11330 foreach ($this->childtablesoncascade as $tabletodelete) {
11331 $deleteFromObject = explode(':', $tabletodelete, 4);
11332 if (count($deleteFromObject) >= 2) {
11333 $className = str_replace('@', '', $deleteFromObject[0]);
11334 $filePath = $deleteFromObject[1];
11335 $columnName = $deleteFromObject[2];
11336 $filter = '';
11337 if (!empty($deleteFromObject[3])) {
11338 $filter = $deleteFromObject[3];
11339 }
11340
11341 if (dol_include_once($filePath)) {
11342 $childObject = new $className($this->db);
11343 if (method_exists($childObject, 'deleteByParentField')) {
11344 '@phan-var-force CommonObject $childObject';
11345 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11346 if ($result < 0) {
11347 $error++;
11348 $this->errors[] = $childObject->error;
11349 break;
11350 }
11351 } else {
11352 $error++;
11353 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11354 break;
11355 }
11356 } else {
11357 $error++;
11358 $this->errors[] = 'Cannot include child class file '.$filePath;
11359 break;
11360 }
11361 } else {
11362 // Delete record in child table
11363 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($tabletodelete)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11364
11365 $resql = $this->db->query($sql);
11366 if (!$resql) {
11367 $error++;
11368 $this->error = $this->db->lasterror();
11369 $this->errors[] = $this->error;
11370 break;
11371 }
11372 }
11373 }
11374 }
11375
11376 if (!$error) {
11377 if (!$notrigger) {
11378 // Call triggers
11379 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11380 if ($result < 0) {
11381 $error++;
11382 } // Do also here what you must do to rollback action if trigger fail
11383 // End call triggers
11384 }
11385 }
11386
11387 // Delete llx_ecm_files
11388 if (!$error) {
11389 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11390 if (!$res) {
11391 $error++;
11392 }
11393 }
11394
11395 if (!$error) {
11396 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11397 // For remove dir
11398 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11399 if (dol_is_dir($dir)) {
11400 if (!dol_delete_dir_recursive($dir)) {
11401 $this->errors[] = 'ErrorFailToDeleteDir';
11402 }
11403 }
11404 }
11405
11406 // Delete linked object
11407 $res = $this->deleteObjectLinked();
11408 if ($res < 0) {
11409 $error++;
11410 }
11411
11412 if (!$error && !empty($this->isextrafieldmanaged)) {
11413 $result = $this->deleteExtraFields();
11414 if ($result < 0) {
11415 $error++;
11416 }
11417 }
11418
11419 if (!$error) {
11420 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11421 $sql .= " WHERE rowid = ".((int) $this->id);
11422
11423 $resql = $this->db->query($sql);
11424 if (!$resql) {
11425 $error++;
11426 $this->errors[] = $this->db->lasterror();
11427 }
11428 }
11429
11430 // Commit or rollback
11431 if ($error) {
11432 $this->db->rollback();
11433 return -1;
11434 } else {
11435 $this->db->commit();
11436 return 1;
11437 }
11438 }
11439
11451 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11452 {
11453 global $user;
11454
11455 $error = 0;
11456 $deleted = 0;
11457
11458 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11459
11460 if (!empty($parentId) && !empty($parentField)) {
11461 if (empty($this->table_element)) {
11462 $this->error = 'Property table_element for object is not defined';
11463 $this->errors[] = $this->error;
11464 $error++;
11465 return -1;
11466 }
11467 if (!method_exists($this, 'fetch')) {
11468 $this->error = 'Method fetch for object is not defined';
11469 $this->errors[] = $this->error;
11470 $error++;
11471 return -1;
11472 }
11473 if (!method_exists($this, 'delete')) {
11474 $this->error = 'Method delete for object is not defined';
11475 $this->errors[] = $this->error;
11476 $error++;
11477 return -1;
11478 }
11479
11480 $this->db->begin();
11481
11482 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11483 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11484
11485 // Manage filter
11486 $errormessage = '';
11487 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11488 if ($errormessage) {
11489 $this->errors[] = $errormessage;
11490 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11491 return -1;
11492 }
11493
11494 $resql = $this->db->query($sql);
11495 if (!$resql) {
11496 $this->errors[] = $this->db->lasterror();
11497 $error++;
11498 } else {
11499 while ($obj = $this->db->fetch_object($resql)) {
11500 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11501 if ($result < 0) {
11502 $error++;
11503 $this->errors[] = $this->error;
11504 } else {
11505 $result = $this->delete($user); // @phpstan-ignore-line
11506 if ($result < 0) {
11507 $error++;
11508 $this->errors[] = $this->error;
11509 } else {
11510 $deleted++;
11511 }
11512 }
11513 }
11514 }
11515
11516 if (empty($error)) {
11517 $this->db->commit();
11518 return $deleted;
11519 } else {
11520 $this->error = implode(', ', $this->errors);
11521 $this->db->rollback();
11522 return $error * -1;
11523 }
11524 }
11525
11526 return $deleted;
11527 }
11528
11537 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11538 {
11539 $error = 0;
11540
11541 $tmpforobjectclass = get_class($this);
11542 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11543
11544 $this->db->begin();
11545
11546 // Call trigger
11547 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11548 if ($result < 0) {
11549 $error++;
11550 }
11551 // End call triggers
11552
11553 if (empty($error)) {
11554 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
11555 $sql .= " WHERE rowid = ".((int) $idline);
11556
11557 $resql = $this->db->query($sql);
11558 if (!$resql) {
11559 $this->error = "Error ".$this->db->lasterror();
11560 $error++;
11561 }
11562 }
11563
11564 if (empty($error)) {
11565 // Remove extrafields
11566 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11567 '@phan-var-force CommonObjectLine $tmpobjectline';
11568 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11569 $tmpobjectline->id = $idline;
11570 $result = $tmpobjectline->deleteExtraFields();
11571 if ($result < 0) {
11572 $error++;
11573 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11574 }
11575 }
11576 }
11577
11578 if (empty($error)) {
11579 $this->db->commit();
11580 return 1;
11581 } else {
11582 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11583 $this->db->rollback();
11584 return -1;
11585 }
11586 }
11587
11588
11599 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11600 {
11601 $error = 0;
11602
11603 $this->db->begin();
11604
11605 $statusfield = 'status';
11606 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11607 $statusfield = 'fk_statut';
11608 }
11609
11610 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11611 $sql .= " SET ".$this->db->sanitize($statusfield)." = ".((int) $status);
11612 $sql .= " WHERE rowid = ".((int) $this->id);
11613
11614 if ($this->db->query($sql)) {
11615 if (!$error) {
11616 $this->oldcopy = clone $this;
11617 }
11618
11619 if (!$error && !$notrigger) {
11620 // Call trigger
11621 $result = $this->call_trigger($triggercode, $user);
11622 if ($result < 0) {
11623 $error++;
11624 }
11625 }
11626
11627 if (!$error) {
11628 $this->status = $status;
11629 if (property_exists($this, 'statut')) { // For backward compatibility
11630 $this->statut = $status;
11631 }
11632 $this->db->commit();
11633 return 1;
11634 } else {
11635 $this->db->rollback();
11636 return -1;
11637 }
11638 } else {
11639 $this->error = $this->db->error();
11640 $this->db->rollback();
11641 return -1;
11642 }
11643 }
11644
11651 public function initAsSpecimenCommon()
11652 {
11653 global $user;
11654
11655 $this->id = 0;
11656 $this->specimen = 1;
11657 $fields = array(
11658 'label' => 'This is label',
11659 'ref' => 'ABCD1234',
11660 'description' => 'This is a description',
11661 'qty' => 123.12,
11662 'note_public' => 'Public note',
11663 'note_private' => 'Private note',
11664 'date_creation' => (dol_now() - 3600 * 48),
11665 'date_modification' => (dol_now() - 3600 * 24),
11666 'fk_user_creat' => $user->id,
11667 'fk_user_modif' => $user->id,
11668 'date' => dol_now(),
11669 );
11670 foreach ($fields as $key => $value) {
11671 if (array_key_exists($key, $this->fields)) {
11672 $this->{$key} = $value; // @phpstan-ignore-line
11673 }
11674 }
11675
11676 // Force values to default values when known
11677 if (property_exists($this, 'fields')) {
11678 foreach ($this->fields as $key => $value) {
11679 // If fields are already set, do nothing
11680 if (array_key_exists($key, $fields)) {
11681 continue;
11682 }
11683
11684 if (!empty($value['default'])) {
11685 $this->$key = $value['default'];
11686 }
11687 }
11688 }
11689
11690 return 1;
11691 }
11692
11693
11694 /* Part for comments */
11695
11701 public function fetchComments()
11702 {
11703 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11704
11705 $comment = new Comment($this->db);
11706 $result = $comment->fetchAllFor($this->element, $this->id);
11707 if ($result < 0) {
11708 $this->setErrorsFromObject($comment);
11709 return -1;
11710 } else {
11711 $this->comments = $comment->comments;
11712 }
11713 return count($this->comments);
11714 }
11715
11721 public function getNbComments()
11722 {
11723 return count($this->comments);
11724 }
11725
11732 public function trimParameters($parameters)
11733 {
11734 if (!is_array($parameters)) {
11735 return;
11736 }
11737 foreach ($parameters as $parameter) {
11738 if (isset($this->$parameter)) {
11739 $this->$parameter = trim($this->$parameter);
11740 }
11741 }
11742 }
11743
11744 /* Part for categories/tags */
11745
11756 public function getCategoriesCommon($type_categ)
11757 {
11758 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11759
11760 // Get current categories
11761 $c = new Categorie($this->db);
11762 $existing = $c->containing($this->id, $type_categ, 'id');
11763
11764 return $existing;
11765 }
11766
11779 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11780 {
11781 // Handle single category
11782 if (!is_array($categories)) {
11783 $categories = array($categories);
11784 }
11785
11786 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11787
11788 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11789
11790 if (empty($type_categ)) {
11791 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11792 return -1;
11793 }
11794
11795 // Get current categories
11796 $c = new Categorie($this->db);
11797 $existing = $c->containing($this->id, $type_categ, 'id');
11798 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11799 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11800 if (!is_array($existing)) {
11801 $existing = array();
11802 }
11803 if ($remove_existing) {
11804 // Diff
11805 $to_del = array_diff($existing, $categories);
11806 $to_add = array_diff($categories, $existing);
11807 } else {
11808 $to_del = array(); // Nothing to delete
11809 $to_add = array_diff($categories, $existing);
11810 }
11811
11812 $error = 0;
11813 $ok = 0;
11814
11815 // Process
11816 foreach ($to_del as $del) {
11817 if ($c->fetch($del) > 0) {
11818 $result = $c->del_type($this, $type_categ);
11819 if ($result < 0) {
11820 $error++;
11821 $this->error = $c->error;
11822 $this->errors = $c->errors;
11823 break;
11824 } else {
11825 $ok += $result;
11826 }
11827 }
11828 }
11829 foreach ($to_add as $add) {
11830 if ($c->fetch($add) > 0) {
11831 $result = $c->add_type($this, $type_categ);
11832 if ($result < 0) {
11833 $error++;
11834 $this->error = $c->error;
11835 $this->errors = $c->errors;
11836 break;
11837 } else {
11838 $ok += $result;
11839 }
11840 }
11841 }
11842
11843 return $error ? (-1 * $error) : $ok;
11844 }
11845
11854 public function cloneCategories($fromId, $toId, $type = '')
11855 {
11856 $this->db->begin();
11857
11858 if (empty($type)) {
11859 $type = $this->table_element;
11860 }
11861
11862 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11863 $categorystatic = new Categorie($this->db);
11864
11865 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11866 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11867
11868 $sql = "INSERT INTO ".$this->db->sanitize($tablename)." (fk_categorie, ".$this->db->sanitize($fkname).")";
11869 $sql .= " SELECT fk_categorie, ".((int) $toId)." FROM ".$this->db->sanitize($tablename);
11870 $sql .= " WHERE ".$this->db->sanitize($fkname)." = ".((int) $fromId);
11871
11872 if (!$this->db->query($sql)) {
11873 $this->error = $this->db->lasterror();
11874 $this->db->rollback();
11875 return -1;
11876 }
11877
11878 $this->db->commit();
11879 return 1;
11880 }
11881
11888 public function deleteEcmFiles($mode = 0)
11889 {
11890 global $conf;
11891
11892 $this->db->begin();
11893
11894 // Delete in database with mode 0
11895 if ($mode == 0) {
11896 switch ($this->element) {
11897 case 'propal':
11898 $element = 'propale';
11899 break;
11900 case 'product':
11901 $element = 'produit';
11902 break;
11903 case 'order_supplier':
11904 $element = 'fournisseur/commande';
11905 break;
11906 case 'invoice_supplier':
11907 // Special cases that need to use get_exdir to get real dir of object
11908 // In future, all object should use this to define path of documents.
11909 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11910 break;
11911 case 'shipping':
11912 $element = 'expedition/sending';
11913 break;
11914 case 'task':
11915 case 'project_task':
11916 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11917
11918 $project_result = $this->fetchProject();
11919 if ($project_result >= 0) {
11920 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11921 }
11922 // no break
11923 case 'contrat':
11924 $element = 'contract';
11925 break;
11926 default:
11927 $element = $this->element;
11928 }
11929 '@phan-var-force string $element';
11930
11931 // Delete ecm_files_extrafields with mode 0 (using name)
11932 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11933 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11934 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11935 $sql .= ")";
11936
11937 if (!$this->db->query($sql)) {
11938 $this->error = $this->db->lasterror();
11939 $this->db->rollback();
11940 return false;
11941 }
11942
11943 // Delete ecm_files with mode 0 (using name)
11944 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11945 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11946 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11947
11948 if (!$this->db->query($sql)) {
11949 $this->error = $this->db->lasterror();
11950 $this->db->rollback();
11951 return false;
11952 }
11953 }
11954
11955 // Delete in database with mode 1
11956 if ($mode == 1) {
11957 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11958 $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).")";
11959 $resql = $this->db->query($sql);
11960 if (!$resql) {
11961 $this->error = $this->db->lasterror();
11962 $this->db->rollback();
11963 return false;
11964 }
11965
11966 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11967 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11968 $resql = $this->db->query($sql);
11969 if (!$resql) {
11970 $this->error = $this->db->lasterror();
11971 $this->db->rollback();
11972 return false;
11973 }
11974 }
11975
11976 $this->db->commit();
11977 return true;
11978 }
11979
11988 public function checkActiveProductInLines($status = 'onsale')
11989 {
11990 global $langs;
11991
11992 if (isModEnabled('product') || isModEnabled('service')) {
11993 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
11994
11995 $ret = true;
11996 $tmpproduct = new Product($this->db);
11997 foreach ($this->lines as $line) {
11998 if ($line->fk_product > 0) {
11999 $tmpproduct->fetch($line->fk_product);
12000 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
12001 if (!$tmpproduct->$statustotest) {
12002 $langs->load('products');
12003 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
12004 $ret = false;
12005 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
12006 break;
12007 }
12008 }
12009 }
12010 if (!$ret) {
12011 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
12012 }
12013 return $ret;
12014 } else {
12015 return true;
12016 }
12017 }
12018
12025 public function setErrorWithoutLog($message)
12026 {
12027 $this->error = $message;
12028 $this->errors[] = $message;
12029 }
12030
12038 public function setErrorWithLog($message, $loglevel = LOG_ERR)
12039 {
12040 global $dolibarr_main_document_root;
12041
12042 $this->setErrorWithoutLog($message);
12043 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
12044 $file = str_replace($dolibarr_main_document_root, '', $trace[0]['file'] ?? 'file unknown');
12045 $line = $trace[0]['line'] ?? 'line unknown';
12046 $syslogMessage = sprintf('%s:%s %s', $file, $line, $message);
12047 dol_syslog($syslogMessage, $loglevel);
12048 }
12049}
$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:476
$c
Definition line.php:334
$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.
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 logs it, including the file name and line number.
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:169
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:248
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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.
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
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.
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.
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.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
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.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
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.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
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.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $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:134
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:130
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.
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.
dolEncrypt($chain, $key='', $ciphering='', $forceseed='', $obfuscationmode='dolcrypt')
Encode a string with a symmetric encryption.