dolibarr 25.0.0-alpha
commonobject.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2015 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
5 * Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
6 * Copyright (C) 2011-2022 Philippe Grand <philippe.grand@atoo-net.com>
7 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
8 * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
10 * Copyright (C) 2015-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 * Copyright (C) 2026 Anthony Berton <anthony.berton@bb2a.fr>
26
27 * Copyright (C) 2026 José MARTINEZ <jose.martinez@pichinov.com>
28 *
29 * This program is free software; you can redistribute it and/or modify
30 * it under the terms of the GNU General Public License as published by
31 * the Free Software Foundation; either version 3 of the License, or
32 * (at your option) any later version.
33 *
34 * This program is distributed in the hope that it will be useful,
35 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 * GNU General Public License for more details.
38 *
39 * You should have received a copy of the GNU General Public License
40 * along with this program. If not, see <https://www.gnu.org/licenses/>.
41 */
42
49require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
50require_once DOL_DOCUMENT_ROOT.'/core/class/commontrigger.class.php';
51
59abstract class CommonObject
60{
61 use DolDeprecationHandler;
62 use CommonTrigger;
63
67 public $module;
68
72 public $db;
73
77 public $id;
78
82 public $entity;
83
88 public $error;
89
93 public $errorhidden;
94
98 public $errors = array();
99
104 public $warning;
105
109 public $warnings = array();
110
114 private $validateFieldsErrors = array();
115
119 public $element;
120
125 public $fk_element;
126
132 public $element_for_permission;
133
137 public $table_element;
138
142 public $table_rowid;
143
147 public $table_element_line = '';
148
153 public $ismultientitymanaged;
154
158 public $import_key;
159
163 public $array_options = array();
164
165
244 public $fields = array();
245
251 public $array_languages = null; // Value is array() when load already tried
252
256 public $contacts_ids;
257
261 public $contacts_ids_internal;
262
266 public $linked_objects;
267
271 public $other_linked_objects;
272
276 public $linkedObjectsIds;
277
281 public $linkedObjects;
282
286 private $linkedObjectsFullLoaded = array();
287
291 public $oldcopy;
292
296 public $oldref;
297
301 protected $table_ref_field = '';
302
306 public $restrictiononfksoc = 0;
307
308
309 // The following vars are used by some objects only.
310 // We keep these properties in CommonObject in order to provide common methods using them.
311
315 public $context = array();
316
320 public $actionmsg;
324 public $actionmsg2;
325
329 public $canvas;
330
335 public $project;
336
341 public $fk_project;
342
348 public $fk_projet;
349
354 public $contact;
355
360 public $contact_id;
361
366 public $thirdparty;
367
372 public $user;
373
378 public $product;
379
384 public $warehouse;
385
390 public $origin_type;
391
396 public $origin_id;
397
402
408 public $origin;
409
418 private $expedition;
419
425 private $livraison;
426
432 private $commandeFournisseur;
433
434
438 public $ref;
439
443 public $ref_ext;
444
448 public $ref_previous;
449
453 public $ref_next;
454
458 public $newref;
459
466 public $statut;
467
476 public $status;
477
482 public $country;
483
488 public $country_id;
489
494 public $country_code;
495
500 public $state;
501
506 public $state_id;
507
512 public $state_code;
513
518 public $region_id;
519
524 public $region_code;
525
530 public $region;
531
532
537 public $barcode_type;
538
543 public $barcode_type_code;
544
549 public $barcode_type_label;
550
555 public $barcode_type_coder;
556
561 public $mode_reglement_id;
562
567 public $cond_reglement_id;
568
572 public $demand_reason_id;
573
578 public $transport_mode_id;
579
590 protected $depr_cond_reglement; // Internal value for deprecation
591
597 public $fk_delivery_address;
598
603 public $shipping_method_id;
604
609 public $shipping_method;
610
611 // Multicurrency
615 public $fk_multicurrency;
616
621 public $multicurrency_code;
622
627 public $multicurrency_tx;
628
632 public $multicurrency_total_ht;
633
637 public $multicurrency_total_tva;
638
642 public $multicurrency_total_localtax1; // not in database
643
647 public $multicurrency_total_localtax2; // not in database
648
652 public $multicurrency_total_ttc;
653
654
659 public $model_pdf;
660
665 public $last_main_doc;
666
672 public $fk_bank;
673
678 public $fk_account;
679
684 public $note_public;
685
690 public $note_private;
691
697 public $note;
698
703 public $total_ht;
704
709 public $total_tva;
710
715 public $total_localtax1;
716
721 public $total_localtax2;
722
727 public $total_ttc;
728
729
733 public $lines;
734
738 public $actiontypecode;
739
744 public $comments = array();
745
749 public $name;
750
754 public $lastname;
755
759 public $firstname;
760
765 public $civility_id;
766
770 public $civility_code;
771
772 // Dates
776 public $date_creation;
777
781 public $date_validation;
782
786 public $date_modification;
787
793 public $tms;
794
800 private $date_update;
801
805 public $date_cloture;
806
810 public $user_creation_id;
811
815 public $user_validation_id;
816
820 public $user_closing_id;
821
825 public $user_modification_id;
826
831 public $fk_user_creat;
832
837 public $fk_user_modif;
838
839
843 public $next_prev_filter;
844
848 public $specimen = 0;
849
853 public $sendtoid;
854
855
860 public $alreadypaid;
861
866 public $totalpaid;
867
872 public $totalpaid_multicurrency;
873
874
878 public $labelStatus = array();
879
883 public $labelStatusShort = array();
884
888 public $tpl;
889
890
894 public $showphoto_on_popup;
895
899 public $nb = array();
900
904 public $nbphoto;
905
909 public $output;
910
914 public $extraparams = array();
915
919 protected $childtables = array();
920
926 protected $childtablesoncascade = array();
927
931 public $cond_reglement_supplier_id;
932
938 public $deposit_percent;
939
940
944 public $warehouse_id;
945
949 public $isextrafieldmanaged = 0;
950
959 public $attachedfiles;
960
961
962
963 // No constructor as it is an abstract class
964
970 protected function deprecatedProperties()
971 {
972 return array(
973 'alreadypaid' => 'totalpaid',
974 'cond_reglement' => 'depr_cond_reglement',
975 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
976 'commandeFournisseur' => 'origin_object',
977 'expedition' => 'origin_object',
978 'fk_project' => 'fk_project',
979 'livraison' => 'origin_object',
980 'projet' => 'project',
981 'statut' => 'status',
982 );
983 }
984
985
996 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
997 {
998 global $db, $conf;
999
1000 $sql = "SELECT rowid";
1001 $sql .= " FROM ".$db->prefix().$element;
1002 $sql .= " WHERE entity IN (".getEntity($element).")";
1003
1004 if ($id > 0) {
1005 $sql .= " AND rowid = ".((int) $id);
1006 } elseif ($ref) {
1007 $sql .= " AND ref = '".$db->escape($ref)."'";
1008 } elseif ($ref_ext) {
1009 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
1010 } else {
1011 $error = 'ErrorWrongParameters';
1012 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
1013 return -1;
1014 }
1015 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
1016 $sql .= " AND entity = ".((int) $conf->entity);
1017 }
1018
1019 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
1020 $resql = $db->query($sql);
1021 if ($resql) {
1022 $num = $db->num_rows($resql);
1023 if ($num > 0) {
1024 return 1;
1025 } else {
1026 return 0;
1027 }
1028 }
1029 return -1;
1030 }
1031
1039 {
1040 if (!empty($object->error)) {
1041 $this->error = $object->error;
1042 }
1043 if (!empty($object->errors)) {
1044 $this->errors = array_merge($this->errors, $object->errors);
1045 }
1046 }
1047
1055 public function getTooltipContentArray($params)
1056 {
1057 return [];
1058 }
1059
1067 public function getTooltipContent($params)
1068 {
1069 global $action, $extrafields, $langs, $hookmanager;
1070
1071 // If there is too much extrafields, we do not include them into tooltip
1072 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
1073
1074 $data = $this->getTooltipContentArray($params);
1075 $count = 0;
1076
1077 // Add extrafields
1078 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1079 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1080 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1081 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1082 continue;
1083 }
1084
1085 if (empty($extrafields->attributes[$this->table_element]['showintooltip'][$key])) {
1086 continue;
1087 }
1088
1089
1090 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1091 $data['more_extrafields'] = '<br>...';
1092 break;
1093 }
1094 $enabled = 1;
1095 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1096 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1097 }
1098 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1099 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1100 }
1101 $perms = 1;
1102 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1103 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1104 }
1105 if (empty($enabled)) {
1106 continue; // 0 = Never visible field
1107 }
1108 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1109 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1110 }
1111 if (empty($perms)) {
1112 continue; // 0 = Not visible
1113 }
1114 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1115 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1116 }
1117 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1118 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1119 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1120 } else {
1121 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1122 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1123 $count++;
1124 }
1125 }
1126 $data['closedivextra'] = '</div>';
1127 }
1128
1129 $hookmanager->initHooks(array($this->element . 'dao'));
1130 $parameters = array(
1131 'tooltipcontentarray' => &$data,
1132 'params' => $params,
1133 );
1134 // Note that $action and $object may have been modified by some hooks
1135 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1136
1137 //var_dump($data);
1138 $label = implode($data);
1139
1140 return $label;
1141 }
1142
1143
1149 public function errorsToString()
1150 {
1151 return $this->error.(is_array($this->errors) && count($this->errors) > 0 ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1152 }
1153
1154
1161 public function getFormatedCustomerRef($objref)
1162 {
1163 global $hookmanager;
1164
1165 $parameters = array('objref' => $objref);
1166 $action = '';
1167 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1168 if ($reshook > 0) {
1169 return $hookmanager->resArray['objref'];
1170 }
1171 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1172 }
1173
1180 public function getFormatedSupplierRef($objref)
1181 {
1182 global $hookmanager;
1183
1184 $parameters = array('objref' => $objref);
1185 $action = '';
1186 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1187 if ($reshook > 0) {
1188 return $hookmanager->resArray['objref'];
1189 }
1190 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1191 }
1192
1202 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1203 {
1204 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1205 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1206 $tmparray = getCountry($this->country_id, 'all');
1207 $this->country_code = $tmparray['code'];
1208 $this->country = $tmparray['label'];
1209 }
1210
1211 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1212 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1213 $tmparray = getState($this->state_id, 'all', null, 1);
1214 $this->state_code = $tmparray['code'];
1215 $this->state = $tmparray['label'];
1216 $this->region_code = $tmparray['region_code'];
1217 $this->region = $tmparray['region'];
1218 }
1219
1220 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1221 }
1222
1223
1232 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1233 {
1234 global $user, $dolibarr_main_url_root;
1235
1236 if (empty($this->last_main_doc)) {
1237 return ''; // No way to known which document name to use
1238 }
1239
1240 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1241 $ecmfile = new EcmFiles($this->db);
1242 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1243 if ($result < 0) {
1244 $this->error = $ecmfile->error;
1245 $this->errors = $ecmfile->errors;
1246 return -1;
1247 }
1248
1249 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1250 // Add entry into index
1251 if ($initsharekey) {
1252 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1253
1254 // 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
1255 /*
1256 $ecmfile->filepath = $rel_dir;
1257 $ecmfile->filename = $filename;
1258 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1259 $ecmfile->fullpath_orig = '';
1260 $ecmfile->gen_or_uploaded = 'generated';
1261 $ecmfile->description = ''; // indexed content
1262 $ecmfile->keywords = ''; // keyword content
1263 $ecmfile->share = getRandomPassword(true);
1264 $result = $ecmfile->create($user);
1265 if ($result < 0)
1266 {
1267 $this->error = $ecmfile->error;
1268 $this->errors = $ecmfile->errors;
1269 }
1270 */
1271 } else {
1272 return '';
1273 }
1274 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1275 // Add entry into index
1276 if ($initsharekey) {
1277 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1278 $ecmfile->share = getRandomPassword(true);
1279 $ecmfile->update($user);
1280 } else {
1281 return '';
1282 }
1283 }
1284 // Define $urlwithroot
1285 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1286 // This is to use external domain name found into config file
1287 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1288 //else
1289 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1290 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1291
1292 $forcedownload = 0;
1293
1294 $paramlink = '';
1295 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1296 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1297 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1298 if (!empty($ecmfile->share)) {
1299 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1300 }
1301 if ($forcedownload) {
1302 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1303 }
1304
1305 if ($relativelink) {
1306 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1307 } else {
1308 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1309 }
1310
1311 // Here $ecmfile->share is defined
1312 return $linktoreturn;
1313 }
1314
1315
1316 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1326 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1327 {
1328 // phpcs:enable
1329 global $user, $langs;
1330
1331 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1332
1333 // Check parameters
1334 if ($fk_socpeople <= 0) {
1335 $langs->load("errors");
1336 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1337 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1338 return -1;
1339 }
1340
1341 // Check that the contact actually exists in database
1342 $sql_check = "SELECT rowid FROM ".$this->db->prefix().($source == 'internal' ? "user" : "socpeople")." WHERE rowid = ".((int) $fk_socpeople);
1343 $resql_check = $this->db->query($sql_check);
1344 if ($resql_check) {
1345 if (!$this->db->num_rows($resql_check)) {
1346 $langs->load("errors");
1347 $this->error = $langs->trans("ErrorRecordNotFound");
1348 dol_syslog(get_class($this)."::add_contact Contact/user id ".$fk_socpeople." does not exist", LOG_ERR);
1349 return -1;
1350 }
1351 } else {
1352 $this->error = $this->db->lasterror();
1353 return -1;
1354 }
1355
1356 if (!$type_contact) {
1357 $langs->load("errors");
1358 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1359 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1360 return -2;
1361 }
1362
1363 // socid is checked: @phan-suppress-next-line PhanUndeclaredProperty
1364 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && $user->id > 0 && !$user->hasRight('societe', 'client', 'voir')) {
1365 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1366 // socid is checked: @phan-suppress-next-line PhanUndeclaredProperty
1367 $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid;
1368 $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id;
1369
1370 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1371
1372 if (!$resql_allowed_contacts) {
1373 $this->errors[] = $this->db->lasterror();
1374 return -3;
1375 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1376 if ($obj->cnt == 0) {
1377 $langs->load("companies");
1378 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1379 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1380 return -4;
1381 }
1382 }
1383 }
1384
1385 $id_type_contact = 0;
1386 if (is_numeric($type_contact)) {
1387 $id_type_contact = $type_contact;
1388 } else {
1389 // We look for id type_contact
1390 $sql = "SELECT tc.rowid";
1391 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1392 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1393 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1394 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1395 //print $sql;
1396 $resql = $this->db->query($sql);
1397 if ($resql) {
1398 $obj = $this->db->fetch_object($resql);
1399 if ($obj) {
1400 $id_type_contact = $obj->rowid;
1401 }
1402 }
1403 }
1404 if ($id_type_contact == 0) {
1405 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");
1406 return 0;
1407 }
1408
1409 $datecreate = dol_now();
1410
1411 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1412 $TListeContacts = $this->liste_contact(-1, $source);
1413 $already_added = false;
1414 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1415 foreach ($TListeContacts as $array_contact) {
1416 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1417 $already_added = true;
1418 break;
1419 }
1420 }
1421 }
1422
1423 if (!$already_added) {
1424 $this->db->begin();
1425
1426 // Insert into database
1427 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1428 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1429 $sql .= " VALUES (".((int) $this->id).", ".((int) $fk_socpeople)." , ";
1430 $sql .= "'".$this->db->idate($datecreate)."'";
1431 $sql .= ", 4, ".((int) $id_type_contact);
1432 $sql .= ")";
1433
1434 $resql = $this->db->query($sql);
1435 if ($resql) {
1436 if (!$notrigger) {
1437 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1438 $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user);
1439 if ($result < 0) {
1440 $this->db->rollback();
1441 return -5;
1442 }
1443 }
1444
1445 $this->db->commit();
1446 return 1;
1447 } else {
1448 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1449 $this->error = $this->db->errno();
1450 $this->db->rollback();
1451 return -6;
1452 } else {
1453 $this->error = $this->db->lasterror();
1454 $this->db->rollback();
1455 return -7;
1456 }
1457 }
1458 } else {
1459 return 0;
1460 }
1461 }
1462
1463 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1471 public function copy_linked_contact($objFrom, $source = 'internal')
1472 {
1473 // phpcs:enable
1474 $contacts = $objFrom->liste_contact(-1, $source);
1475 foreach ($contacts as $contact) {
1476 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1477 return -1;
1478 }
1479 }
1480 return 1;
1481 }
1482
1483 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1493 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1494 {
1495 // phpcs:enable
1496 // Insert into database
1497 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1498 $sql .= " statut = ".((int) $statut);
1499 if ($type_contact_id) {
1500 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1501 }
1502 if ($fk_socpeople) {
1503 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1504 }
1505 $sql .= " where rowid = ".((int) $rowid);
1506
1507 $resql = $this->db->query($sql);
1508 if ($resql) {
1509 return 0;
1510 } else {
1511 $this->error = $this->db->lasterror();
1512 return -1;
1513 }
1514 }
1515
1516 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1524 public function delete_contact($rowid, $notrigger = 0)
1525 {
1526 // phpcs:enable
1527 global $user;
1528
1529 $error = 0;
1530
1531 // When called on a loaded object, the link must be one of its own contacts (same filter as liste_contact())
1532 if ($this->id > 0) {
1533 $sql = "SELECT ec.rowid FROM ".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc";
1534 $sql .= " WHERE ec.rowid = ".((int) $rowid)." AND ec.element_id = ".((int) $this->id);
1535 $sql .= " AND ec.fk_c_type_contact = tc.rowid AND tc.element = '".$this->db->escape($this->element)."'";
1536 $resql = $this->db->query($sql);
1537 if (!$resql || !$this->db->num_rows($resql)) {
1538 $this->error = 'ErrorRecordNotFound';
1539 return -1;
1540 }
1541 }
1542
1543 $this->db->begin();
1544
1545 if (!$error && empty($notrigger)) {
1546 // Call trigger
1547 $this->context['contact_id'] = ((int) $rowid);
1548 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1549 $result = $this->call_trigger($triggerPrefix.'_DELETE_CONTACT', $user);
1550 if ($result < 0) {
1551 $error++;
1552 }
1553 // End call triggers
1554 }
1555
1556 if (!$error) {
1557 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1558
1559 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1560 $sql .= " WHERE rowid = ".((int) $rowid);
1561
1562 $result = $this->db->query($sql);
1563 if (!$result) {
1564 $error++;
1565 $this->errors[] = $this->db->lasterror();
1566 }
1567 }
1568
1569 if (!$error) {
1570 $this->db->commit();
1571 return 1;
1572 } else {
1573 $this->error = $this->db->lasterror();
1574 $this->db->rollback();
1575 return -1;
1576 }
1577 }
1578
1579 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1587 public function delete_linked_contact($source = '', $code = '')
1588 {
1589 // phpcs:enable
1590 $listId = '';
1591 $temp = array();
1592 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1593
1594 if (!empty($typeContact)) {
1595 foreach ($typeContact as $key => $value) {
1596 array_push($temp, $key);
1597 }
1598 $listId = implode(",", $temp);
1599 }
1600
1601 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1602 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1603 if (empty($listId)) {
1604 return 0;
1605 }
1606
1607 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1608 $sql .= " WHERE element_id = ".((int) $this->id);
1609 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1610
1611 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1612 if ($this->db->query($sql)) {
1613 return 1;
1614 } else {
1615 $this->error = $this->db->lasterror();
1616 return -1;
1617 }
1618 }
1619
1620 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1632 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1633 {
1634 // phpcs:enable
1635 global $langs;
1636
1637 $tab = array();
1638
1639 $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
1640 if ($source == 'internal') {
1641 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1642 }
1643 if ($source == 'external' || $source == 'thirdparty') {
1644 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1645 }
1646 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1647 if (empty($arrayoftcids)) {
1648 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1649 }
1650 $sql .= " FROM";
1651 if (empty($arrayoftcids)) {
1652 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1653 }
1654 $sql .= " ".$this->db->prefix()."element_contact as ec";
1655 if ($source == 'internal') { // internal contact (user)
1656 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1657 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1658 }
1659 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1660 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1661 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1662 }
1663 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1664 if (empty($arrayoftcids)) {
1665 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1666 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1667 if ($code) {
1668 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1669 }
1670 if ($source == 'internal') {
1671 $sql .= " AND tc.source = 'internal'";
1672 }
1673 if ($source == 'external' || $source == 'thirdparty') {
1674 $sql .= " AND tc.source = 'external'";
1675 }
1676 $sql .= " AND tc.active = 1";
1677 } else {
1678 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1679 }
1680 if ($status >= 0) {
1681 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1682 }
1683 if ($statusoflink >= 0) {
1684 $sql .= " AND ec.statut = ".((int) $statusoflink);
1685 }
1686 $sql .= " ORDER BY t.lastname ASC";
1687
1688 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1689 $resql = $this->db->query($sql);
1690 if ($resql) {
1691 $num = $this->db->num_rows($resql);
1692 $i = 0;
1693 while ($i < $num) {
1694 $obj = $this->db->fetch_object($resql);
1695
1696 if (!$list) {
1697 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1698 $label_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1699 $tab[$i] = array(
1700 'parentId' => $this->id,
1701 'source' => $obj->source,
1702 'socid' => $obj->socid,
1703 'id' => $obj->id,
1704 'nom' => $obj->lastname, // For backward compatibility
1705 'civility' => $obj->civility,
1706 'lastname' => $obj->lastname,
1707 'firstname' => $obj->firstname,
1708 'email' => $obj->email,
1709 'address' => $obj->address,
1710 'zip' => $obj->zip,
1711 'town' => $obj->town,
1712 'country_id' => $obj->country_id,
1713 'country' => $obj->country,
1714 'login' => (empty($obj->login) ? '' : $obj->login),
1715 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1716 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1717 'statuscontact' => $obj->statuscontact,
1718 'rowid' => $obj->rowid,
1719 'code' => $obj->code,
1720 'libelle' => $label_type,
1721 'status' => (int) $obj->statuslink,
1722 'fk_c_type_contact' => $obj->fk_c_type_contact
1723 );
1724 } else {
1725 $tab[$i] = $obj->id;
1726 }
1727
1728 $i++;
1729 }
1730
1731 return $tab;
1732 } else {
1733 $this->error = $this->db->lasterror();
1734 dol_print_error($this->db);
1735 return -1;
1736 }
1737 }
1738
1739
1746 public function swapContactStatus($rowid)
1747 {
1748 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1749 $sql .= " tc.code, tc.libelle as type_label";
1750 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1751 $sql .= " WHERE ec.rowid =".((int) $rowid);
1752 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1753 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1754
1755 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1756 $resql = $this->db->query($sql);
1757 if ($resql) {
1758 $obj = $this->db->fetch_object($resql);
1759 $newstatut = ($obj->statut == 4) ? 5 : 4;
1760 $result = $this->update_contact($rowid, $newstatut);
1761 $this->db->free($resql);
1762 return $result;
1763 } else {
1764 $this->error = $this->db->error();
1765 dol_print_error($this->db);
1766 return -1;
1767 }
1768 }
1769
1770 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1781 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1782 {
1783 // phpcs:enable
1784 global $langs;
1785
1786 $sanitized_order = $order; // @phan-suppress-current-line SqlInjection
1787
1788 if (empty($sanitized_order)) {
1789 $sanitized_order = 'position';
1790 }
1791 if ($sanitized_order == 'position') {
1792 $sanitized_order .= ',code';
1793 }
1794
1795 $tab = array();
1796
1797 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1798 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1799 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1800 if ($activeonly == 1) {
1801 $sql .= " AND tc.active = 1"; // only the active types
1802 }
1803 if (!empty($source) && $source != 'all') {
1804 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1805 }
1806 if (!empty($code)) {
1807 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1808 }
1809 $sql .= $this->db->order($sanitized_order, 'ASC');
1810
1811 //print "sql=".$sql;
1812 $resql = $this->db->query($sql);
1813 if (!$resql) {
1814 $this->error = $this->db->lasterror();
1815 //dol_print_error($this->db);
1816 return null;
1817 }
1818
1819 $num = $this->db->num_rows($resql);
1820 $i = 0;
1821 while ($i < $num) {
1822 $obj = $this->db->fetch_object($resql);
1823
1824 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1825 $label_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1826 if (empty($option)) {
1827 $tab[$obj->rowid] = $label_type;
1828 } elseif ($option == 1) {
1829 $tab[$obj->code] = $label_type;
1830 } else {
1831 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $label_type);
1832 }
1833 $i++;
1834 }
1835
1836 return $tab;
1837 }
1838
1850 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1851 {
1852 global $langs;
1853
1854 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1855
1856 $tab = array();
1857
1858 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1859 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1860
1861 $sqlWhere = array();
1862 if (!empty($element)) {
1863 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1864 }
1865 if (!empty($excludeelement)) {
1866 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1867 }
1868
1869 if ($activeonly == 1) {
1870 $sqlWhere[] = " tc.active=1"; // only the active types
1871 }
1872
1873 if (!empty($source) && $source != 'all') {
1874 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1875 }
1876
1877 if (!empty($code)) {
1878 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1879 }
1880
1881 if (count($sqlWhere) > 0) {
1882 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1883 }
1884
1885 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1886
1887 dol_syslog(__METHOD__, LOG_DEBUG);
1888 $resql = $this->db->query($sql);
1889 if ($resql) {
1890 $num = $this->db->num_rows($resql);
1891 if ($num > 0) {
1892 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1893
1894 while ($obj = $this->db->fetch_object($resql)) {
1895 $modulename = $obj->module ?? $obj->element;
1896 if (strpos($obj->element, 'project') !== false) {
1897 $modulename = 'projet';
1898 } elseif ($obj->element == 'contrat') {
1899 $element = 'contract';
1900 } elseif ($obj->element == 'action') {
1901 $modulename = 'agenda';
1902 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1903 $modulename = 'fournisseur';
1904 }
1905 if (isModEnabled($modulename)) {
1906 $label_element = $langs->trans('ContactDefault_'.$obj->element);
1907 $tmpelement = $obj->element;
1908 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1909 $label_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1910 $tab[$obj->rowid] = $label_element.' - '.$label_type;
1911 }
1912 }
1913 }
1914 return $tab;
1915 } else {
1916 $this->error = $this->db->lasterror();
1917 return null;
1918 }
1919 }
1920
1932 public function getIdContact($source, $code, $status = 0)
1933 {
1934 $result = array();
1935 $i = 0;
1936 // Particular case for shipping
1937 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1938 $id = $this->origin_id;
1939 $element = 'commande';
1940 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1941 $id = $this->origin_id;
1942 $element = 'order_supplier';
1943 } else {
1944 $id = $this->id;
1945 $element = $this->element;
1946 }
1947
1948 $sql = "SELECT ec.fk_socpeople";
1949 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1950 if ($source == 'internal') {
1951 $sql .= " ".$this->db->prefix()."user as c,";
1952 }
1953 if ($source == 'external') {
1954 $sql .= " ".$this->db->prefix()."socpeople as c,";
1955 }
1956 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1957 $sql .= " WHERE ec.element_id = ".((int) $id);
1958 $sql .= " AND ec.fk_socpeople = c.rowid";
1959 if ($source == 'internal') {
1960 $sql .= " AND c.entity IN (".getEntity('user').")";
1961 }
1962 if ($source == 'external') {
1963 $sql .= " AND c.entity IN (".getEntity('societe').")";
1964 }
1965 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1966 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1967 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1968 if ($code) {
1969 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1970 }
1971 $sql .= " AND tc.active = 1";
1972 if ($status) {
1973 $sql .= " AND ec.statut = ".((int) $status);
1974 }
1975
1976 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1977 $resql = $this->db->query($sql);
1978 if ($resql) {
1979 while ($obj = $this->db->fetch_object($resql)) {
1980 $result[$i] = $obj->fk_socpeople;
1981 $i++;
1982 }
1983 } else {
1984 $this->error = $this->db->error();
1985 return null;
1986 }
1987
1988 return $result;
1989 }
1990
1991 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1998 public function fetch_contact($contactid = null)
1999 {
2000 // phpcs:enable
2001 if (empty($contactid)) {
2002 $contactid = $this->contact_id;
2003 }
2004
2005 if (empty($contactid)) {
2006 return 0;
2007 }
2008
2009 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
2010 $contact = new Contact($this->db);
2011 $result = $contact->fetch($contactid);
2012 $this->contact = $contact;
2013 return $result;
2014 }
2015
2016 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2024 public function fetch_thirdparty($force_thirdparty_id = 0)
2025 {
2026 // phpcs:enable
2027 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
2028 return 0;
2029 }
2030
2031 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
2032
2033 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2034 if ($force_thirdparty_id) {
2035 $idtofetch = $force_thirdparty_id;
2036 }
2037
2038 if ($idtofetch) {
2039 $thirdparty = new Societe($this->db);
2040 $result = $thirdparty->fetch($idtofetch);
2041 if ($result < 0) {
2042 $this->errors = array_merge($this->errors, $thirdparty->errors);
2043 }
2044 $this->thirdparty = $thirdparty;
2045
2046 // Use first price level if level not defined for third party
2047 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
2048 $this->thirdparty->price_level = 1;
2049 }
2050
2051 return $result;
2052 } else {
2053 return -1;
2054 }
2055 }
2056
2057
2065 public function fetchOneLike($ref)
2066 {
2067 if (!$this->table_ref_field) {
2068 return 0;
2069 }
2070
2071 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
2072 $sql .= " WHERE ".$this->db->sanitize($this->table_ref_field)." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
2073 $sql .= " LIMIT 1";
2074
2075 $query = $this->db->query($sql);
2076
2077 if (!$this->db->num_rows($query)) {
2078 return 0;
2079 }
2080
2081 $result = $this->db->fetch_object($query);
2082
2083 if (method_exists($this, 'fetch')) {
2084 return $this->fetch($result->rowid);
2085 } else {
2086 $this->error = 'Fetch method not implemented on '.get_class($this);
2087 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2088 array_push($this->errors, $this->error);
2089 return -1;
2090 }
2091 }
2092
2093 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2102 public function fetch_barcode()
2103 {
2104 // phpcs:enable
2105 return $this->fetchBarCode();
2106 }
2107
2115 public function fetchBarCode()
2116 {
2117 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2118
2119 $idtype = $this->barcode_type;
2120 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
2121 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2122 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2123 } elseif ($this->element == 'societe') {
2124 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2125 } else {
2126 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2127 }
2128 }
2129
2130 if ($idtype > 0) {
2131 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
2132 $sql = "SELECT rowid, code, libelle as label, coder";
2133 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2134 $sql .= " WHERE rowid = ".((int) $idtype);
2135
2136 $resql = $this->db->query($sql);
2137 if ($resql) {
2138 $obj = $this->db->fetch_object($resql);
2139
2140 $this->barcode_type = $obj->rowid;
2141 $this->barcode_type_code = $obj->code;
2142 $this->barcode_type_label = $obj->label;
2143 $this->barcode_type_coder = $obj->coder;
2144 return 1;
2145 } else {
2146 dol_print_error($this->db);
2147 return -1;
2148 }
2149 }
2150 }
2151 return 0;
2152 }
2153
2159 public function fetchProject()
2160 {
2161 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2162
2163 /* Done with DolDeprecationHandler
2164 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2165 $this->fk_project = $this->fk_projet; // For backward compatibility
2166 }
2167 */
2168 if (empty($this->fk_project)) {
2169 return 0;
2170 }
2171
2172 $project = new Project($this->db);
2173 $result = $project->fetch($this->fk_project);
2174
2175 $this->project = $project;
2176
2177 return $result;
2178 }
2179
2180 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2188 public function fetch_project()
2189 {
2190 // phpcs:enable
2191 return $this->fetchProject();
2192 }
2193
2194 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2202 public function fetch_projet()
2203 {
2204 // phpcs:enable
2205 return $this->fetchProject();
2206 }
2207
2208 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2214 public function fetch_product()
2215 {
2216 // phpcs:enable
2217 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2218
2219 // @phan-suppress-next-line PhanUndeclaredProperty
2220 if (empty($this->fk_product)) {
2221 return 0;
2222 }
2223
2224 $product = new Product($this->db);
2225 // @phan-suppress-next-line PhanUndeclaredProperty
2226 $result = $product->fetch($this->fk_product);
2227
2228 $this->product = $product;
2229 return $result;
2230 }
2231
2238 public function fetchWarehouse($force_warehouse_id = 0)
2239 {
2240 // testing through empty: @phan-suppress-next-line PhanUndeclaredProperty
2241 if (empty($this->warehouse_id) && empty($this->fk_warehouse) && empty($force_warehouse_id)) {
2242 return 0;
2243 }
2244
2245 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
2246
2247 // testing through isset: @phan-suppress-next-line PhanUndeclaredProperty
2248 $idtofetch = isset($this->warehouse_id) ? $this->warehouse_id : (isset($this->fk_warehouse) ? $this->fk_warehouse : 0);
2249 if (!empty($force_warehouse_id)) {
2250 $idtofetch = $force_warehouse_id;
2251 }
2252
2253 if ($idtofetch) {
2254 $warehouse = new Entrepot($this->db);
2255 $result = $warehouse->fetch($idtofetch);
2256 if ($result < 0) {
2257 $this->errors = array_merge($this->errors, $warehouse->errors);
2258 }
2259 $this->warehouse = $warehouse;
2260
2261 return $result;
2262 } else {
2263 return -1;
2264 }
2265 }
2266
2267 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2274 public function fetch_user($userid)
2275 {
2276 // phpcs:enable
2277 $user = new User($this->db);
2278 $result = $user->fetch($userid);
2279 $this->user = $user;
2280 return $result;
2281 }
2282
2283 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2290 public function fetch_origin()
2291 {
2292 // phpcs:enable
2293 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2294
2295 // Manage classes with non standard name
2296 if ($tmpclassname == 'shipping') {
2297 $tmpclassname = 'Expedition';
2298 }
2299 if ($tmpclassname == 'delivery') {
2300 $tmpclassname = 'Livraison';
2301 }
2302 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2303 $tmpclassname = 'CommandeFournisseur';
2304 }
2305
2306 $classname = ucfirst($tmpclassname);
2307
2308 $this->origin_object = new $classname($this->db);
2309 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2310 $this->origin_object->fetch($this->origin_id);
2311 }
2312
2322 public function fetchObjectFrom($table, $field, $key, $element = null)
2323 {
2324 global $conf;
2325
2326 $result = false;
2327
2328 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2329 $sql .= " WHERE ".$this->db->sanitize($field)." = '".$this->db->escape($key)."'";
2330 if (!empty($element)) {
2331 $sql .= " AND entity IN (".getEntity($element).")";
2332 } else {
2333 $sql .= " AND entity = ".((int) $conf->entity);
2334 }
2335
2336 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2337 $resql = $this->db->query($sql);
2338 if ($resql) {
2339 $obj = $this->db->fetch_object($resql);
2340 // Test for avoid error -1
2341 if ($obj) {
2342 if (method_exists($this, 'fetch')) {
2343 $result = $this->fetch($obj->rowid);
2344 } else {
2345 $this->error = 'fetch() method not implemented on '.get_class($this);
2346 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2347 array_push($this->errors, $this->error);
2348 $result = -1;
2349 }
2350 }
2351 }
2352
2353 return $result;
2354 }
2355
2364 public function getValueFrom($table, $id, $field)
2365 {
2366 $result = false;
2367 if (!empty($id) && !empty($field) && !empty($table)) {
2368 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2369 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2370 return -1;
2371 }
2372 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2373 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2374 return -1;
2375 }
2376
2377 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2378 $sql .= " WHERE rowid = ".((int) $id);
2379
2380 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2381 $resql = $this->db->query($sql);
2382 if ($resql) {
2383 $row = $this->db->fetch_row($resql);
2384 $result = $row[0];
2385 }
2386 }
2387 return $result;
2388 }
2389
2406 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2407 {
2408 global $user;
2409
2410 if (empty($table)) {
2411 $table = $this->table_element;
2412 }
2413 if (empty($id)) {
2414 $id = $this->id;
2415 }
2416 if (empty($format)) {
2417 $format = 'text';
2418 }
2419 if (empty($id_field)) {
2420 $id_field = 'rowid';
2421 }
2422
2423 $propfield = $field;
2424
2425 // Special case
2426 if ($table == 'product') {
2427 if ($field == 'note_private') {
2428 $field = 'note';
2429 $propfield = 'note_private';
2430 } elseif ($field == 'status') {
2431 $field = 'tosell';
2432 $propfield = 'status';
2433 } elseif ($field == 'status_buy') {
2434 $field = 'tobuy';
2435 $propfield = 'status_buy';
2436 } elseif ($field == 'status_batch') {
2437 $field = 'tobatch';
2438 $propfield = 'status_batch';
2439 }
2440 }
2441
2442 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2443 $fk_user_field = 'fk_user_mod';
2444 }
2445 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2446 $fk_user_field = '';
2447 }
2448
2449 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2450 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2451 return -1;
2452 }
2453 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2454 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2455 return -1;
2456 }
2457
2458 $oldvalue = null;
2459 if ($trigkey) {
2460 $sql = "SELECT " . $this->db->sanitize($field);
2461 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2462 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2463
2464 $resql = $this->db->query($sql);
2465 if ($resql) {
2466 if ($obj = $this->db->fetch_object($resql)) {
2467 if ($format == 'date') {
2468 $oldvalue = $this->db->jdate($obj->$field);
2469 } else {
2470 $oldvalue = $obj->$field;
2471 }
2472 }
2473 } else {
2474 $this->error = $this->db->lasterror();
2475 return -1;
2476 }
2477 }
2478
2479 $error = 0;
2480
2481 dol_syslog(__METHOD__, LOG_DEBUG);
2482
2483 $this->db->begin();
2484
2485 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2486
2487 if ($format == 'text') {
2488 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2489 } elseif ($format == 'int') {
2490 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2491 } elseif ($format == 'date') {
2492 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2493 } elseif ($format == 'dategmt') {
2494 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2495 }
2496
2497 if ($fk_user_field) {
2498 if (!empty($fuser) && is_object($fuser)) {
2499 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2500 } elseif (empty($fuser) || $fuser != 'none') {
2501 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2502 }
2503 }
2504
2505 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2506
2507 $resql = $this->db->query($sql);
2508 if ($resql) {
2509 if ($trigkey) {
2510 // call trigger with updated object values
2511 if (method_exists($this, 'fetch')) {
2512 $result = $this->fetch($id);
2513 } else {
2514 $result = $this->fetchCommon($id);
2515 }
2516
2517 $this->oldcopy = clone $this;
2518 if (property_exists($this->oldcopy, $field)) {
2519 $this->oldcopy->$field = $oldvalue;
2520 }
2521 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2522 $this->oldcopy->$propfield = $oldvalue;
2523 }
2524
2525 if (empty($this->context['actionmsgmore'])) {
2526 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2527 }
2528
2529 if ($result >= 0) {
2530 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2531 }
2532 if ($result < 0) {
2533 $error++;
2534 }
2535 } else {
2536 if (property_exists($this, $field)) {
2537 $this->$field = $value;
2538 }
2539 if ($propfield != $field && property_exists($this, $propfield)) {
2540 $this->$propfield = $value;
2541 }
2542 }
2543
2544 if (!$error) {
2545 $this->db->commit();
2546 return 1;
2547 } else {
2548 if (property_exists($this, $field)) {
2549 $this->$field = $oldvalue;
2550 }
2551 if ($propfield != $field && property_exists($this, $propfield)) {
2552 $this->$propfield = $oldvalue;
2553 }
2554 $this->db->rollback();
2555 return -2;
2556 }
2557 } else {
2558 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2559 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2560 } else {
2561 $this->error = $this->db->lasterror();
2562 }
2563 $this->db->rollback();
2564 return -1;
2565 }
2566 }
2567
2568 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2579 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2580 {
2581 // phpcs:enable
2582 global $conf, $user;
2583
2584 if (!$this->table_element) {
2585 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2586 return -1;
2587 }
2588 if ($fieldid == 'none') {
2589 return 1;
2590 }
2591
2592 // For backward compatibility
2593 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2594 $fieldid = 'titre';
2595 }
2596
2597 // Security on socid
2598 $socid = 0;
2599 if ($user->socid > 0) {
2600 $socid = $user->socid;
2601 }
2602
2603 // this->ismultientitymanaged contains
2604 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2605 $aliastablesociete = 's';
2606 if ($this->element == 'societe') {
2607 $aliastablesociete = 'te'; // te as table_element
2608 }
2609 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2610 $sql = "SELECT MAX(te.".$this->db->sanitize($fieldid).")";
2611 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2612 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2613 $tmparray = explode('@', $this->ismultientitymanaged);
2614 $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
2615 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2616 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2617 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2618 $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
2619 }
2620 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2621 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2622 }
2623 if ($fieldid == 'rowid') {
2624 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < ".((int) $this->id);
2625 } elseif ($fieldid == 'label') {
2626 // Suppose caller is not in error @phan-suppress-next-line PhanUndeclaredProperty
2627 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < '".$this->db->escape((string) $this->label)."'";
2628 } else { // Should be 'ref' or any other string field
2629 $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)
2630 }
2631 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2632 $sql .= " AND sc.fk_user = ".((int) $user->id);
2633 }
2634 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2635 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2636 }
2637
2638 $filtermax = $filter;
2639
2640 // Manage filter
2641 $errormessage = '';
2642 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2643 if ($errormessage) {
2644 if (!preg_match('/^\s*AND/i', $filtermax)) {
2645 $sql .= " AND ";
2646 }
2647 $sql .= $filtermax; // @phan-suppress-current-line SqlInjection
2648 } else {
2649 $sql .= $tmpsql;
2650 }
2651
2652 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2653 $tmparray = explode('@', $this->ismultientitymanaged);
2654 $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
2655 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2656 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2657 }
2658 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2659 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2660 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2661 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2662 } else {
2663 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2664 }
2665 } else {
2666 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2667 }
2668 }
2669 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2670 $tmparray = explode('@', $this->ismultientitymanaged);
2671 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2672 }
2673 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2674 $sql .= ' AND te.fk_soc = '.((int) $socid);
2675 }
2676 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2677 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2678 }
2679 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2680 $sql .= ' AND te.rowid = '.((int) $socid);
2681 }
2682 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2683
2684 $result = $this->db->query($sql);
2685 if (!$result) {
2686 $this->error = $this->db->lasterror();
2687 return -1;
2688 }
2689 $row = $this->db->fetch_row($result);
2690 $this->ref_previous = $row[0];
2691
2692 $sql = "SELECT MIN(te.".$this->db->sanitize($fieldid).")";
2693 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2694 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2695 $tmparray = explode('@', $this->ismultientitymanaged);
2696 $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
2697 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2698 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2699 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2700 $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
2701 }
2702 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2703 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2704 }
2705 if ($fieldid == 'rowid') {
2706 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > ".((int) $this->id);
2707 } elseif ($fieldid == 'label') {
2708 // Suppose caller is not in error @phan-suppress-next-line PhanUndeclaredProperty
2709 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > '".$this->db->escape((string) $this->label)."'";
2710 } else { // Should be 'ref' or any other string field
2711 $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)
2712 }
2713 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2714 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2715 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2716 $userschilds = $user->getAllChildIds();
2717 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2718 }
2719 $sql .= ')';
2720 }
2721 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2722 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2723 }
2724
2725 $filtermin = $filter;
2726
2727 // Manage filter
2728 $errormessage = '';
2729 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2730 if ($errormessage) {
2731 if (!preg_match('/^\s*AND/i', $filtermin)) {
2732 $sql .= " AND ";
2733 }
2734 $sql .= $filtermin; // @phan-suppress-current-line SqlInjection
2735
2736 $filtermin = '';
2737 } else {
2738 $sql .= $tmpsql;
2739 }
2740
2741 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2742 $tmparray = explode('@', $this->ismultientitymanaged);
2743 $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
2744 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2745 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2746 }
2747 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2748 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2749 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2750 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2751 } else {
2752 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2753 }
2754 } else {
2755 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2756 }
2757 }
2758 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2759 $tmparray = explode('@', $this->ismultientitymanaged);
2760 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2761 }
2762 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2763 $sql .= ' AND te.fk_soc = '.((int) $socid);
2764 }
2765 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2766 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2767 }
2768 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2769 $sql .= ' AND te.rowid = '.((int) $socid);
2770 }
2771 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2772 // 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
2773
2774 $result = $this->db->query($sql);
2775 if (!$result) {
2776 $this->error = $this->db->lasterror();
2777 return -2;
2778 }
2779 $row = $this->db->fetch_row($result);
2780 $this->ref_next = $row[0];
2781
2782 return 1;
2783 }
2784
2785
2793 public function getListContactId($source = 'external')
2794 {
2795 $contactAlreadySelected = array();
2796 $tab = $this->liste_contact(-1, $source);
2797 $num = count($tab);
2798 $i = 0;
2799 while ($i < $num) {
2800 if ($source == 'thirdparty') {
2801 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2802 } else {
2803 $contactAlreadySelected[$i] = $tab[$i]['id'];
2804 }
2805 $i++;
2806 }
2807 return $contactAlreadySelected;
2808 }
2809
2810
2818 public function setProject($projectid, $notrigger = 0)
2819 {
2820 global $user;
2821 $error = 0;
2822
2823 if (!$this->table_element) {
2824 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2825 return -1;
2826 }
2827
2828 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2829 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2830 if ($projectid) {
2831 $sql .= " SET fk_project = ".((int) $projectid);
2832 } else {
2833 $sql .= " SET fk_project = NULL";
2834 }
2835 $sql .= ' WHERE id = '.((int) $this->id);
2836 // @phan-suppress-next-line PhanTypeMismatchProperty
2837 } elseif (!empty($this->fields['fk_project'])) { // Common case
2838 if ($projectid) {
2839 $sql .= " SET fk_project = ".((int) $projectid);
2840 } else {
2841 $sql .= " SET fk_project = NULL";
2842 }
2843 $sql .= ' WHERE rowid = '.((int) $this->id);
2844 } else { // Special case for old architecture objects
2845 if ($projectid) {
2846 $sql .= ' SET fk_projet = '.((int) $projectid);
2847 } else {
2848 $sql .= ' SET fk_projet = NULL';
2849 }
2850 $sql .= " WHERE rowid = ".((int) $this->id);
2851 }
2852
2853 $this->db->begin();
2854
2855 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2856 if ($this->db->query($sql)) {
2857 $this->fk_project = ((int) $projectid);
2858 } else {
2859 dol_print_error($this->db);
2860 $error++;
2861 }
2862
2863 // Triggers
2864 if (!$error && !$notrigger) {
2865 // Call triggers
2866 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2867 if ($result < 0) {
2868 $error++;
2869 } //Do also here what you must do to rollback action if trigger fail
2870 // End call triggers
2871 }
2872
2873 // Commit or rollback
2874 if ($error) {
2875 $this->db->rollback();
2876 return -1;
2877 } else {
2878 $this->db->commit();
2879 return 1;
2880 }
2881 }
2882
2890 public function setPaymentMethods($id)
2891 {
2892 global $user;
2893
2894 $error = 0;
2895 $notrigger = 0;
2896
2897 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2898
2899 if ($this->status >= 0 || $this->element == 'societe') {
2900 $this->oldcopy = dol_clone($this, 2); // @phan-suppress-current-line PhanTypeMismatchProperty
2901
2902 // TODO uniformize field name
2903 $fieldname = 'fk_mode_reglement';
2904 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2905 if ($this->element == 'societe') {
2906 $fieldname = 'mode_reglement';
2907 $triggerName = 'COMPANY';
2908 }
2909 if (get_class($this) == 'Facture') {
2910 $triggerName = 'BILL';
2911 }
2912 if (get_class($this) == 'Fournisseur') {
2913 $fieldname = 'mode_reglement_supplier';
2914 $triggerName = 'BILL_SUPPLIER';
2915 }
2916 if (get_class($this) == 'Tva') {
2917 $fieldname = 'fk_typepayment';
2918 $triggerName = 'VAT';
2919 }
2920 if (get_class($this) == 'Salary') {
2921 $fieldname = 'fk_typepayment';
2922 }
2923
2924 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2925 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2926 $sql .= ' WHERE rowid = '.((int) $this->id);
2927
2928 if ($this->db->query($sql)) {
2929 $this->mode_reglement_id = $id;
2930 // for supplier
2931 if (get_class($this) == 'Fournisseur') {
2932 $this->mode_reglement_supplier_id = $id;
2933 }
2934
2935 // Triggers
2936 if (!$error && !$notrigger) {
2937 // Call triggers
2938 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2939 if ($result < 0) {
2940 $error++;
2941 }
2942 // End call triggers
2943 }
2944 return 1;
2945 } else {
2946 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2947 $this->error = $this->db->error();
2948 return -1;
2949 }
2950 } else {
2951 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2952 $this->error = 'Status of the object is incompatible '.$this->status;
2953 return -2;
2954 }
2955 }
2956
2963 public function setMulticurrencyCode($code)
2964 {
2965 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2966 if ($this->status >= 0 || $this->element == 'societe') {
2967 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2968 $sql .= " SET multicurrency_code = '".$this->db->escape($code)."'";
2969 $sql .= ' WHERE rowid='.((int) $this->id);
2970
2971 if ($this->db->query($sql)) {
2972 $this->multicurrency_code = $code;
2973
2974 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2975 if ($rate) {
2976 $this->setMulticurrencyRate($rate, 2);
2977 }
2978
2979 return 1;
2980 } else {
2981 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2982 $this->error = $this->db->error();
2983 return -1;
2984 }
2985 } else {
2986 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2987 $this->error = 'Status of the object is incompatible '.$this->status;
2988 return -2;
2989 }
2990 }
2991
2999 public function setMulticurrencyRate($rate, $mode = 1)
3000 {
3001 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
3002 if ($this->status >= 0 || $this->element == 'societe') {
3003 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3004 $sql .= " SET multicurrency_tx = ".((float) $rate);
3005 $sql .= ' WHERE rowid='.((int) $this->id);
3006
3007 if ($this->db->query($sql)) {
3008 $this->multicurrency_tx = $rate;
3009
3010 // Update line price
3011 if (!empty($this->lines)) {
3012 foreach ($this->lines as &$line) {
3013 // A credit line (deposit, credit note, discount applied to the invoice) is not priced at the invoice rate:
3014 // both its amounts are the historical ones of the credit, so a rate change must leave it untouched.
3015 if (!empty($line->fk_remise_except)) {
3016 continue;
3017 }
3018
3019 // Amounts in company currency will be recalculated
3020 if ($mode == 1) {
3021 $line->subprice = 0;
3022 }
3023
3024 // Amounts in foreign currency will be recalculated
3025 if ($mode == 2) {
3026 $line->multicurrency_subprice = 0;
3027 }
3028
3029 switch ($this->element) {
3030 case 'propal':
3033 '@phan-var-force Propal $this';
3034 '@phan-var-force PropaleLigne $line';
3035 $this->updateline(
3036 $line->id,
3037 $line->subprice,
3038 $line->qty,
3039 $line->remise_percent,
3040 $line->tva_tx,
3041 $line->localtax1_tx,
3042 $line->localtax2_tx,
3043 ($line->description ? $line->description : $line->desc),
3044 'HT',
3045 $line->info_bits,
3046 $line->special_code,
3047 $line->fk_parent_line,
3048 $line->skip_update_total,
3049 $line->fk_fournprice,
3050 $line->pa_ht,
3051 $line->label,
3052 $line->product_type,
3053 $line->date_start,
3054 $line->date_end,
3055 $line->array_options,
3056 $line->fk_unit,
3057 $line->multicurrency_subprice
3058 );
3059 break;
3060 case 'commande':
3063 '@phan-var-force Commande $this';
3064 '@phan-var-force OrderLine $line';
3065 $this->updateline(
3066 $line->id,
3067 ($line->description ? $line->description : $line->desc),
3068 $line->subprice,
3069 $line->qty,
3070 $line->remise_percent,
3071 $line->tva_tx,
3072 $line->localtax1_tx,
3073 $line->localtax2_tx,
3074 'HT',
3075 $line->info_bits,
3076 $line->date_start,
3077 $line->date_end,
3078 $line->product_type,
3079 $line->fk_parent_line,
3080 $line->skip_update_total,
3081 $line->fk_fournprice,
3082 $line->pa_ht,
3083 $line->label,
3084 $line->special_code,
3085 $line->array_options,
3086 $line->fk_unit,
3087 $line->multicurrency_subprice
3088 );
3089 break;
3090 case 'facture':
3093 '@phan-var-force Facture $this';
3094 '@phan-var-force FactureLigne $line';
3095 $this->updateline(
3096 $line->id,
3097 ($line->description ? $line->description : $line->desc),
3098 $line->subprice,
3099 $line->qty,
3100 $line->remise_percent,
3101 $line->date_start,
3102 $line->date_end,
3103 $line->tva_tx,
3104 $line->localtax1_tx,
3105 $line->localtax2_tx,
3106 'HT',
3107 $line->info_bits,
3108 $line->product_type,
3109 $line->fk_parent_line,
3110 $line->skip_update_total,
3111 $line->fk_fournprice,
3112 $line->pa_ht,
3113 $line->label,
3114 $line->special_code,
3115 $line->array_options,
3116 $line->situation_percent,
3117 $line->fk_unit,
3118 $line->multicurrency_subprice
3119 );
3120 break;
3121 case 'facturerec':
3124 '@phan-var-force FactureRec $this';
3125 '@phan-var-force FactureLigneRec $line';
3126 $this->updateline(
3127 $line->id,
3128 ($line->description ? $line->description : $line->desc),
3129 $line->subprice,
3130 $line->qty,
3131 $line->tva_tx,
3132 $line->localtax1_tx,
3133 $line->localtax2_tx,
3134 $line->fk_product,
3135 $line->remise_percent,
3136 'HT',
3137 $line->info_bits,
3138 0,
3139 0,
3140 $line->product_type,
3141 $line->rang,
3142 $line->special_code,
3143 $line->label,
3144 $line->fk_unit,
3145 $line->multicurrency_subprice,
3146 0,
3147 $line->date_start, // Ignore real issue with FactureLigneRec @phan-suppress-current-line PhanUndeclaredProperty
3148 $line->date_end, // Ignore real issue with FactureLigneRec @phan-suppress-current-line PhanUndeclaredProperty
3149 $line->fk_fournprice,
3150 $line->pa_ht,
3151 $line->fk_parent_line
3152 );
3153 break;
3154 case 'supplier_proposal':
3157 '@phan-var-force SupplierProposal $this';
3158 '@phan-var-force SupplierProposalLine $line';
3159 $this->updateline(
3160 $line->id,
3161 $line->subprice,
3162 $line->qty,
3163 $line->remise_percent,
3164 $line->tva_tx,
3165 $line->localtax1_tx,
3166 $line->localtax2_tx,
3167 ($line->description ? $line->description : $line->desc),
3168 'HT',
3169 $line->info_bits,
3170 $line->special_code,
3171 $line->fk_parent_line,
3172 $line->skip_update_total,
3173 $line->fk_fournprice,
3174 $line->pa_ht,
3175 $line->label,
3176 $line->product_type,
3177 $line->array_options,
3178 $line->ref_fourn,
3179 (int) $line->fk_unit,
3180 $line->multicurrency_subprice
3181 );
3182 break;
3183 case 'order_supplier':
3186 '@phan-var-force CommandeFournisseur $this';
3187 '@phan-var-force CommandeFournisseurLigne $line';
3188 $this->updateline(
3189 $line->id,
3190 ($line->description ? $line->description : $line->desc),
3191 $line->subprice,
3192 $line->qty,
3193 $line->remise_percent,
3194 $line->tva_tx,
3195 $line->localtax1_tx,
3196 $line->localtax2_tx,
3197 'HT',
3198 $line->info_bits,
3199 $line->product_type,
3200 0,
3201 $line->date_start,
3202 $line->date_end,
3203 $line->array_options,
3204 $line->fk_unit,
3205 $line->multicurrency_subprice,
3206 $line->ref_supplier
3207 );
3208 break;
3209 case 'invoice_supplier':
3212 '@phan-var-force FactureFournisseur $this';
3213 '@phan-var-force SupplierInvoiceLIne $line';
3214 $this->updateline(
3215 $line->id,
3216 ($line->description ? $line->description : $line->desc),
3217 $line->subprice,
3218 $line->tva_tx,
3219 $line->localtax1_tx,
3220 $line->localtax2_tx,
3221 $line->qty,
3222 0,
3223 'HT',
3224 $line->info_bits,
3225 $line->product_type,
3226 $line->remise_percent,
3227 0,
3228 $line->date_start,
3229 $line->date_end,
3230 $line->array_options,
3231 $line->fk_unit,
3232 $line->multicurrency_subprice,
3233 $line->ref_supplier
3234 );
3235 break;
3236 default:
3237 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3238 break;
3239 }
3240 }
3241 }
3242
3243 return 1;
3244 } else {
3245 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3246 $this->error = $this->db->error();
3247 return -1;
3248 }
3249 } else {
3250 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3251 $this->error = 'Status of the object is incompatible '.$this->status;
3252 return -2;
3253 }
3254 }
3255
3263 public function setPaymentTerms($id, $deposit_percent = null)
3264 {
3265 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.formatLogObject($deposit_percent).')');
3266 if ($this->status >= 0 || $this->element == 'societe') {
3267 // TODO uniformize field name
3268 $fieldname = 'fk_cond_reglement';
3269 if ($this->element == 'societe') {
3270 $fieldname = 'cond_reglement';
3271 }
3272 if (get_class($this) == 'Fournisseur') {
3273 $fieldname = 'cond_reglement_supplier';
3274 }
3275
3276 if (empty($deposit_percent) || $deposit_percent < 0) {
3277 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3278 }
3279
3280 if ($deposit_percent > 100) {
3281 $deposit_percent = 100;
3282 }
3283
3284 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3285 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3286 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3287 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3288 }
3289 $sql .= ' WHERE rowid = '.((int) $this->id);
3290
3291 if ($this->db->query($sql)) {
3292 $this->cond_reglement_id = $id;
3293 // for supplier
3294 if (get_class($this) == 'Fournisseur') {
3295 $this->cond_reglement_supplier_id = $id;
3296 }
3297 $this->deposit_percent = $deposit_percent;
3298 return 1;
3299 } else {
3300 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3301 $this->error = $this->db->error();
3302 return -1;
3303 }
3304 } else {
3305 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3306 $this->error = 'Status of the object is incompatible '.$this->status;
3307 return -2;
3308 }
3309 }
3310
3317 public function setTransportMode($id)
3318 {
3319 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3320 if ($this->status >= 0 || $this->element == 'societe') {
3321 $fieldname = 'fk_transport_mode';
3322 if ($this->element == 'societe') {
3323 $fieldname = 'transport_mode';
3324 }
3325 if (get_class($this) == 'Fournisseur') {
3326 $fieldname = 'transport_mode_supplier';
3327 }
3328
3329 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3330 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3331 $sql .= ' WHERE rowid='.((int) $this->id);
3332
3333 if ($this->db->query($sql)) {
3334 $this->transport_mode_id = $id;
3335 // for supplier
3336 if (get_class($this) == 'Fournisseur') {
3337 $this->transport_mode_supplier_id = $id;
3338 }
3339 return 1;
3340 } else {
3341 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3342 $this->error = $this->db->error();
3343 return -1;
3344 }
3345 } else {
3346 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3347 $this->error = 'Status of the object is incompatible '.$this->status;
3348 return -2;
3349 }
3350 }
3351
3359 public function setDeliveryAddress($id)
3360 {
3361 $fieldname = 'fk_delivery_address';
3362 if ($this->element == 'delivery' || $this->element == 'shipping') {
3363 $fieldname = 'fk_address';
3364 }
3365
3366 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$this->db->sanitize($fieldname)." = ".((int) $id);
3367 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3368
3369 if ($this->db->query($sql)) {
3370 $this->fk_delivery_address = $id;
3371 return 1;
3372 } else {
3373 $this->error = $this->db->error();
3374 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3375 return -1;
3376 }
3377 }
3378
3379
3388 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3389 {
3390 global $user;
3391
3392 if (empty($userused)) {
3393 $userused = $user;
3394 }
3395
3396 $error = 0;
3397
3398 if (!$this->table_element) {
3399 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3400 return -1;
3401 }
3402
3403 $this->db->begin();
3404
3405 if ($shipping_method_id < 0) {
3406 $shipping_method_id = 'NULL';
3407 }
3408 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3409
3410 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3411 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3412 $sql .= " WHERE rowid=".((int) $this->id);
3413 $resql = $this->db->query($sql);
3414 if (!$resql) {
3415 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3416 $this->error = $this->db->lasterror();
3417 $error++;
3418 } else {
3419 if (!$notrigger) {
3420 // Call trigger
3421 $this->context = array('shippingmethodupdate' => 1);
3422 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3423 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3424 if ($result < 0) {
3425 $error++;
3426 }
3427 // End call trigger
3428 }
3429 }
3430 if ($error) {
3431 $this->db->rollback();
3432 return -1;
3433 } else {
3434 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3435 $this->db->commit();
3436 return 1;
3437 }
3438 }
3439
3440
3447 public function setWarehouse($warehouse_id)
3448 {
3449 if (!$this->table_element) {
3450 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3451 return -1;
3452 }
3453 if ($warehouse_id < 0) {
3454 $warehouse_id = 'NULL';
3455 }
3456 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3457
3458 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3459 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3460 $sql .= " WHERE rowid=".((int) $this->id);
3461
3462 if ($this->db->query($sql)) {
3463 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3464 return 1;
3465 } else {
3466 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3467 $this->error = $this->db->error();
3468 return 0;
3469 }
3470 }
3471
3472
3480 public function setDocModel($user, $modelpdf)
3481 {
3482 if (!$this->table_element) {
3483 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3484 return -1;
3485 }
3486
3487 $newmodelpdf = dol_trunc($modelpdf, 255);
3488
3489 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3490 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3491 $sql .= " WHERE rowid = ".((int) $this->id);
3492
3493 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3494 $resql = $this->db->query($sql);
3495 if ($resql) {
3496 $this->model_pdf = $modelpdf;
3497 return 1;
3498 } else {
3499 dol_print_error($this->db);
3500 return 0;
3501 }
3502 }
3503
3504
3513 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3514 {
3515 global $user;
3516
3517 if (empty($userused)) {
3518 $userused = $user;
3519 }
3520
3521 $error = 0;
3522
3523 if (!$this->table_element) {
3524 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3525 return -1;
3526 }
3527 $this->db->begin();
3528
3529 if ($fk_account < 0) {
3530 $fk_account = 'NULL';
3531 }
3532 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3533
3534 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3535 $sql .= " SET fk_account = ".((int) $fk_account);
3536 $sql .= " WHERE rowid=".((int) $this->id);
3537
3538 $resql = $this->db->query($sql);
3539 if (!$resql) {
3540 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3541 $this->error = $this->db->lasterror();
3542 $error++;
3543 } else {
3544 if (!$notrigger) {
3545 // Call trigger
3546 $this->context['bankaccountupdate'] = 1;
3547
3548 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3549 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3550 if ($result < 0) {
3551 $error++;
3552 }
3553 // End call trigger
3554 }
3555 }
3556 if ($error) {
3557 $this->db->rollback();
3558 return -1;
3559 } else {
3560 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3561 $this->db->commit();
3562 return 1;
3563 }
3564 }
3565
3566
3567 // TODO: Move line related operations to CommonObjectLine?
3568
3569 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3579 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3580 {
3581 // phpcs:enable
3582 if (!$this->table_element_line) {
3583 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3584 return -1;
3585 }
3586 if (!$this->fk_element) {
3587 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3588 return -1;
3589 }
3590
3591 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3592 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3593 $fieldposition = 'position';
3594 }
3595
3596 // Count number of lines to reorder (according to choice $renum)
3597 $nl = 0;
3598 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3599 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3600 if (!$renum) {
3601 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = 0";
3602 }
3603 if ($renum) {
3604 $sql .= " AND " . $this->db->sanitize($fieldposition) . " <> 0";
3605 }
3606
3607 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3608 $resql = $this->db->query($sql);
3609 if ($resql) {
3610 $row = $this->db->fetch_row($resql);
3611 $nl = $row[0];
3612 } else {
3613 dol_print_error($this->db);
3614 }
3615 if ($nl > 0) {
3616 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3617 $rows = array();
3618
3619 // We first search all lines that are parent lines (for multilevel details lines)
3620 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3621 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3622 if ($fk_parent_line) {
3623 $sql .= ' AND fk_parent_line IS NULL';
3624 }
3625 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC, rowid " . $this->db->sanitize($rowidorder);
3626
3627 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3628 $resql = $this->db->query($sql);
3629 if ($resql) {
3630 $i = 0;
3631 $num = $this->db->num_rows($resql);
3632 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3633 while ($i < $num) {
3634 $row = $this->db->fetch_row($resql);
3635 $rows[] = $row[0]; // Add parent line into array rows
3636 if ($fk_parent_line) {
3637 $children = $this->getChildrenOfLine($row[0], $grandchild);
3638 }
3639 if (!empty($children)) {
3640 foreach ($children as $child) {
3641 array_push($rows, $child);
3642 }
3643 }
3644 $i++;
3645 }
3646
3647 // Now we set a new number for each lines (parent and children with children included into parent tree)
3648 if (!empty($rows)) {
3649 foreach ($rows as $key => $row) {
3650 $this->updateRangOfLine($row, ($key + 1));
3651 }
3652 }
3653 } else {
3654 dol_print_error($this->db);
3655 }
3656 }
3657 return 1;
3658 }
3659
3667 public function getChildrenOfLine($id, $includealltree = 0)
3668 {
3669 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3670 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3671 $fieldposition = 'position';
3672 }
3673
3674 $rows = array();
3675
3676 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3677 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3678 $sql .= ' AND fk_parent_line = '.((int) $id);
3679 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC";
3680
3681 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3682
3683 $resql = $this->db->query($sql);
3684 if (!$resql || $this->db->num_rows($resql) <= 0) {
3685 return array();
3686 }
3687
3688 while ($row = $this->db->fetch_row($resql)) {
3689 $rows[] = $row[0];
3690 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3691 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3692 }
3693 }
3694 return $rows;
3695 }
3696
3697 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3705 public function line_up($rowid, $fk_parent_line = true)
3706 {
3707 // phpcs:enable
3708 $this->line_order(false, 'ASC', $fk_parent_line);
3709
3710 // Get rang of line
3711 $rang = $this->getRangOfLine($rowid);
3712
3713 // Update position of line
3714 $this->updateLineUp($rowid, $rang);
3715 }
3716
3717 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3725 public function line_down($rowid, $fk_parent_line = true)
3726 {
3727 // phpcs:enable
3728 $this->line_order(false, 'ASC', $fk_parent_line);
3729
3730 // Get rang of line
3731 $rang = $this->getRangOfLine($rowid);
3732
3733 // Get max value for rang
3734 $max = $this->line_max();
3735
3736 // Update position of line
3737 $this->updateLineDown($rowid, $rang, $max);
3738 }
3739
3747 public function updateRangOfLine($rowid, $rang)
3748 {
3749 global $hookmanager;
3750 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3751 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3752 $fieldposition = 'position';
3753 }
3754
3755 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3756 $sql .= ' WHERE rowid = '.((int) $rowid);
3757 if ($this->id > 0 && !empty($this->fk_element)) {
3758 // The line must belong to the object we reorder lines of
3759 $sql .= " AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3760 }
3761
3762 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3763 if (!$this->db->query($sql)) {
3764 dol_print_error($this->db);
3765 return -1;
3766 }
3767
3768 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3769 $action = '';
3770 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3771 return 1;
3772 }
3773
3774 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3781 public function line_ajaxorder($rows)
3782 {
3783 // phpcs:enable
3784 $num = count($rows);
3785 for ($i = 0; $i < $num; $i++) {
3786 $this->updateRangOfLine($rows[$i], ($i + 1));
3787 }
3788 }
3789
3797 public function updateLineUp($rowid, $rang)
3798 {
3799 if ($rang <= 1) {
3800 return -1;
3801 }
3802
3803 $fieldposition = 'rang';
3804 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3805 $fieldposition = 'position';
3806 }
3807
3808 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3809 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3810 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang - 1));
3811 if (!$this->db->query($sql)) {
3812 $this->error = $this->db->lasterror();
3813 return -1;
3814 }
3815
3816 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang - 1));
3817 $sql .= ' WHERE rowid = '.((int) $rowid);
3818 if (!$this->db->query($sql)) {
3819 $this->error = $this->db->lasterror();
3820 return -1;
3821 }
3822
3823 return 1;
3824 }
3825
3834 public function updateLineDown($rowid, $rang, $max)
3835 {
3836 if ($rang >= $max) {
3837 return -1;
3838 }
3839
3840 $fieldposition = 'rang';
3841 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3842 $fieldposition = 'position';
3843 }
3844
3845 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3846 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3847 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang + 1));
3848 if (!$this->db->query($sql)) {
3849 $this->error = $this->db->lasterror();
3850 return -1;
3851 }
3852
3853 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang + 1));
3854 $sql .= ' WHERE rowid = '.((int) $rowid);
3855 if (!$this->db->query($sql)) {
3856 $this->error = $this->db->lasterror();
3857 return -1;
3858 }
3859
3860 return 1;
3861 }
3862
3869 public function getRangOfLine($rowid)
3870 {
3871 $fieldposition = 'rang';
3872 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3873 $fieldposition = 'position';
3874 }
3875
3876 $sql = "SELECT " . $this->db->sanitize($fieldposition) . " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3877 $sql .= " WHERE rowid = ".((int) $rowid);
3878
3879 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3880 $resql = $this->db->query($sql);
3881 if (!$resql) {
3882 return 0;
3883 }
3884
3885 $row = $this->db->fetch_row($resql);
3886 return $row[0];
3887 }
3888
3895 public function getIdOfLine($rang)
3896 {
3897 $fieldposition = 'rang';
3898 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3899 $fieldposition = 'position';
3900 }
3901
3902 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3903 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3904 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = ".((int) $rang);
3905 $resql = $this->db->query($sql);
3906 if (!$resql) {
3907 return 0;
3908 }
3909
3910 $row = $this->db->fetch_row($resql);
3911 return $row[0];
3912 }
3913
3923 public function roundQtyToPackaging($qty, $packaging)
3924 {
3925 if (empty($packaging) || !is_numeric($packaging) || (float) $packaging <= 0) {
3926 return $qty;
3927 }
3928 $sign = ((float) $qty < 0 ? -1 : 1);
3929 $absqty = abs((float) $qty);
3930 if ($absqty < (float) $packaging) {
3931 return $sign * (float) $packaging;
3932 }
3933 if ((float) price2num(fmod($absqty, (float) $packaging), 'MS')) {
3934 $coeff = intval($absqty / (float) $packaging) + 1;
3935 return $sign * (float) price2num((float) $packaging * $coeff, 'MS');
3936 }
3937 return $qty;
3938 }
3939
3940 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3947 public function line_max($fk_parent_line = 0)
3948 {
3949 // phpcs:enable
3950 $positionfield = 'rang';
3951 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3952 $positionfield = 'position';
3953 }
3954
3955 $sql = "SELECT max(".$this->db->sanitize($positionfield).") FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3956 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3957
3958 // Search the last rang with fk_parent_line
3959 if ($fk_parent_line) {
3960 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3961 }
3962
3963 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3964 $resql = $this->db->query($sql);
3965 if ($resql) {
3966 $row = $this->db->fetch_row($resql);
3967
3968 if ($fk_parent_line) {
3969 if (!empty($row[0])) {
3970 return $row[0];
3971 } else {
3972 return $this->getRangOfLine($fk_parent_line);
3973 }
3974 } else {
3975 return $row[0];
3976 }
3977 }
3978
3979 return 0;
3980 }
3981
3982 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3989 public function update_ref_ext($ref_ext)
3990 {
3991 // phpcs:enable
3992 if (!$this->table_element) {
3993 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3994 return -1;
3995 }
3996
3997 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3998 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3999 // @phan-suppress-next-line PhanUndeclaredProperty
4000 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
4001
4002 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
4003 if ($this->db->query($sql)) {
4004 $this->ref_ext = $ref_ext;
4005 return 1;
4006 } else {
4007 $this->error = $this->db->error();
4008 return -1;
4009 }
4010 }
4011
4012 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4021 public function update_note($note, $suffix = '', $notrigger = 0)
4022 {
4023 // phpcs:enable
4024 global $user;
4025
4026 if (!$this->table_element) {
4027 $this->error = 'update_note was called on object with property table_element not defined';
4028 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
4029 return -1;
4030 }
4031 if (!in_array($suffix, array('', '_public', '_private'))) {
4032 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
4033 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
4034 return -2;
4035 }
4036
4037 $newsuffix = $suffix;
4038
4039 // Special case
4040 if ($this->table_element == 'product' && $newsuffix == '_private') {
4041 $newsuffix = '';
4042 }
4043 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
4044 $fieldusermod = "fk_user_mod";
4045 } elseif ($this->table_element == 'ecm_files') {
4046 $fieldusermod = "fk_user_m";
4047 } else {
4048 $fieldusermod = "fk_user_modif";
4049 }
4050 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
4051 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
4052 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
4053 $sql .= " WHERE rowid = ".((int) $this->id);
4054
4055 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
4056 if ($this->db->query($sql)) {
4057 if ($suffix == '_public') {
4058 $this->note_public = $note;
4059 } elseif ($suffix == '_private') {
4060 $this->note_private = $note;
4061 } else {
4062 $this->note = $note; // deprecated
4063 $this->note_private = $note;
4064 }
4065 if (empty($notrigger)) {
4066 // TODO Use the $this->TRIGGER_PREFIX when implemented
4067 switch ($this->element) {
4068 case 'societe':
4069 $triggerName = 'COMPANY_MODIFY';
4070 break;
4071 case 'facture':
4072 $triggerName = 'BILL_MODIFY';
4073 break;
4074 case 'invoice_supplier':
4075 $triggerName = 'BILL_SUPPLIER_MODIFY';
4076 break;
4077 case 'facturerec':
4078 $triggerName = 'BILLREC_MODIFIY';
4079 break;
4080 case 'expensereport':
4081 $triggerName = 'EXPENSE_REPORT_MODIFY';
4082 break;
4083 default:
4084 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
4085 }
4086 $ret = $this->call_trigger($triggerName, $user);
4087 if ($ret < 0) {
4088 return -1;
4089 }
4090 }
4091 return 1;
4092 } else {
4093 $this->error = $this->db->lasterror();
4094 return -1;
4095 }
4096 }
4097
4098 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4107 public function update_note_public($note)
4108 {
4109 // phpcs:enable
4110 return $this->update_note($note, '_public');
4111 }
4112
4113 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4124 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4125 {
4126 // phpcs:enable
4127 global $conf, $hookmanager, $action;
4128
4129 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4130 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4131 if ($reshook > 0) {
4132 return 1; // replacement code
4133 } elseif ($reshook < 0) {
4134 return -1; // failure
4135 } // reshook = 0 => execute normal code
4136
4137 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4138 $isElementForSupplier = false;
4139 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4140 $MODULE = "";
4141 if ($this->element == 'propal') {
4142 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4143 } elseif ($this->element == 'commande' || $this->element == 'order') {
4144 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4145 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4146 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4147 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4148 $isElementForSupplier = true;
4149 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4150 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4151 $isElementForSupplier = true;
4152 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4153 } elseif ($this->element == 'supplier_proposal') {
4154 $isElementForSupplier = true;
4155 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4156 }
4157 if ($isElementForSupplier) {
4158 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4159 }
4160
4161 if (!empty($MODULE)) {
4162 if (getDolGlobalString($MODULE)) {
4163 $modsactivated = explode(',', getDolGlobalString($MODULE));
4164 foreach ($modsactivated as $mod) {
4165 if (isModEnabled($mod)) {
4166 return 1; // update was disabled by specific setup
4167 }
4168 }
4169 }
4170 }
4171
4172 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4173
4174 $forcedroundingmode = $roundingadjust;
4175 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4176 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4177 } elseif ($forcedroundingmode == 'auto') {
4178 $forcedroundingmode = '0';
4179 }
4180
4181 $error = 0;
4182
4183 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4184
4185 // Define constants to find lines to sum (field name into the table_element_line not into table_element)
4186 $fieldtva = 'total_tva';
4187 $fieldlocaltax1 = 'total_localtax1';
4188 $fieldlocaltax2 = 'total_localtax2';
4189 $fieldup = 'subprice';
4190 $base_price_type = 'HT';
4191 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4192 $fieldtva = 'tva';
4193 $fieldup = 'pu_ht';
4194 }
4195 if ($this->element == 'invoice_supplier_rec') {
4196 $fieldup = 'pu_ht';
4197 }
4198 if ($this->element == 'expensereport') {
4199 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4200 // 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
4201 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4202 $forcedroundingmode = '0';
4203 $fieldup = 'value_unit';
4204 $base_price_type = 'TTC';
4205 }
4206
4207 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4208 $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,";
4209 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4210 $sql .= ' info_bits, product_type,';
4211 if ($this->element == 'expensereport') {
4212 $sql .= ' comments as description';
4213 } else {
4214 $sql .= ' description';
4215 }
4216 if ($this->table_element_line == 'facturedet') {
4217 $sql .= ', situation_percent';
4218 }
4219 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4220 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
4221 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
4222 if ($exclspec) {
4223 $product_field = 'product_type';
4224 if ($this->table_element_line == 'contratdet') {
4225 $product_field = ''; // contratdet table has no product_type field
4226 }
4227 if ($product_field) {
4228 $sql .= " AND ".$this->db->sanitize($product_field)." <> 9";
4229 }
4230 }
4231 $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
4232
4233 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4234
4235 $resql = $this->db->query($sql);
4236 if ($resql) {
4237 $this->total_ht = 0;
4238 $this->total_tva = 0;
4239 $this->total_localtax1 = 0;
4240 $this->total_localtax2 = 0;
4241 $this->total_ttc = 0;
4242 $total_ht_by_vats = array();
4243 $total_tva_by_vats = array();
4244 $total_ttc_by_vats = array();
4245 $this->multicurrency_total_ht = 0;
4246 $this->multicurrency_total_tva = 0;
4247 $this->multicurrency_total_ttc = 0;
4248
4249 $this->db->begin();
4250
4251 $num = $this->db->num_rows($resql);
4252 $i = 0;
4253 while ($i < $num) {
4254 $obj = $this->db->fetch_object($resql);
4255
4256 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4257 $parameters = array('fk_element' => $obj->rowid);
4258 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4259
4260 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'
4261 // This part of code is to fix data. We should not call it too often.
4262 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4263 $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);
4264
4265 $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.
4266 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4267 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4268 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4269
4270 if ($diff_on_current_total) {
4271 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4272 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4273 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4274 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4275 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4276 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);
4277 $resqlfix = $this->db->query($sqlfix);
4278 if (!$resqlfix) {
4279 dol_print_error($this->db, 'Failed to update line');
4280 }
4281 $obj->total_tva = $tmpcal[1];
4282 $obj->total_ttc = $tmpcal[2];
4283 $obj->multicurrency_total_tva = $tmpcal[17];
4284 $obj->multicurrency_total_ttc = $tmpcal[18];
4285 } elseif ($diff_when_using_price_ht) {
4286 // 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
4287 // 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),
4288 // so we continue only if price HT are same.
4289 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4290 // 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
4291 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4292 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4293 // 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,
4294 // 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.
4295 // 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.
4296 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4297 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4298 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4299 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4300
4301 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);
4302
4303 $resqlfix = $this->db->query($sqlfix);
4304 if (!$resqlfix) {
4305 dol_print_error($this->db, 'Failed to update line');
4306 }
4307 $obj->total_tva = $tmpcal[1];
4308 $obj->total_ttc = $tmpcal[2];
4309 $obj->multicurrency_total_tva = $tmpcal[17];
4310 $obj->multicurrency_total_ttc = $tmpcal[18];
4311 }
4312 }
4313 }
4314 }
4315
4316 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4317 $this->total_tva += $obj->total_tva;
4318 $this->total_localtax1 += $obj->total_localtax1;
4319 $this->total_localtax2 += $obj->total_localtax2;
4320 $this->total_ttc += $obj->total_ttc;
4321 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4322 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4323 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4324
4325 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4326 $total_ht_by_vats[$obj->vatrate] = 0;
4327 }
4328 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4329 $total_tva_by_vats[$obj->vatrate] = 0;
4330 }
4331 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4332 $total_ttc_by_vats[$obj->vatrate] = 0;
4333 }
4334 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4335 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4336 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4337
4338 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4339 if ($base_price_type == 'TTC') {
4340 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4341 } else {
4342 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4343 }
4344 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4345 //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";
4346 if ($diff) {
4347 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4348 if (abs((float) $diff) > $maxdiff) {
4349 // If error is more than 10 times the accuracy of rounding. This should not happen.
4350 $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.';
4351 dol_syslog($errmsg, LOG_WARNING);
4352 $this->error = $errmsg;
4353 $error++;
4354 break;
4355 }
4356
4357 if ($base_price_type == 'TTC') {
4358 $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);
4359 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);
4360 } else {
4361 $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);
4362 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);
4363 }
4364
4365 $resqlfix = $this->db->query($sqlfix);
4366
4367 if (!$resqlfix) {
4368 dol_print_error($this->db, 'Failed to update line');
4369 }
4370
4371 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4372 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4373 if ($base_price_type == 'TTC') {
4374 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4375 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4376 } else {
4377 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4378 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4379 }
4380 }
4381 }
4382
4383 $i++;
4384 }
4385
4386 // Add revenue stamp to total
4387 // @phan-suppress-next-line PhanUndeclaredProperty
4388 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4389 // @phan-suppress-next-line PhanUndeclaredProperty
4390 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4391
4392 // Situations totals
4393 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
4394 '@phan-var-force Facture $this';
4395 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
4396 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4397 if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) {
4398 $prev_sits = $this->get_prev_sits();
4399
4400 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4401 $this->total_ht -= $sit->total_ht;
4402 $this->total_tva -= $sit->total_tva;
4403 $this->total_localtax1 -= $sit->total_localtax1;
4404 $this->total_localtax2 -= $sit->total_localtax2;
4405 $this->total_ttc -= $sit->total_ttc;
4406 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4407 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4408 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4409 }
4410 }
4411 }
4412 }
4413
4414 // Clean total
4415 $this->total_ht = (float) price2num($this->total_ht);
4416 $this->total_tva = (float) price2num($this->total_tva);
4417 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4418 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4419 $this->total_ttc = (float) price2num($this->total_ttc);
4420
4421 $this->db->free($resql);
4422
4423 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4424 $fieldht = 'total_ht';
4425 $fieldtva = 'tva';
4426 $fieldlocaltax1 = 'localtax1';
4427 $fieldlocaltax2 = 'localtax2';
4428 $fieldttc = 'total_ttc';
4429 // Specific code for backward compatibility with old field names
4430 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4431 $fieldtva = 'total_tva';
4432 }
4433
4434 if (!$error && empty($nodatabaseupdate)) {
4435 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4436 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4437 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4438 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4439 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4440 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4441 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4442 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4443 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4444 $sql .= " WHERE rowid = ".((int) $this->id);
4445
4446 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4447 $resql = $this->db->query($sql);
4448
4449 if (!$resql) {
4450 $error++;
4451 $this->error = $this->db->lasterror();
4452 $this->errors[] = $this->db->lasterror();
4453 }
4454 }
4455
4456 if (!$error) {
4457 $this->db->commit();
4458 return 1;
4459 } else {
4460 $this->db->rollback();
4461 return -1;
4462 }
4463 } else {
4464 dol_print_error($this->db, 'Bad request in update_price');
4465 return -1;
4466 }
4467 }
4468
4469 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4480 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4481 {
4482 // phpcs:enable
4483 global $user, $hookmanager, $action;
4484 dol_syslog(__METHOD__, LOG_DEBUG);
4485
4486 if (empty($this->origin_type) && !empty($this->origin)) {
4487 $this->origin_type = $this->origin;
4488 }
4489 $origin = (!empty($origin) ? $origin : $this->origin_type);
4490 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4491 $f_user = isset($f_user) ? $f_user : $user;
4492
4493 // Special case
4494 if ($origin == 'order') {
4495 $origin = 'commande';
4496 }
4497 if ($origin == 'invoice') {
4498 $origin = 'facture';
4499 }
4500 if ($origin == 'invoice_template') {
4501 $origin = 'facturerec';
4502 }
4503 if ($origin == 'supplierorder') {
4504 $origin = 'order_supplier';
4505 }
4506
4507 // Add module part to target type
4508 $targettype = $this->getElementType();
4509
4510 $parameters = array('targettype' => $targettype);
4511 // Hook for explicitly set the targettype if it must be different than $this->element
4512 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4513 if ($reshook > 0) {
4514 if (!empty($hookmanager->resArray['targettype'])) {
4515 $targettype = $hookmanager->resArray['targettype'];
4516 }
4517 }
4518
4519 $this->db->begin();
4520 $error = 0;
4521
4522 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4523 $sql .= "fk_source";
4524 $sql .= ", sourcetype";
4525 $sql .= ", fk_target";
4526 $sql .= ", targettype";
4527 $sql .= ") VALUES (";
4528 $sql .= ((int) $origin_id);
4529 $sql .= ", '" . $this->db->escape($origin) . "'";
4530 $sql .= ", " . ((int) $this->id);
4531 $sql .= ", '" . $this->db->escape($targettype) . "'";
4532 $sql .= ")";
4533
4534 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4535 if ($this->db->query($sql)) {
4536 // If we link a supplier order to a supplier invoice that already uses discounts coming from other supplier invoices
4537 // (deposit invoice / credit note used as payment), also link those source invoices to the same supplier order.
4538 // This helps keep "linked elements" consistent when totals are impacted by such discounts.
4539 if ($origin === 'order_supplier' && $targettype === 'invoice_supplier' && $this->element === 'invoice_supplier' && (int) $origin_id > 0 && (int) $this->id > 0) {
4540 $sourceinvoices = array();
4541
4542 $sqlsources = "SELECT DISTINCT rc.fk_invoice_supplier_source as rowid";
4543 $sqlsources .= " FROM ".$this->db->prefix()."societe_remise_except as rc";
4544 $sqlsources .= " WHERE rc.fk_invoice_supplier = ".((int) $this->id);
4545 $sqlsources .= " AND rc.fk_invoice_supplier_source IS NOT NULL";
4546
4547 $resqlsources = $this->db->query($sqlsources);
4548 if ($resqlsources) {
4549 while ($objsrc = $this->db->fetch_object($resqlsources)) {
4550 $srcid = (int) $objsrc->rowid;
4551 if ($srcid > 0 && $srcid !== (int) $this->id) {
4552 $sourceinvoices[$srcid] = $srcid;
4553 }
4554 }
4555 } else {
4556 dol_syslog(get_class($this)."::add_object_linked Failed to read supplier invoice discounts sources", LOG_WARNING);
4557 }
4558
4559 if (!empty($sourceinvoices)) {
4560 $existing = array();
4561 $sourcelist = implode(',', $sourceinvoices);
4562
4563 $sqlexists = "SELECT fk_target";
4564 $sqlexists .= " FROM ".$this->db->prefix()."element_element";
4565 $sqlexists .= " WHERE fk_source = ".((int) $origin_id);
4566 $sqlexists .= " AND sourcetype = '".$this->db->escape($origin)."'";
4567 $sqlexists .= " AND targettype = '".$this->db->escape($targettype)."'";
4568 $sqlexists .= " AND fk_target IN (".$this->db->sanitize($sourcelist).")";
4569
4570 $resqlexists = $this->db->query($sqlexists);
4571 if ($resqlexists) {
4572 while ($objexist = $this->db->fetch_object($resqlexists)) {
4573 $existing[(int) $objexist->fk_target] = 1;
4574 }
4575 } else {
4576 dol_syslog(get_class($this)."::add_object_linked Failed to read existing linked supplier invoices", LOG_WARNING);
4577 }
4578
4579 foreach ($sourceinvoices as $srcid) {
4580 if (!empty($existing[(int) $srcid])) {
4581 continue;
4582 }
4583
4584 $sqladd = "INSERT INTO " . $this->db->prefix() . "element_element (fk_source, sourcetype, fk_target, targettype)";
4585 $sqladd .= " VALUES (".((int) $origin_id).", '".$this->db->escape($origin)."', ".((int) $srcid).", '".$this->db->escape($targettype)."')";
4586 $this->db->query($sqladd); // Best-effort: do not fail original link action
4587 }
4588 }
4589 }
4590
4591 if (!$notrigger) {
4592 // Call trigger
4593 $this->context['link_origin'] = $origin;
4594 $this->context['link_origin_id'] = $origin_id;
4595
4596 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4597 if ($result < 0) {
4598 $error++;
4599 }
4600 // End call triggers
4601 }
4602 } else {
4603 $this->error = $this->db->lasterror();
4604 $error++;
4605 }
4606
4607 if (!$error) {
4608 $this->db->commit();
4609 return 1;
4610 } else {
4611 $this->db->rollback();
4612 return 0;
4613 }
4614 }
4615
4621 public function getElementType()
4622 {
4623 // 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.
4624 // 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).
4625 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4626 // Add module part to target type if object has $module property and isn't in core modules.
4627 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4628 }
4629
4630
4653 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4654 {
4655 global $hookmanager, $action;
4656
4657 // Important for pdf generation time reduction
4658 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4659 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4660 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4661 return 1;
4662 }
4663
4664 $this->linkedObjectsIds = array();
4665 $this->linkedObjects = array();
4666
4667 // Hook for allowing modules to completely alter the behavior of the method
4668 $parameters = array(
4669 'sourceid' => $sourceid,
4670 'sourcetype' => $sourcetype,
4671 'targetid' => $targetid,
4672 'targettype' => $targettype,
4673 'clause' => $clause,
4674 'alsosametype' => $alsosametype,
4675 'orderby' => $orderby,
4676 'loadalsoobjects' => $loadalsoobjects
4677 );
4678 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4679 if ($reshook > 0) {
4680 return $reshook;
4681 }
4682
4683 $justsource = false;
4684 $justtarget = false;
4685 $withtargettype = false;
4686 $withsourcetype = false;
4687
4688 // Hook for explicitly set the targettype if it must be differtent than $this->element
4689 if (is_object($hookmanager)) {
4690 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4691 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4692 if ($reshook > 0) {
4693 if (!empty($hookmanager->resArray['sourcetype'])) {
4694 $sourcetype = $hookmanager->resArray['sourcetype'];
4695 }
4696 if (!empty($hookmanager->resArray['sourceid'])) {
4697 $sourceid = $hookmanager->resArray['sourceid'];
4698 }
4699 if (!empty($hookmanager->resArray['targettype'])) {
4700 $targettype = $hookmanager->resArray['targettype'];
4701 }
4702 if (!empty($hookmanager->resArray['targetid'])) {
4703 $targetid = $hookmanager->resArray['targetid'];
4704 }
4705 }
4706 }
4707
4708 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4709 $justsource = true; // the source (id and type) is a search criteria
4710 if (!empty($targettype)) {
4711 $withtargettype = true;
4712 }
4713 }
4714 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4715 $justtarget = true; // the target (id and type) is a search criteria
4716 if (!empty($sourcetype)) {
4717 $withsourcetype = true;
4718 }
4719 }
4720
4721 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4722 $targetid = (!empty($targetid) ? $targetid : $this->id);
4723 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4724 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4725
4726 /*if (empty($sourceid) && empty($targetid))
4727 {
4728 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4729 return -1;
4730 }*/
4731
4732 // Links between objects are stored in table element_element
4733 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4734 $sql .= " FROM ".$this->db->prefix()."element_element";
4735 $sql .= " WHERE ";
4736 if ($justsource || $justtarget) {
4737 if ($justsource) {
4738 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4739 if ($withtargettype) {
4740 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4741 }
4742 } elseif ($justtarget) {
4743 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4744 if ($withsourcetype) {
4745 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4746 }
4747 }
4748 } else {
4749 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4750 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4751 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4752 $this->linkedObjectsFullLoaded[$this->id] = true;
4753 }
4754 }
4755 $sql .= $this->db->order($orderby);
4756
4757 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4758
4759 $resql = $this->db->query($sql);
4760 if ($resql) {
4761 $num = $this->db->num_rows($resql);
4762 $i = 0;
4763 while ($i < $num) {
4764 $obj = $this->db->fetch_object($resql);
4765 if ($justsource || $justtarget) {
4766 if ($justsource) {
4767 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4768 } elseif ($justtarget) {
4769 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4770 }
4771 } else {
4772 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4773 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4774 }
4775 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4776 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4777 }
4778 }
4779 $i++;
4780 }
4781
4782 if (!empty($this->linkedObjectsIds)) {
4783 $tmparray = $this->linkedObjectsIds;
4784 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4785 $element_properties = getElementProperties($objecttype);
4786 $element = $element_properties['element'];
4787 $classPath = $element_properties['classpath'];
4788 $classFile = $element_properties['classfile'];
4789 $className = $element_properties['classname'];
4790 $module = $element_properties['module'];
4791
4792 // Here $module, $classFile and $className are set, we can use them.
4793 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4794 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4795 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4796 if (class_exists($className)) {
4797 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4798 $object = new $className($this->db);
4799 '@phan-var-force CommonObject $object';
4800 $ret = $object->fetch($objectid);
4801 if ($ret > 0) {
4802 $this->linkedObjects[$objecttype][$i] = $object;
4803 }
4804 }
4805 }
4806 }
4807 } else {
4808 unset($this->linkedObjectsIds[$objecttype]);
4809 }
4810 }
4811 }
4812 return 1;
4813 } else {
4814 dol_print_error($this->db);
4815 return -1;
4816 }
4817 }
4818
4825 public function clearObjectLinkedCache()
4826 {
4827 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4828 unset($this->linkedObjectsFullLoaded[$this->id]);
4829 }
4830
4831 return 1;
4832 }
4833
4846 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4847 {
4848 global $user;
4849 $updatesource = false;
4850 $updatetarget = false;
4851 $f_user = isset($f_user) ? $f_user : $user;
4852 dol_syslog(__METHOD__, LOG_DEBUG);
4853
4854 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4855 $updatesource = true;
4856 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4857 $updatetarget = true;
4858 }
4859
4860 $this->db->begin();
4861 $error = 0;
4862
4863 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4864 if ($updatesource) {
4865 $sql .= "fk_source = " . ((int) $sourceid);
4866 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4867 $sql .= " WHERE fk_target = " . ((int) $this->id);
4868 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4869 } elseif ($updatetarget) {
4870 $sql .= "fk_target = " . ((int) $targetid);
4871 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4872 $sql .= " WHERE fk_source = " . ((int) $this->id);
4873 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4874 }
4875
4876 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4877 if ($this->db->query($sql)) {
4878 if (!$notrigger) {
4879 // Call trigger
4880 $this->context['link_source_id'] = $sourceid;
4881 $this->context['link_source_type'] = $sourcetype;
4882 $this->context['link_target_id'] = $targetid;
4883 $this->context['link_target_type'] = $targettype;
4884
4885 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4886 if ($result < 0) {
4887 $error++;
4888 }
4889 // End call triggers
4890 }
4891 } else {
4892 $this->error = $this->db->lasterror();
4893 $error++;
4894 }
4895
4896 if (!$error) {
4897 $this->db->commit();
4898 return 1;
4899 } else {
4900 $this->db->rollback();
4901 return -1;
4902 }
4903 }
4904
4918 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4919 {
4920 global $user;
4921 $deletesource = false;
4922 $deletetarget = false;
4923 $f_user = isset($f_user) ? $f_user : $user;
4924
4925 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4926 $deletesource = true;
4927 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4928 $deletetarget = true;
4929 }
4930
4931 $element = $this->getElementType();
4932 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4933 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4934 $targetid = (!empty($targetid) ? $targetid : $this->id);
4935 $targettype = (!empty($targettype) ? $targettype : $element);
4936 $this->db->begin();
4937 $error = 0;
4938
4939 if (!$notrigger) {
4940 // Call trigger
4941 $this->context['link_id'] = $rowid;
4942 $this->context['link_source_id'] = $sourceid;
4943 $this->context['link_source_type'] = $sourcetype;
4944 $this->context['link_target_id'] = $targetid;
4945 $this->context['link_target_type'] = $targettype;
4946
4947 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4948 if ($result < 0) {
4949 $error++;
4950 }
4951 // End call triggers
4952 }
4953
4954 if (!$error) {
4955 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4956 $sql .= " WHERE";
4957 if ($rowid > 0) {
4958 $sql .= " rowid = " . ((int) $rowid);
4959 } else {
4960 if ($deletesource) {
4961 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4962 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4963 } elseif ($deletetarget) {
4964 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4965 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4966 } else {
4967 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4968 $sql .= " OR";
4969 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4970 }
4971 }
4972
4973 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4974 if (!$this->db->query($sql)) {
4975 $this->error = $this->db->lasterror();
4976 $this->errors[] = $this->error;
4977 $error++;
4978 }
4979 }
4980
4981 if (!$error) {
4982 $this->db->commit();
4983 return 1;
4984 } else {
4985 $this->db->rollback();
4986 return 0;
4987 }
4988 }
4989
4999 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
5000 {
5001 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
5002 return -1;
5003 }
5004 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
5005 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
5006 }
5007
5008 global $db;
5009
5010 $sql = "SELECT ".$db->sanitize($field_select)." FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
5011 $resql = $db->query($sql);
5012
5013 $TRes = array();
5014 if (!empty($resql)) {
5015 while ($res = $db->fetch_object($resql)) {
5016 $TRes[] = $res->{$field_select};
5017 }
5018 }
5019
5020 return $TRes;
5021 }
5022
5031 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
5032 {
5033 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
5034 return -1;
5035 }
5036
5037 global $db;
5038
5039 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
5040 $resql = $db->query($sql);
5041 $n = 0;
5042 if ($resql) {
5043 $res = $db->fetch_object($resql);
5044 if ($res) {
5045 $n = $res->nb;
5046 }
5047 }
5048
5049 return $n;
5050 }
5051
5060 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
5061 {
5062 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
5063 return -1;
5064 }
5065
5066 global $db;
5067
5068 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
5069 $resql = $db->query($sql);
5070
5071 if (empty($resql)) {
5072 return 0;
5073 }
5074
5075 return 1;
5076 }
5077
5089 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
5090 {
5091 global $user;
5092
5093 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
5094
5095 $elementId = (!empty($elementId) ? $elementId : $this->id);
5096 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
5097
5098 $this->db->begin();
5099
5100 if (empty($fieldstatus)) {
5101 if ($elementTable == 'facture_rec') {
5102 $fieldstatus = "suspended";
5103 }
5104 if ($elementTable == 'mailing') {
5105 $fieldstatus = "statut";
5106 }
5107 if ($elementTable == 'cronjob') {
5108 $fieldstatus = "status";
5109 }
5110 if ($elementTable == 'user') {
5111 $fieldstatus = "statut";
5112 }
5113 if ($elementTable == 'expensereport') {
5114 $fieldstatus = "fk_statut";
5115 }
5116 if ($elementTable == 'receptiondet_batch') {
5117 $fieldstatus = "status";
5118 }
5119 if ($elementTable == 'prelevement_bons') {
5120 $fieldstatus = "statut";
5121 }
5122 if ($elementTable == 'bank_account') {
5123 $fieldstatus = "clos";
5124 }
5125 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
5126 $fieldstatus = 'status';
5127 }
5128 // If still empty
5129 if (empty($fieldstatus)) {
5130 $fieldstatus = 'fk_statut';
5131 }
5132 }
5133
5134 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
5135 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
5136 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
5137 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
5138 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
5139 $sql .= ", fk_user_valid = ".((int) $user->id);
5140 }
5141 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
5142 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
5143 }
5144 if ($status == 1 && in_array($elementTable, array('inventory'))) {
5145 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
5146 }
5147 $sql .= " WHERE rowid = ".((int) $elementId);
5148 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
5149
5150 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
5151 $resql = $this->db->query($sql);
5152 if ($resql) {
5153 $error = 0;
5154
5155 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
5156
5157 if ($nb_rows_affected > 0) {
5158 if (empty($trigkey)) {
5159 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5160 if ($this->element == 'supplier_proposal' && $status == 2) {
5161 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5162 }
5163 if ($this->element == 'supplier_proposal' && $status == 3) {
5164 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5165 }
5166 if ($this->element == 'supplier_proposal' && $status == 4) {
5167 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5168 }
5169 if ($this->element == 'fichinter' && $status == 3) {
5170 $trigkey = 'FICHINTER_CLASSIFY_DONE';
5171 }
5172 if ($this->element == 'fichinter' && $status == 2) {
5173 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5174 }
5175 if ($this->element == 'fichinter' && $status == 1) {
5176 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5177 }
5178 }
5179
5180 $this->context = array_merge($this->context, array('newstatus' => $status));
5181
5182 if ($trigkey && $trigkey != 'none') {
5183 $this->oldcopy = dol_clone($this);
5184
5185 // Call trigger
5186 $result = $this->call_trigger($trigkey, $user);
5187 if ($result < 0) {
5188 $error++;
5189 }
5190 // End call triggers
5191 }
5192 } else {
5193 // The status was probably already good. We do nothing more, no triggers.
5194 }
5195
5196 if (!$error) {
5197 $this->db->commit();
5198
5199 if (empty($savElementId)) {
5200 // If the element we update is $this (so $elementId was provided as null)
5201 if ($fieldstatus == 'dispute_status') {
5202 $this->dispute_status = $status;
5203 } elseif ($fieldstatus == 'tosell') {
5204 $this->status = $status;
5205 } elseif ($fieldstatus == 'tobuy') {
5206 $this->status_buy = $status; // @phpstan-ignore-line
5207 } elseif ($fieldstatus == 'tobatch') {
5208 $this->status_batch = $status; // @phpstan-ignore-line
5209 } else {
5210 $this->status = $status;
5211 }
5212 }
5213
5214 return 1;
5215 } else {
5216 $this->db->rollback();
5217 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5218 return -1;
5219 }
5220 } else {
5221 $this->error = $this->db->lasterror();
5222 $this->db->rollback();
5223 return -1;
5224 }
5225 }
5226
5227
5235 public function getCanvas($id = 0, $ref = '')
5236 {
5237 if (empty($id) && empty($ref)) {
5238 return 0;
5239 }
5240 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5241 return 0; // To increase speed. Not enabled by default.
5242 }
5243
5244 // Clean parameters
5245 $ref = trim($ref);
5246
5247 $sql = "SELECT rowid, canvas";
5248 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5249 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5250 if (!empty($id)) {
5251 $sql .= " AND rowid = ".((int) $id);
5252 }
5253 if (!empty($ref)) {
5254 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5255 }
5256
5257 $resql = $this->db->query($sql);
5258 if ($resql) {
5259 $obj = $this->db->fetch_object($resql);
5260 if ($obj) {
5261 $this->canvas = $obj->canvas;
5262 return 1;
5263 } else {
5264 return 0;
5265 }
5266 } else {
5267 dol_print_error($this->db);
5268 return -1;
5269 }
5270 }
5271
5272
5279 public function getSpecialCode($lineid)
5280 {
5281 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
5282 $sql .= " WHERE rowid = ".((int) $lineid);
5283 $resql = $this->db->query($sql);
5284 if ($resql) {
5285 $row = $this->db->fetch_row($resql);
5286 return (!empty($row[0]) ? $row[0] : 0);
5287 }
5288
5289 return 0;
5290 }
5291
5300 public function isObjectUsed($id = 0, $entity = 0)
5301 {
5302 global $langs;
5303
5304 if (empty($id)) {
5305 $id = $this->id;
5306 }
5307
5308 // Check parameters
5309 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5310 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5311 return -1;
5312 }
5313
5314 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5315 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5316 $tmparray = array_keys($this->childtables);
5317 if (is_numeric($tmparray[0])) {
5318 $arraytoscan = array_flip($this->childtables);
5319 }
5320
5321 // Test on all child tables to scan if child exists
5322 $haschild = 0;
5323 foreach ($arraytoscan as $table => $element) {
5324 //print $id.'-'.$table.'-'.$elementname.'<br>';
5325
5326 // Check if module is enabled (to avoid error if tables of module not created)
5327 if (isset($element['enabled']) && !empty($element['enabled'])) {
5328 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5329 if (empty($enabled)) {
5330 continue;
5331 }
5332 }
5333
5334 // Check if element can be deleted
5335 $sql = "SELECT COUNT(*) as nb";
5336 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($table)." as c";
5337 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5338 $sql .= ", ".$this->db->prefix().$this->db->sanitize($element['parent'])." as p";
5339 }
5340 if (!empty($element['fk_element'])) {
5341 $sql .= " WHERE c.".$this->db->sanitize($element['fk_element'])." = ".((int) $id);
5342 } else {
5343 $sql .= " WHERE c.".$this->db->sanitize($this->fk_element)." = ".((int) $id);
5344 }
5345 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5346 $sql .= " AND c.".$this->db->sanitize($element['parentkey'])." = p.rowid";
5347 }
5348 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5349 $sql .= " AND c.".$this->db->sanitize($element['parenttypefield'])." = '".$this->db->escape($element['parenttypevalue'])."'";
5350 }
5351 if (!empty($entity)) {
5352 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5353 $sql .= " AND p.entity = ".((int) $entity);
5354 } else {
5355 $sql .= " AND c.entity = ".((int) $entity);
5356 }
5357 }
5358 //var_dump($table, $element, $sql);
5359
5360 $resql = $this->db->query($sql);
5361 if ($resql) {
5362 $obj = $this->db->fetch_object($resql);
5363 if ($obj->nb > 0) {
5364 $langs->load("errors");
5365 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5366 $haschild += (int) $obj->nb;
5367 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5368 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5369 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5370 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5371 } else { // new usage: $element['name']=Translation key
5372 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5373 }
5374 break; // We found at least one, we stop here
5375 }
5376 } else {
5377 $this->errors[] = $this->db->lasterror();
5378 return -1;
5379 }
5380 }
5381
5382 if ($haschild > 0) {
5383 $this->errors[] = "ErrorRecordHasChildren";
5384 return $haschild;
5385 } else {
5386 return 0;
5387 }
5388 }
5389
5396 public function hasProductsOrServices($predefined = -1)
5397 {
5398 $nb = 0;
5399
5400 foreach ($this->lines as $key => $val) {
5401 $qualified = 0;
5402 if ($predefined == -1) {
5403 $qualified = 1;
5404 }
5405 if ($predefined == 1 && $val->fk_product > 0) {
5406 $qualified = 1;
5407 }
5408 if ($predefined == 0 && $val->fk_product <= 0) {
5409 $qualified = 1;
5410 }
5411 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5412 $qualified = 1;
5413 }
5414 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5415 $qualified = 1;
5416 }
5417 if ($qualified) {
5418 $nb++;
5419 }
5420 }
5421 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5422 return $nb;
5423 }
5424
5430 public function getTotalDiscount()
5431 {
5432 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5433 $total_discount = 0.00;
5434
5435 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5436 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5437 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
5438
5439 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5440 $resql = $this->db->query($sql);
5441 if ($resql) {
5442 $num = $this->db->num_rows($resql);
5443 $i = 0;
5444 while ($i < $num) {
5445 $obj = $this->db->fetch_object($resql);
5446
5447 $pu_ht = $obj->pu_ht;
5448 $qty = $obj->qty;
5449 $total_ht = $obj->total_ht;
5450
5451 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5452 $total_discount += $total_discount_line;
5453
5454 $i++;
5455 }
5456 }
5457
5458 //print $total_discount; exit;
5459 return (float) price2num($total_discount);
5460 }
5461
5462 return null;
5463 }
5464
5465
5472 public function getTotalWeightVolume()
5473 {
5474 $totalWeight = 0;
5475 $totalVolume = 0;
5476 // defined for shipment only
5477 $totalOrdered = 0;
5478 // defined for shipment only
5479 $totalToShip = 0;
5480
5481 if (empty($this->lines)) {
5482 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5483 }
5484
5485 foreach ($this->lines as $line) {
5486 if (isset($line->qty_asked)) {
5487 $totalOrdered += $line->qty_asked; // defined for shipment only
5488 }
5489 if (isset($line->qty_shipped)) {
5490 $totalToShip += $line->qty_shipped; // defined for shipment only
5491 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5492 if (empty($totalToShip)) {
5493 $totalToShip = 0;
5494 }
5495 $totalToShip += $line->qty; // defined for reception only
5496 }
5497
5498 // Define qty, weight, volume, weight_units, volume_units
5499 if ($this->element == 'shipping') {
5500 // for shipments
5501 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5502 } else {
5503 $qty = $line->qty ? $line->qty : 0;
5504 }
5505
5506 $weight = !empty($line->weight) ? $line->weight : 0;
5507 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5508 $volume = !empty($line->volume) ? $line->volume : 0;
5509 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5510
5511 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5512 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5513 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5514 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5515
5516 $weightUnit = 0;
5517 $volumeUnit = 0;
5518 if (!empty($weight_units)) {
5519 $weightUnit = $weight_units;
5520 }
5521 if (!empty($volume_units)) {
5522 $volumeUnit = $volume_units;
5523 }
5524
5525 if (empty($totalWeight)) {
5526 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5527 }
5528 if (empty($totalVolume)) {
5529 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5530 }
5531
5532 //var_dump($line->volume_units);
5533 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5534 $trueWeightUnit = pow(10, $weightUnit);
5535 $totalWeight += $weight * $qty * $trueWeightUnit;
5536 } else {
5537 if ($weight_units == 99) {
5538 // conversion 1 Pound = 0.45359237 KG
5539 $trueWeightUnit = 0.45359237;
5540 $totalWeight += $weight * $qty * $trueWeightUnit;
5541 } elseif ($weight_units == 98) {
5542 // conversion 1 Ounce = 0.0283495 KG
5543 $trueWeightUnit = 0.0283495;
5544 $totalWeight += $weight * $qty * $trueWeightUnit;
5545 } else {
5546 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5547 }
5548 }
5549 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5550 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5551 $trueVolumeUnit = pow(10, $volumeUnit);
5552 //print $line->volume;
5553 $totalVolume += $volume * $qty * $trueVolumeUnit;
5554 } else {
5555 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5556 }
5557 }
5558
5559 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5560 }
5561
5562
5568 public function setExtraParameters()
5569 {
5570 $this->db->begin();
5571
5572 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5573 $extraparams = dol_trunc($extraparams, 250);
5574
5575 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5576 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5577 $sql .= " WHERE rowid = ".((int) $this->id);
5578
5579 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5580 $resql = $this->db->query($sql);
5581 if (!$resql) {
5582 $this->error = $this->db->lasterror();
5583 $this->db->rollback();
5584 return -1;
5585 } else {
5586 $this->db->commit();
5587 return 1;
5588 }
5589 }
5590
5591
5592 // --------------------
5593 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5594 // --------------------
5595
5596 /* This is to show add lines */
5597
5607 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5608 {
5609 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5610
5611 // Line extrafield
5612 if (!is_object($extrafields)) {
5613 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5614 $extrafields = new ExtraFields($this->db);
5615 }
5616 $extrafields->fetch_name_optionals_label($this->table_element_line);
5617
5618 // Output template part (modules that overwrite templates must declare this into descriptor)
5619 // Use global variables + $dateSelector + $seller and $buyer
5620 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5621 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5622 foreach ($dirtpls as $module => $reldir) {
5623 if (!empty($module)) {
5624 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5625 } else {
5626 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5627 }
5628
5629 if (empty($conf->file->strict_mode)) {
5630 $res = @include $tpl;
5631 } else {
5632 $res = include $tpl; // for debug
5633 }
5634 if ($res) {
5635 break;
5636 }
5637 }
5638 }
5639
5640
5641
5642 /* This is to show array of line of details */
5643
5644
5659 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5660 {
5661 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5662 // TODO We should not use global var for this
5663 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5664
5665 // Define $usemargins (used by objectline_xxx.tpl.php files)
5666 $usemargins = 0;
5667 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5668 $usemargins = 1;
5669 }
5670
5671 $num = count($this->lines);
5672
5673 // Line extrafield
5674 if (!is_object($extrafields)) {
5675 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5676 $extrafields = new ExtraFields($this->db);
5677 }
5678 $extrafields->fetch_name_optionals_label($this->table_element_line);
5679
5680 if (method_exists($this, 'loadExpeditions')) {
5681 // 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.
5682 $this->loadExpeditions();
5683 }
5684
5685 $parameters = array();
5686 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5687 if (empty($reshook)) {
5688 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5689 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5690 if (empty($reshook)) {
5691 // Output template part (modules that overwrite templates must declare this into descriptor)
5692 // Use global variables + $dateSelector + $seller and $buyer
5693 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5694 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5695 foreach ($dirtpls as $module => $reldir) {
5696 $res = 0;
5697 if (!empty($module)) {
5698 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5699 } else {
5700 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5701 }
5702 if (file_exists($tpl)) {
5703 if (empty($conf->file->strict_mode)) {
5704 $res = @include $tpl;
5705 } else {
5706 $res = include $tpl; // for debug
5707 }
5708 }
5709 if ($res) {
5710 break;
5711 }
5712 }
5713 }
5714
5715 $i = 0;
5716
5717 print "<!-- begin printObjectLines() -->\n";
5718 foreach ($this->lines as $line) {
5719 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5720 //$line->fetch_optionals();
5721
5722 if (is_object($hookmanager)) {
5723 if (empty($line->fk_parent_line)) {
5724 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5725 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5726 } else {
5727 $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);
5728 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5729 }
5730 }
5731 if (empty($reshook)) {
5732 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5733 }
5734
5735 $i++;
5736 }
5737 print "<!-- end printObjectLines() -->\n";
5738 }
5739 }
5740
5758 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5759 {
5760 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5761 global $form;
5762 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5763
5764 // var used into tpl
5765 $text = '';
5766 $description = '';
5767
5768 // Line in view mode
5769 if ($action != 'editline' || $selected != $line->id) {
5770 // Product
5771 if (!empty($line->fk_product) && $line->fk_product > 0) {
5772 $product_static = new Product($this->db);
5773 $product_static->fetch($line->fk_product);
5774
5775 $product_static->ref = (string) $line->ref; //can change ref in hook
5776 // label is checked with empty @phan-suppress-next-line PhanUndeclaredProperty
5777 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5778
5779 $text = $product_static->getNomUrl(1);
5780
5781 // Define output language and label
5782 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5783 // @phan-suppress-next-line PhanUndeclaredProperty
5784 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5785 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5786 return;
5787 }
5788
5789 $prod = new Product($this->db);
5790 $prod->fetch($line->fk_product);
5791
5792 $outputlangs = $langs;
5793 $newlang = '';
5794 if (GETPOST('lang_id', 'aZ09')) {
5795 $newlang = GETPOST('lang_id', 'aZ09');
5796 }
5797 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5798 $newlang = $this->thirdparty->default_lang; // To use language of customer
5799 }
5800 if (!empty($newlang)) {
5801 $outputlangs = new Translate("", $conf);
5802 $outputlangs->setDefaultLang($newlang);
5803 }
5804
5805 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5806 } else {
5807 $label = $line->product_label;
5808 }
5809
5810 // label is checked with empty @phan-suppress-next-line PhanUndeclaredProperty
5811 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5812 $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.
5813 }
5814
5815 // Recalculate unit price with tax if not defined
5816 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)
5817 // So we calculate an estimated value just to show something on screen.
5818 // Not: the unit price is always for 100% of line, it is not a prorata of situation invoice (when total is)
5819 if ($line->remise_percent != 100) {
5820 // Suppose situation_percent is defined @phan-suppress-next-line PhanUndeclaredProperty
5821 $line->subprice_ttc = (float) price2num($line->total_ttc * ($line->situation_percent ? 100 / $line->situation_percent : 1) / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5822 } else {
5823 // Other method is less accurate
5824 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5825 }
5826 }
5827 $line->pu_ttc = $line->subprice_ttc; // deprecated
5828
5829 // Output template part (modules that overwrite templates must declare this into descriptor)
5830 // Use global variables + $dateSelector + $seller and $buyer
5831 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5832
5833 $qty_shipped = 0;
5834 if (isset($this->expeditions[$line->id])) { // @phan-suppress-current-line PhanUndeclaredProperty
5835 // Suppose existence of expeditions is tested @phan-suppress-next-line PhanUndeclaredProperty
5836 $qty_shipped = $this->expeditions[$line->id];
5837 }
5838 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5839
5840 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5841 foreach ($dirtpls as $module => $reldir) {
5842 $res = 0;
5843 if (!empty($module)) {
5844 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5845 } else {
5846 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5847 }
5848 //var_dump($tpl);
5849 if (file_exists($tpl)) {
5850 if (empty($conf->file->strict_mode)) {
5851 $res = @include $tpl;
5852 } else {
5853 $res = include $tpl; // for debug
5854 }
5855 }
5856 if ($res) {
5857 break;
5858 }
5859 }
5860 }
5861
5862 // Line in update mode
5863 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5864 // label is tested @phan-suppress-next-line PhanUndeclaredProperty
5865 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5866
5867 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5868 $line->pu_ttc = $line->subprice_ttc; // deprecated
5869
5870 // Output template part (modules that overwrite templates must declare this into descriptor)
5871 // Use global variables + $dateSelector + $seller and $buyer
5872 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5873 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5874 foreach ($dirtpls as $module => $reldir) {
5875 if (!empty($module)) {
5876 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5877 } else {
5878 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5879 }
5880
5881 if (empty($conf->file->strict_mode)) {
5882 $res = @include $tpl;
5883 } else {
5884 $res = include $tpl; // for debug
5885 }
5886 if ($res) {
5887 break;
5888 }
5889 }
5890 }
5891 }
5892
5893
5894 /* This is to show array of line of details of source object */
5895
5896
5907 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5908 {
5909 global $langs, $hookmanager, $form, $action;
5910
5911 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5912 print '<tr class="liste_titre">';
5913 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5914 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5915 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5916 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5917 if (isModEnabled("multicurrency")) {
5918 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5919 }
5920 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5921 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5922 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5923 }
5924 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5925 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5926 print '<td class="center">';
5927 print $form->showCheckAddButtons('checkforselect', 1);
5928 print '</td>';
5929 print '</tr>';
5930 $i = 0;
5931
5932 if (!empty($this->lines)) {
5933 foreach ($this->lines as $line) {
5934 $reshook = 0;
5935 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5936 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5937 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5938 if (!empty($line->fk_parent_line)) {
5939 $parameters['fk_parent_line'] = $line->fk_parent_line;
5940 }
5941 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5942 }
5943 if (empty($reshook)) {
5944 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5945 }
5946
5947 $i++;
5948 }
5949 }
5950 }
5951
5965 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5966 {
5967 global $langs, $conf;
5968
5969 //var_dump($line);
5970 if (!empty($line->date_start)) {
5971 // @phan-suppress-next-line PhanUndeclaredProperty
5972 $date_start = $line->date_start;
5973 } else {
5974 $date_start = $line->date_debut_prevue;
5975 if ($line->date_debut_reel) {
5976 $date_start = $line->date_debut_reel;
5977 }
5978 }
5979 if (!empty($line->date_end)) {
5980 // @phan-suppress-next-line PhanUndeclaredProperty
5981 $date_end = $line->date_end;
5982 } else {
5983 $date_end = $line->date_fin_prevue;
5984 if ($line->date_fin_reel) {
5985 $date_end = $line->date_fin_reel;
5986 }
5987 }
5988
5989 // Set thevalue into ->tpl[] array.
5990 $this->tpl['id'] = $line->id;
5991
5992 $this->tpl['label'] = '';
5993 if (!empty($line->fk_parent_line)) {
5994 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5995 }
5996
5997 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5998 $discount = new DiscountAbsolute($this->db);
5999 if (property_exists($this, 'socid')) {
6000 // Tested if socid exists @phan-suppress-next-line PhanUndeclaredProperty
6001 $discount->fk_soc = $this->socid;
6002 // Tested if socid exists @phan-suppress-next-line PhanUndeclaredProperty
6003 $discount->socid = $this->socid;
6004 }
6005 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
6006 } elseif (!empty($line->fk_product)) {
6007 $productstatic = new Product($this->db);
6008 $productstatic->id = $line->fk_product;
6009 $productstatic->ref = (string) $line->ref;
6010 $productstatic->type = $line->fk_product_type;
6011 if (empty($productstatic->ref)) {
6012 $line->fetch_product();
6013 $productstatic = $line->product;
6014 }
6015
6016 $this->tpl['label'] .= $productstatic->getNomUrl(1);
6017 // Existence of $line->label is tested @phan-suppress-next-line PhanUndeclaredProperty
6018 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
6019 // Dates
6020 if ($line->product_type == 1 && ($date_start || $date_end)) {
6021 $this->tpl['label'] .= get_date_range($date_start, $date_end);
6022 }
6023 } else {
6024 $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"')));
6025 if (!empty($line->desc)) {
6026 $this->tpl['label'] .= $line->desc;
6027 } else {
6028 // Supposes existence of $line->label @phan-suppress-next-line PhanUndeclaredProperty
6029 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
6030 }
6031
6032 // Dates
6033 if ($line->product_type == 1 && ($date_start || $date_end)) {
6034 $this->tpl['label'] .= get_date_range($date_start, $date_end);
6035 }
6036 }
6037
6038 if (!empty($line->desc)) {
6039 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
6040 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
6041 $discount = new DiscountAbsolute($this->db);
6042 $discount->fetch($line->fk_remise_except);
6043 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
6044 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
6045 $discount = new DiscountAbsolute($this->db);
6046 $discount->fetch($line->fk_remise_except);
6047 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
6048 } elseif ($line->desc == '(EXCESS RECEIVED)') {
6049 $discount = new DiscountAbsolute($this->db);
6050 $discount->fetch($line->fk_remise_except);
6051 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
6052 } elseif ($line->desc == '(EXCESS PAID)') {
6053 $discount = new DiscountAbsolute($this->db);
6054 $discount->fetch($line->fk_remise_except);
6055 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
6056 } else {
6057 $this->tpl['description'] = dol_trunc($line->desc, 60);
6058 }
6059 } else {
6060 $this->tpl['description'] = '&nbsp;';
6061 }
6062
6063 // VAT Rate
6064 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
6065 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
6066 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
6067 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
6068 }
6069
6070 $this->tpl['price'] = price($line->subprice);
6071 $this->tpl['total_ht'] = price($line->total_ht);
6072 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
6073 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
6074 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
6075 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
6076 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
6077 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
6078 }
6079 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
6080
6081 // Is the line strike or not
6082 $this->tpl['strike'] = 0;
6083 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
6084 $this->tpl['strike'] = 1;
6085 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
6086 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
6087 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
6088 // Without the guard, printing origin lines from a PO into a vendor
6089 // invoice fatals (#37663). Other tpl files already follow this pattern.
6090 $this->tpl['strike'] = 1;
6091 }
6092
6093 // Output template part (modules that overwrite templates must declare this into descriptor)
6094 // Use global variables + $dateSelector + $seller and $buyer
6095 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
6096 foreach ($dirtpls as $module => $reldir) {
6097 if (!empty($module)) {
6098 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
6099 } else {
6100 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
6101 }
6102
6103 if (empty($conf->file->strict_mode)) {
6104 $res = @include $tpl;
6105 } else {
6106 $res = include $tpl; // for debug
6107 }
6108 if ($res) {
6109 break;
6110 }
6111 }
6112 }
6113
6114
6115 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6127 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
6128 {
6129 // phpcs:enable
6130 global $user;
6131 $this->db->begin();
6132
6133 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
6134 $sql .= "resource_id";
6135 $sql .= ", resource_type";
6136 $sql .= ", element_id";
6137 $sql .= ", element_type";
6138 $sql .= ", busy";
6139 $sql .= ", mandatory";
6140 $sql .= ") VALUES (";
6141 $sql .= ((int) $resource_id);
6142 $sql .= ", '".$this->db->escape($resource_type)."'";
6143 $sql .= ", '".$this->db->escape((string) $this->id)."'";
6144 $sql .= ", '".$this->db->escape($this->element)."'";
6145 $sql .= ", '".$this->db->escape((string) $busy)."'";
6146 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
6147 $sql .= ")";
6148
6149 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
6150 if ($this->db->query($sql)) {
6151 if (!$notrigger) {
6152 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
6153 if ($result < 0) {
6154 $this->db->rollback();
6155 return -1;
6156 }
6157 }
6158 $this->db->commit();
6159 return 1;
6160 } else {
6161 $this->error = $this->db->lasterror();
6162 $this->db->rollback();
6163 return 0;
6164 }
6165 }
6166
6167 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6176 public function delete_resource($rowid, $element = '', $notrigger = 0)
6177 {
6178 // phpcs:enable
6179 global $user;
6180
6181 $this->db->begin();
6182
6183 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6184 $sql .= " WHERE rowid = ".((int) $rowid);
6185
6186 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6187
6188 $resql = $this->db->query($sql);
6189 if (!$resql) {
6190 $this->error = $this->db->lasterror();
6191 $this->db->rollback();
6192 return -1;
6193 } else {
6194 if (!$notrigger) {
6195 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6196 if ($result < 0) {
6197 $this->db->rollback();
6198 return -1;
6199 }
6200 }
6201 $this->db->commit();
6202 return 1;
6203 }
6204 }
6205
6206
6212 public function __clone()
6213 {
6214 // Force a copy of this->lines, otherwise it will point to same object.
6215 if (isset($this->lines) && is_array($this->lines)) {
6216 $nboflines = count($this->lines);
6217 for ($i = 0; $i < $nboflines; $i++) {
6218 if (is_object($this->lines[$i])) {
6219 $this->lines[$i] = clone $this->lines[$i];
6220 }
6221 }
6222 }
6223 }
6224
6238 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6239 {
6240 global $conf, $langs, $hookmanager, $action;
6241
6242 $srctemplatepath = '';
6243
6244 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6245 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6246
6247 if (!empty($reshook)) {
6248 return $reshook;
6249 }
6250
6251 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6252
6253 if (empty($modele)) {
6254 $this->error = 'BadValueForParameterModele';
6255 return -1;
6256 }
6257
6258 // Increase limit for PDF build
6259 $err = error_reporting();
6260 error_reporting(0);
6261 @set_time_limit(120);
6262 error_reporting($err);
6263
6264 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6265 $tmp = explode(':', $modele, 2);
6266 $saved_model = $modele;
6267 if (!empty($tmp[1])) {
6268 $modele = $tmp[0];
6269 $srctemplatepath = $tmp[1];
6270
6271 if (!preg_match('/^'.preg_quote(DOL_DATA_ROOT, '/').'\/(ecm|doctemplates)/', $srctemplatepath)) {
6272 $this->error = 'BadDirForTemplateFile';
6273 return -1;
6274 }
6275 }
6276
6277 // Search template files
6278 $file = '';
6279 $classname = '';
6280 $filefound = '';
6281 $dirmodels = array('/');
6282 if (is_array($conf->modules_parts['models'])) {
6283 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6284 }
6285 foreach ($dirmodels as $reldir) {
6286 foreach (array('doc', 'pdf') as $prefix) {
6287 if (in_array(get_class($this), array('Adherent'))) {
6288 // Member module use prefix_modele.class.php
6289 $file = $prefix."_".$modele.".class.php";
6290 } else {
6291 // Other module use prefix_modele.modules.php
6292 $file = $prefix."_".$modele.".modules.php";
6293 }
6294
6295 $file = dol_sanitizeFileName($file);
6296
6297 // We check if the file exists
6298 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6299 if (file_exists($file)) {
6300 $filefound = $file;
6301 $classname = $prefix.'_'.$modele;
6302 break;
6303 }
6304 }
6305 if ($filefound) {
6306 break;
6307 }
6308 }
6309
6310 if ($filefound === '' || $classname === '') {
6311 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6312 $this->errors[] = $this->error;
6313 dol_syslog($this->error, LOG_ERR);
6314 return -1;
6315 }
6316
6317 // Sanitize $filefound
6318 $filefound = dol_sanitizePathName($filefound);
6319
6320 // If generator was found
6321 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6322
6323 require_once $filefound;
6324
6325 $obj = new $classname($this->db);
6326
6327 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6328 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6329 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6330 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6331 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6332 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6333 // ModelePDFStock|ModelePDFStockTransfer|
6334 // ModeleDon|ModelePDFTask|
6335 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6336 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6337 '@phan-var-force ModelePDFMember $obj';
6338 // '@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';
6339
6340 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6341 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6342 $varfortemplatedir = $obj->scandir;
6343 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6344 $dirtoscan = getDolGlobalString($varfortemplatedir);
6345
6346 $listoffiles = array();
6347
6348 // Now we add first model found in directories scanned
6349 $listofdir = explode(',', $dirtoscan);
6350 foreach ($listofdir as $key => $tmpdir) {
6351 $tmpdir = trim($tmpdir);
6352 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6353 if (!$tmpdir) {
6354 unset($listofdir[$key]);
6355 continue;
6356 }
6357 if (is_dir($tmpdir)) {
6358 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6359 if (count($tmpfiles)) {
6360 $listoffiles = array_merge($listoffiles, $tmpfiles);
6361 }
6362 }
6363 }
6364
6365 if (count($listoffiles)) {
6366 foreach ($listoffiles as $record) {
6367 $srctemplatepath = $record['fullname'];
6368 break;
6369 }
6370 }
6371 }
6372
6373 if (empty($srctemplatepath)) {
6374 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6375 return -1;
6376 }
6377 }
6378
6379 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6380 if (!dol_is_file($srctemplatepath)) {
6381 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6382 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6383 return -1;
6384 }
6385 }
6386
6387 // We save charset_output to restore it because write_file can change it if needed for
6388 // output format that does not support UTF8.
6389 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6390
6391 // update model_pdf in object
6392 $this->model_pdf = $saved_model;
6393
6394 try {
6395 if ($obj instanceof ModelePDFMember) {
6396 if ($this instanceof Adherent) {
6397 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6398 } else {
6399 $resultwritefile = -1;
6400 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6401 }
6402 } elseif ($obj instanceof ModeleDon) {
6403 // Only 3 arguments
6404 if ($this instanceof Don) {
6405 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6406 } else {
6407 $resultwritefile = -1;
6408 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6409 }
6410 } else {
6411 // TODO: Try to set type above again
6412 '@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';
6413 $this->context['moreparams'] = $moreparams;
6414 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref); // @phan-suppress-current-line PhanTypeMismatchArgument
6415 }
6416 } catch (\Throwable $e) {
6417 $resultwritefile = -1;
6418 $this->error = $e->getMessage();
6419 dol_syslog("Exception in write_file() for ".get_class($this).": ".$e->getMessage(), LOG_ERR);
6420 }
6421 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6422
6423
6424 if ($resultwritefile > 0) {
6425 $outputlangs->charset_output = $sav_charset_output;
6426
6427 // We delete old preview
6428 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6429 dol_delete_preview($this);
6430
6431 // Index file in database
6432 if (!empty($obj->result['fullpath'])) {
6433 $destfull = $obj->result['fullpath'];
6434
6435 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6436 $update_main_doc_field = 0;
6437 if (!empty($obj->update_main_doc_field)) {
6438 $update_main_doc_field = 1;
6439 }
6440
6441 // Check that the file exists, before indexing it.
6442 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6443 if (dol_is_file($destfull)) {
6444 $this->indexFile($destfull, $update_main_doc_field);
6445 }
6446 } else {
6447 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);
6448 }
6449
6450 // Success in building document. We build meta file.
6451 dol_meta_create($this);
6452
6453 $this->warnings = $obj->warnings;
6454
6455 return 1;
6456 } else {
6457 $outputlangs->charset_output = $sav_charset_output;
6458 if (empty($this->error)) {
6459 $this->error = $obj->error;
6460 }
6461 if (empty($this->errors)) {
6462 $this->errors = $obj->errors;
6463 }
6464 $this->warnings = $obj->warnings;
6465 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$this->error, LOG_ERR);
6466 return -1;
6467 }
6468 }
6469
6479 public function indexFile($destfull, $update_main_doc_field)
6480 {
6481 global $conf, $user;
6482
6483 $upload_dir = dirname($destfull);
6484 $destfile = basename($destfull);
6485 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6486
6487 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6488 $filename = basename($destfile);
6489 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6490 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6491
6492 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6493 $ecmfile = new EcmFiles($this->db);
6494 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6495
6496 // Set the public "share" key
6497 $setsharekey = false;
6498 if (
6499 !empty($this->TRIGGER_PREFIX)
6500 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6501 ) {
6502 $setsharekey = true;
6503 }
6504 // TODO Remove case covered by trigger prefix
6505 if ($this->element == 'propal' || $this->element == 'proposal') {
6506 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6507 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6508 }
6509 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6510 $setsharekey = true;
6511 }
6512 }
6513 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6514 $setsharekey = true;
6515 }
6516 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6517 $setsharekey = true;
6518 }
6519 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6520 $setsharekey = true;
6521 }
6522 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6523 $setsharekey = true;
6524 }
6525 if ($this->element == 'order_supplier' && getDolGlobalInt("SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6526 $setsharekey = true;
6527 }
6528 if ($this->element == 'invoice_supplier' && getDolGlobalInt("SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6529 $setsharekey = true;
6530 }
6531 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6532 $setsharekey = true;
6533 }
6534
6535 if ($setsharekey) {
6536 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6537 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6538 $ecmfile->share = getRandomPassword(true);
6539 }
6540 }
6541
6542 if ($result > 0) {
6543 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6544 $ecmfile->fullpath_orig = '';
6545 $ecmfile->gen_or_uploaded = 'generated';
6546 $ecmfile->description = ''; // indexed content
6547 $ecmfile->keywords = ''; // keyword content
6548 $ecmfile->src_object_type = $this->table_element;
6549 $ecmfile->src_object_id = $this->id;
6550 $result = $ecmfile->update($user);
6551 if ($result < 0) {
6552 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6553 return -1;
6554 }
6555 } else {
6556 $ecmfile->entity = $conf->entity;
6557 $ecmfile->filepath = $rel_dir;
6558 $ecmfile->filename = $filename;
6559 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6560 $ecmfile->fullpath_orig = '';
6561 $ecmfile->gen_or_uploaded = 'generated';
6562 $ecmfile->description = ''; // indexed content
6563 $ecmfile->keywords = ''; // keyword content
6564 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6565 $ecmfile->src_object_id = $this->id;
6566
6567 $result = $ecmfile->create($user);
6568 if ($result < 0) {
6569 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6570 return -1;
6571 }
6572 }
6573
6574 /*$this->result['fullname']=$destfull;
6575 $this->result['filepath']=$ecmfile->filepath;
6576 $this->result['filename']=$ecmfile->filename;*/
6577 //var_dump($obj->update_main_doc_field);exit;
6578
6579 if ($update_main_doc_field && !empty($this->table_element)) {
6580 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6581 $sql .= " WHERE rowid = ".((int) $this->id);
6582
6583 $resql = $this->db->query($sql);
6584 if (!$resql) {
6585 dol_print_error($this->db);
6586 return -1;
6587 } else {
6588 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6589 }
6590 }
6591 }
6592
6593 return 1;
6594 }
6595
6604 public function addThumbs($file, $quality = 50)
6605 {
6606 $file_osencoded = dol_osencode($file);
6607
6608 if (file_exists($file_osencoded)) {
6609 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6610
6611 $tmparraysize = getDefaultImageSizes();
6612 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6613 $maxheightsmall = $tmparraysize['maxheightsmall'];
6614 $maxwidthmini = $tmparraysize['maxwidthmini'];
6615 $maxheightmini = $tmparraysize['maxheightmini'];
6616 //$quality = $tmparraysize['quality'];
6617
6618 // Create small thumbs for company (Ratio is near 16/9)
6619 // Used on logon for example
6620 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6621
6622 // Create mini thumbs for company (Ratio is near 16/9)
6623 // Used on menu or for setup page for example
6624 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6625 }
6626 }
6627
6635 public function delThumbs($file)
6636 {
6637 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6638 dol_delete_file($imgThumbName);
6639 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6640 dol_delete_file($imgThumbName);
6641 }
6642
6643
6644 /* Functions common to commonobject and commonobjectline */
6645
6646 /* For default values */
6647
6661 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6662 {
6663 /* TODO Remove this. Must use now something like:
6664 $note_private = GETPOST('note_private', 'restricthtml');
6665 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6666 $note_private = $objectsrc->note_private;
6667 }
6668 */
6669
6670 // If param here has been posted, we use this value first.
6671 if (GETPOSTISSET($fieldname)) {
6672 return GETPOST($fieldname, $type, 3);
6673 }
6674
6675 if (isset($alternatevalue)) {
6676 return $alternatevalue;
6677 }
6678
6679 $newelement = $this->element;
6680 if ($newelement == 'facture') {
6681 $newelement = 'invoice';
6682 }
6683 if ($newelement == 'commande') {
6684 $newelement = 'order';
6685 }
6686 if (empty($newelement)) {
6687 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6688 return '';
6689 }
6690
6691 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6692 //var_dump($keyforfieldname);
6693 if (getDolGlobalString($keyforfieldname)) {
6694 return getDolGlobalString($keyforfieldname);
6695 }
6696
6697 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6698 // store content into $conf->cache['overwrite_default']
6699
6700 return '';
6701 }
6702
6703
6704 /* Functions for data in other language */
6705
6706
6716 {
6717 // To avoid SQL errors. Probably not the better solution though
6718 if (!$this->element) {
6719 return 0;
6720 }
6721 if (!($this->id > 0)) {
6722 return 0;
6723 }
6724 if (is_array($this->array_languages)) {
6725 return 1;
6726 }
6727
6728 $this->array_languages = array();
6729
6730 $element = $this->element;
6731 if ($element == 'categorie') {
6732 $element = 'categories'; // For compatibility
6733 }
6734
6735 // Request to get translation values for object
6736 $sql = "SELECT rowid, property, lang , value";
6737 $sql .= " FROM ".$this->db->prefix()."object_lang";
6738 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6739 $sql .= " AND fk_object = ".((int) $this->id);
6740
6741 $resql = $this->db->query($sql);
6742 if ($resql) {
6743 $numrows = $this->db->num_rows($resql);
6744 if ($numrows) {
6745 $i = 0;
6746 while ($i < $numrows) {
6747 $obj = $this->db->fetch_object($resql);
6748 $key = $obj->property;
6749 $value = $obj->value;
6750 $codelang = $obj->lang;
6751 $type = $this->fields[$key]['type'];
6752
6753 // we can add this attribute to object
6754 if (preg_match('/date/', $type)) {
6755 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6756 } else {
6757 $this->array_languages[$key][$codelang] = $value;
6758 }
6759
6760 $i++;
6761 }
6762 }
6763
6764 $this->db->free($resql);
6765
6766 if ($numrows) {
6767 return $numrows;
6768 } else {
6769 return 0;
6770 }
6771 } else {
6772 dol_print_error($this->db);
6773 return -1;
6774 }
6775 }
6776
6784 public function setValuesForExtraLanguages($onlykey = '')
6785 {
6786 // Get extra fields
6787 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6788 $tmparray = explode('-', $postfieldkey);
6789 if ($tmparray[0] != 'field') {
6790 continue;
6791 }
6792
6793 $element = $tmparray[1];
6794 $key = $tmparray[2];
6795 $codelang = $tmparray[3];
6796 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6797
6798 if (!empty($onlykey) && $key != $onlykey) {
6799 continue;
6800 }
6801 if ($element != $this->element) {
6802 continue;
6803 }
6804
6805 $key_type = $this->fields[$key]['type'];
6806
6807 $enabled = 1;
6808 if (isset($this->fields[$key]['enabled'])) {
6809 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6810 }
6811 /*$perms = 1;
6812 if (isset($this->fields[$key]['perms']))
6813 {
6814 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6815 }*/
6816 if (empty($enabled)) {
6817 continue;
6818 }
6819 //if (empty($perms)) continue;
6820
6821 if (in_array($key_type, array('date'))) {
6822 // Clean parameters
6823 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6824 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6825 } elseif (in_array($key_type, array('datetime'))) {
6826 // Clean parameters
6827 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6828 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6829 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6830 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6831 if (!empty($value_arr)) {
6832 $value_key = implode(',', $value_arr);
6833 } else {
6834 $value_key = '';
6835 }
6836 } elseif (in_array($key_type, array('price', 'double'))) {
6837 $value_arr = GETPOST($postfieldkey, 'alpha');
6838 $value_key = price2num($value_arr);
6839 } else {
6840 $value_key = GETPOST($postfieldkey);
6841 if (in_array($key_type, array('link')) && $value_key == '-1') {
6842 $value_key = '';
6843 }
6844 }
6845
6846 $this->array_languages[$key][$codelang] = $value_key;
6847
6848 /*if ($nofillrequired) {
6849 $langs->load('errors');
6850 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6851 return -1;
6852 }*/
6853 }
6854
6855 return 1;
6856 }
6857
6858
6859 /* Functions for extrafields */
6860
6867 public function fetchNoCompute($id)
6868 {
6869 global $conf;
6870
6871 $savDisableCompute = $conf->disable_compute;
6872 $conf->disable_compute = 1;
6873
6874 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6875
6876 $conf->disable_compute = $savDisableCompute;
6877
6878 return $ret;
6879 }
6880
6881 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6891 public function fetch_optionals($rowid = null, $optionsArray = null)
6892 {
6893 // phpcs:enable
6894 global $conf, $extrafields;
6895
6896 if (empty($rowid)) {
6897 $rowid = $this->id;
6898 }
6899 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6900 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6901 }
6902
6903 // To avoid SQL errors. Probably not the better solution though
6904 if (!$this->table_element) {
6905 return 0;
6906 }
6907 $this->array_options = array();
6908
6909 if (!is_array($optionsArray)) {
6910 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6911 if (!isset($extrafields) || !is_object($extrafields)) {
6912 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6913 $extrafields = new ExtraFields($this->db);
6914 }
6915
6916 // Load array of extrafields for elementype = $this->table_element
6917 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6918 $extrafields->fetch_name_optionals_label($this->table_element);
6919 }
6920 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6921 } else {
6922 global $extrafields;
6923 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6924 }
6925
6926 $table_element = $this->table_element;
6927 if ($table_element == 'categorie') {
6928 $table_element = 'categories'; // For compatibility
6929 }
6930
6931 // Request to get complementary values
6932 if (is_array($optionsArray) && count($optionsArray) > 0) {
6933 $sql = "SELECT rowid";
6934 foreach ($optionsArray as $name => $label) {
6935 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6936 $sql .= ", ".$this->db->sanitize($name);
6937 }
6938 // use geo sql function to read as text
6939 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6940 // TODO Add an abstraction method in the database driver
6941 $sql .= ", ST_AsWKT(".$this->db->sanitize($name).") as ".$this->db->sanitize($name);
6942 }
6943 }
6944 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6945 $sql .= " WHERE fk_object = ".((int) $rowid);
6946
6947 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6948 $resql = $this->db->query($sql);
6949 if ($resql) {
6950 $numrows = $this->db->num_rows($resql);
6951 if ($numrows) {
6952 $tab = $this->db->fetch_array($resql);
6953 foreach ($tab as $key => $value) {
6954 // 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)
6955 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6956 // we can add this attribute to object
6957 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6958 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6959 $this->array_options["options_".$key] = $this->db->jdate($value);
6960 } else {
6961 $this->array_options["options_".$key] = $value;
6962 }
6963
6964 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6965 }
6966 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6967 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6968 $this->array_options["options_".$key] = dolDecrypt($value);
6969 }
6970 }
6971 }
6972 } else {
6977 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6978 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6979 $this->array_options['options_' . $key] = null;
6980 }
6981 }
6982 }
6983
6984 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6985 // in the element's extrafields table)
6986 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6987 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6988 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6989 //var_dump($conf->disable_compute);
6990 if (empty($conf->disable_compute)) {
6991 // We set a global variable to $objectoffield so we can use it inside computed formula (must be before the assignment)
6992 global $objectoffield;
6993 $objectoffield = dol_clone($this, 2);
6994 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6995 }
6996 }
6997 }
6998 }
6999
7000 $this->db->free($resql);
7001
7002 if ($numrows) {
7003 return $numrows;
7004 } else {
7005 return 0;
7006 }
7007 } else {
7008 $this->errors[] = $this->db->lasterror();
7009 return -1;
7010 }
7011 }
7012 return 0;
7013 }
7014
7021 public function deleteExtraFields()
7022 {
7023 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7024 return 0;
7025 }
7026
7027 $this->db->begin();
7028
7029 $table_element = $this->table_element;
7030 if ($table_element == 'categorie') {
7031 $table_element = 'categories'; // For compatibility
7032 }
7033
7034 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
7035
7036 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7037
7038 $resql = $this->db->query($sql_del);
7039 if (!$resql) {
7040 $this->error = $this->db->lasterror();
7041 $this->db->rollback();
7042 return -1;
7043 } else {
7044 $this->db->commit();
7045 return 1;
7046 }
7047 }
7048
7059 public function insertExtraFields($trigger = '', $userused = null)
7060 {
7061 global $langs, $extrafields, $user;
7062
7063 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
7064 return 0;
7065 }
7066
7067 if (empty($userused)) {
7068 $userused = $user;
7069 }
7070
7071 $error = 0;
7072
7073 // Check parameters
7074 $langs->load('admin');
7075 if (!isset($extrafields) || !is_object($extrafields)) {
7076 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7077 $extrafields = new ExtraFields($this->db);
7078 }
7079 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
7080
7081 // Eliminate copied source object extra fields that do not exist in target object
7082 $new_array_options = array();
7083 foreach ($this->array_options as $key => $value) {
7084 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
7085 $new_array_options[$key] = $value;
7086 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
7087 $new_array_options['options_'.$key] = $value;
7088 }
7089 }
7090
7091 foreach ($new_array_options as $key => $value) {
7092 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7093 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7094 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
7095 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7096 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7097 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7098 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7099 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
7100 /*
7101 $attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7102 $enabled = 0;
7103 if (!empty($extrafields->attributes[$this->table_element]['enabled'][$key])) {
7104 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
7105 }
7106 */
7107
7108 // If we clone, we have to clean unique extrafields to prevent duplicates.
7109 // If we clone, we have to clean extrafields having "empty on clone" option on.
7110 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
7111 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
7112 $new_array_options[$key] = null;
7113 continue;
7114 }
7115
7116 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
7117 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
7118 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
7119 $new_array_options[$key] = null;
7120 continue;
7121 }
7122
7123 // Similar code than into insertExtraFields
7124 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'.
7125 $v = $this->array_options[$key];
7126 if (ExtraFields::isEmptyValue($v, $attributeType)) {
7127 $langs->load("errors");
7128 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
7129 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7130 return -1;
7131 }
7132 }
7133
7134 if (!empty($attrfieldcomputed)) {
7135 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7136 global $objectoffield; // We set a global variable to $objectoffield so
7137 $objectoffield = $this; // we can use it inside the computed formula
7138 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7139 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7140 $new_array_options[$key] = $value;
7141 } else {
7142 $new_array_options[$key] = null;
7143 }
7144 continue;
7145 }
7146
7147 switch ($attributeType) {
7148 case 'int':
7149 case 'duration':
7150 case 'stars':
7151 if (!is_numeric($value) && $value != '') {
7152 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7153 return -1;
7154 } elseif ($value === '') {
7155 $new_array_options[$key] = null;
7156 }
7157 break;
7158 case 'boolean':
7159 if ($value === '' || $value === false || $value === null) {
7160 $new_array_options[$key] = null;
7161 }
7162 break;
7163 case 'price':
7164 case 'double':
7165 $value = price2num($value);
7166 if (!is_numeric($value) && $value != '') {
7167 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7168 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7169 return -1;
7170 } elseif ($value == '') {
7171 $value = null;
7172 }
7173 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7174 $new_array_options[$key] = $value;
7175 break;
7176 /*case 'select': // Not required, we chose value='0' for undefined values
7177 if ($value=='-1')
7178 {
7179 $this->array_options[$key] = null;
7180 }
7181 break;*/
7182 case 'password':
7183 $algo = '';
7184 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7185 // If there is an encryption choice, we use it to encrypt data before insert
7186 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7187 $algo = reset($tmparrays);
7188 if ($algo != '') {
7189 //global $action; // $action may be 'create', 'update', 'update_extras'...
7190 //var_dump($action);
7191 //var_dump($this->oldcopy);exit;
7192 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
7193 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7194 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7195 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7196 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7197 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7198 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7199 } else {
7200 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7201 }
7202 } else {
7203 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7204 }
7205 } else {
7206 // If value has changed
7207 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7208 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7209 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7210 } else {
7211 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7212 }
7213 } else {
7214 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7215 }
7216 }
7217 } else {
7218 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7219 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7220 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7221 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7222 } else {
7223 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7224 }
7225 }
7226 } else {
7227 // No encryption
7228 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7229 }
7230 } else { // Common usage
7231 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7232 }
7233 break;
7234 case 'date':
7235 case 'datetime':
7236 // If data is a string instead of a timestamp, we convert it
7237 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7238 $this->array_options[$key] = strtotime($this->array_options[$key]);
7239 }
7240 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7241 break;
7242 case 'datetimegmt':
7243 // If data is a string instead of a timestamp, we convert it
7244 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7245 $this->array_options[$key] = strtotime($this->array_options[$key]);
7246 }
7247 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7248 break;
7249 case 'link':
7250 $param_list = array_keys($attributeParam['options']);
7251 // 0 : ObjectName
7252 // 1 : classPath
7253 $InfoFieldList = explode(":", $param_list[0]);
7254 dol_include_once($InfoFieldList[1]);
7255 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7256 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7257 $new_array_options[$key] = '';
7258 } elseif ($value) {
7259 $object = new $InfoFieldList[0]($this->db);
7260 '@phan-var-force CommonObject $object';
7261
7262 $objectId = 0;
7263
7264 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7265 if (is_numeric($value)) {
7266 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7267 } else {
7268 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7269 }
7270
7271 $obj = $this->db->getRow($sqlFetchObject);
7272
7273 // getRow() returns an object on success, int 0 when the query succeeded but
7274 // returned no row, and false on SQL failure. Testing "!== false" let the 0
7275 // through as a success: $obj->rowid on an int is null, $res was set to 1 and
7276 // null was stored in the column while a success was reported.
7277 if (is_object($obj)) {
7278 $objectId = $obj->rowid;
7279 $res = 1;
7280 } else {
7281 $res = -1;
7282 }
7283
7284 if ($res > 0) {
7285 $new_array_options[$key] = $objectId;
7286 } else {
7287 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7288 return -1;
7289 }
7290 }
7291 } else {
7292 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7293 }
7294 break;
7295 case 'checkbox':
7296 case 'chkbxlst':
7297 if (is_array($this->array_options[$key])) {
7298 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7299 } else {
7300 $new_array_options[$key] = $this->array_options[$key];
7301 }
7302 break;
7303 }
7304 }
7305
7306 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7307 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7308
7309 foreach ($new_array_options as $key => $newValue) {
7310 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7311 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7312 if ($attributeType === 'separate') {
7313 // separator extrafields don't have data per object so they don't have a comlumn in the database
7314 continue;
7315 }
7316 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7317 // Add field of attribute
7318 if (! $geoDataType) {
7319 // not a geodata type
7320 if ($newValue != '') {
7321 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7322 } else {
7323 $sqlColumnValues[$attributeKey] = 'null';
7324 }
7325 continue;
7326 }
7327 if (empty($newValue)) {
7328 $sqlColumnValues[$attributeKey] = 'null';
7329 continue;
7330 }
7331 if (preg_match('/error/i', $newValue)) {
7332 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7333 $sqlColumnValues[$attributeKey] = 'null';
7334 continue;
7335 }
7336
7337 // Geodata type: Text must be a WKT string. Examples:
7338 // - point => "POINT(15 20)"
7339 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7340 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7341 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7342 $sqlColumnValues[$attributeKey] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')"; // @phan-suppress-current-line SqlInjection
7343 }
7344
7345 $this->db->begin();
7346
7347 $table_element = $this->table_element;
7348 if ($table_element == 'categorie') {
7349 $table_element = 'categories'; // For compatibility
7350 }
7351 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7352
7353 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7354
7355 $linealreadyfound = 0;
7356 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7357 $resql = $this->db->query($sql);
7358 if ($resql) {
7359 $tmpobj = $this->db->fetch_object($resql);
7360 if ($tmpobj) {
7361 $linealreadyfound = $tmpobj->nb;
7362 }
7363 }
7364
7365 // if the extrafields row already exists for the object, we update it
7366 if ($linealreadyfound) {
7367 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7368 if (empty($sqlColumnValues)) {
7369 // No column of the target element to update. This happens when the object was created from
7370 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7371 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7372 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7373 // that would produce an invalid SQL statement.
7374 $this->db->commit();
7375 return 1;
7376 }
7377 $sqlColumnValueString = implode(
7378 ',',
7383 array_map(function ($key) use ($sqlColumnValues) {
7384 return "{$key} = {$sqlColumnValues[$key]}";
7385 }, array_keys($sqlColumnValues))
7386 );
7387 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7388 } else {
7389 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7390 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7391 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7392 if (isset($sqlColumnValues[$key])) {
7393 // extrafield value already provided: no need to add it
7394 continue;
7395 }
7396 // default value: empty string, except for 'int', 'double' and 'price'
7397 $sqlColumnValues[$key] = "''";
7398 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7399 $sqlColumnValues[$key] = 0;
7400 }
7401 }
7402 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7403 $sqlValues = implode(',', array_values($sqlColumnValues));
7404 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7405 }
7406
7407 $resql = $this->db->query($sql);
7408 if (!$resql) {
7409 $this->error = $this->db->lasterror();
7410 $error++;
7411 }
7412
7413 // Update also the user of last modification in parent table
7414 if (!$error && !empty($this->fields['fk_user_modif'])) { // @phan-suppress-current-line PhanTypeMismatchProperty
7415 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7416 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7417 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7418 $this->db->query($sql);
7419 }
7420
7421 if (!$error && $trigger) {
7422 // Call trigger
7423 $this->context = array('extrafieldaddupdate' => 1);
7424 $result = $this->call_trigger($trigger, $userused);
7425 if ($result < 0) {
7426 $error++;
7427 }
7428 // End call trigger
7429 }
7430
7431 if ($error) {
7432 $this->db->rollback();
7433 return -1;
7434 } else {
7435 $this->db->commit();
7436 return 1;
7437 }
7438 }
7439
7451 public function insertExtraLanguages($trigger = '', $userused = null)
7452 {
7453 global $langs, $user;
7454
7455 if (empty($userused)) {
7456 $userused = $user;
7457 }
7458
7459 $error = 0;
7460
7461 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7462 return 0; // For avoid conflicts if trigger used
7463 }
7464
7465 if (is_array($this->array_languages)) {
7466 $new_array_languages = $this->array_languages;
7467
7468 foreach ($new_array_languages as $key => $value) {
7469 $attributeKey = $key;
7470 $attributeType = $this->fields[$attributeKey]['type'];
7471 $attributeLabel = $this->fields[$attributeKey]['label'];
7472
7473 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7474 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7475
7476 switch ($attributeType) {
7477 case 'int':
7478 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7479 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7480 return -1;
7481 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7482 $new_array_languages[$key] = null;
7483 }
7484 break;
7485 case 'double':
7486 $value = price2num((string) $value);
7487 if (!is_numeric($value) && $value != '') {
7488 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7489 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7490 return -1;
7491 } elseif ($value == '') {
7492 $new_array_languages[$key] = null;
7493 } else {
7494 $new_array_languages[$key] = $value;
7495 }
7496 break;
7497 /*case 'select': // Not required, we chose value='0' for undefined values
7498 if ($value=='-1')
7499 {
7500 $this->array_options[$key] = null;
7501 }
7502 break;*/
7503 }
7504 }
7505
7506 $this->db->begin();
7507
7508 $table_element = $this->table_element;
7509 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7510 $table_element = 'categories'; // For compatibility
7511 }
7512
7513 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7514
7515 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7516 foreach ($langcodearray as $langcode => $value) {
7517 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7518 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7519 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7520 $this->db->query($sql_del);
7521
7522 if ($value !== '') {
7523 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7524 $sql .= ") VALUES (".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7525 $sql .= ")";
7526
7527 $resql = $this->db->query($sql);
7528 if (!$resql) {
7529 $this->error = $this->db->lasterror();
7530 $error++;
7531 break;
7532 }
7533 }
7534 }
7535 }
7536
7537 if (!$error && $trigger) {
7538 // Call trigger
7539 $this->context = array('extralanguagesaddupdate' => 1);
7540 $result = $this->call_trigger($trigger, $userused);
7541 if ($result < 0) {
7542 $error++;
7543 }
7544 // End call trigger
7545 }
7546
7547 if ($error) {
7548 $this->db->rollback();
7549 return -1;
7550 } else {
7551 $this->db->commit();
7552 return 1;
7553 }
7554 } else {
7555 return 0;
7556 }
7557 }
7558
7569 public function updateExtraField($key, $trigger = null, $userused = null)
7570 {
7571 global $langs, $user, $extrafields, $hookmanager;
7572
7573 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7574 return 0;
7575 }
7576
7577 if (empty($userused)) {
7578 $userused = $user;
7579 }
7580
7581 $error = 0;
7582
7583 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7584 // Check parameters
7585 $langs->load('admin');
7586 if (!isset($extrafields) || !is_object($extrafields)) {
7587 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7588 $extrafields = new ExtraFields($this->db);
7589 }
7590 $extrafields->fetch_name_optionals_label($this->table_element);
7591 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7592 // Extrafield not defined for this object type: nothing to update
7593 return 0;
7594 }
7595 $value = $this->array_options["options_".$key];
7596
7597 $attributeKey = $key;
7598 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7599 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7600 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7601 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7602 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7603 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7604 //$attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7605
7606 // Similar code than into insertExtraFields
7607 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'.
7608 $mandatorypb = false;
7609 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7610 $mandatorypb = true;
7611 }
7612 if ($this->array_options["options_".$key] === '') {
7613 $mandatorypb = true;
7614 }
7615 if ($mandatorypb) {
7616 $langs->load("errors");
7617 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7618 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7619 return -1;
7620 }
7621 }
7622
7623 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7624 $new_array_options = $this->array_options;
7625
7626 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7627 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7628 if (!empty($attrfieldcomputed)) {
7629 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7630 global $objectoffield; // We set a global variable to $objectoffield so
7631 $objectoffield = $this; // we can use it inside the computed formula
7632 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7633 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7634
7635 $new_array_options["options_".$key] = $value;
7636
7637 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7638 } else {
7639 $new_array_options["options_".$key] = null;
7640
7641 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7642 }
7643 }
7644
7645 switch ($attributeType) {
7646 case 'int':
7647 if (!is_numeric($value) && $value != '') {
7648 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7649 return -1;
7650 } elseif ($value === '') {
7651 $new_array_options["options_".$key] = null;
7652
7653 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7654 }
7655 break;
7656 case 'price':
7657 case 'double':
7658 $value = price2num($value);
7659 if (!is_numeric($value) && $value != '') {
7660 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7661 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7662 return -1;
7663 } elseif ($value === '') {
7664 $value = null;
7665 }
7666 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7667 $new_array_options["options_".$key] = $value;
7668
7669 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7670 break;
7671 /*case 'select': // Not required, we chose value='0' for undefined values
7672 if ($value=='-1')
7673 {
7674 $new_array_options["options_".$key] = $value;
7675
7676 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7677 }
7678 break;*/
7679 case 'password':
7680 $algo = '';
7681 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7682 // If there is an encryption choice, we use it to encrypt data before insert
7683 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7684 $algo = reset($tmparrays);
7685 if ($algo != '') {
7686 //global $action; // $action may be 'create', 'update', 'update_extras'...
7687 //var_dump($action);
7688 //var_dump($this->oldcopy);exit;
7689 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7690 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
7691 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7692 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.
7693 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7694 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7695 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7696 } else {
7697 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7698 }
7699 } else {
7700 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7701 }
7702 } else {
7703 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7704 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7705 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7706 } else {
7707 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7708 }
7709 } else {
7710 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7711 }
7712 }
7713 } else {
7714 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7715 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7716 } else {
7717 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7718 }
7719 }
7720 } else {
7721 // No encryption
7722 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7723 }
7724 } else { // Common usage
7725 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7726 }
7727
7728 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7729 break;
7730 case 'date':
7731 case 'datetime':
7732 if (empty($this->array_options["options_".$key])) {
7733 $new_array_options["options_".$key] = null;
7734
7735 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7736 } else {
7737 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7738 }
7739 break;
7740 case 'datetimegmt':
7741 if (empty($this->array_options["options_".$key])) {
7742 $new_array_options["options_".$key] = null;
7743
7744 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7745 } else {
7746 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7747 }
7748 break;
7749 case 'boolean':
7750 if (empty($this->array_options["options_".$key])) {
7751 $new_array_options["options_".$key] = null;
7752
7753 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7754 }
7755 break;
7756 case 'link':
7757 if ($this->array_options["options_".$key] === '') {
7758 $new_array_options["options_".$key] = null;
7759
7760 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7761 }
7762 break;
7763 /*
7764 case 'link':
7765 $param_list = array_keys($attributeParam['options']);
7766 // 0 : ObjectName
7767 // 1 : classPath
7768 $InfoFieldList = explode(":", $param_list[0]);
7769 dol_include_once($InfoFieldList[1]);
7770 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7771 {
7772 if ($value == '-1') // -1 is key for no defined in combo list of objects
7773 {
7774 $new_array_options[$key] = '';
7775 } elseif ($value) {
7776 $object = new $InfoFieldList[0]($this->db);
7777 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7778 else $res = $object->fetch(0, $value); // For compatibility
7779
7780 if ($res > 0) $new_array_options[$key] = $object->id;
7781 else {
7782 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7783 $this->db->rollback();
7784 return -1;
7785 }
7786 }
7787 } else {
7788 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7789 }
7790 break;
7791 */
7792 case 'checkbox':
7793 case 'chkbxlst':
7794 $new_array_options = array();
7795 if (is_array($this->array_options["options_".$key])) {
7796 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7797 } else {
7798 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7799 }
7800
7801 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7802 break;
7803 }
7804
7805 $this->db->begin();
7806
7807 $linealreadyfound = 0;
7808
7809 // 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)
7810 $table_element = $this->table_element;
7811 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7812 $table_element = 'categories'; // For compatibility
7813 }
7814
7815 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7816 $resql = $this->db->query($sql);
7817 if ($resql) {
7818 $tmpobj = $this->db->fetch_object($resql);
7819 if ($tmpobj) {
7820 $linealreadyfound = $tmpobj->nb;
7821 }
7822 }
7823
7824 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7825 if ($linealreadyfound) {
7826 $sanitizedGeoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null; // @phan-suppress-current-line SqlInjection
7827 if ($this->array_options["options_".$key] === null) {
7828 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7829 $sql .= " SET ".$this->db->sanitize($key)." = null";
7830 } elseif (!empty($sanitizedGeoDataType['ST_Function'])) {
7831 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7832 $sql .= " SET ".$this->db->sanitize($key)." = ".$this->db->sanitize($sanitizedGeoDataType['ST_Function'])."('".$this->db->escape($this->array_options["options_".$key])."')";
7833 } else {
7834 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7835 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7836 $sql .= " SET ".$this->db->sanitize($key)." = '".$this->db->escape($new_array_options["options_".$key])."'";
7837 }
7838 $sql .= " WHERE fk_object = ".((int) $this->id);
7839
7840 $resql = $this->db->query($sql);
7841 if (!$resql) {
7842 $error++;
7843 $this->error = $this->db->lasterror();
7844 }
7845 } else {
7846 $result = $this->insertExtraFields('', $user);
7847 if ($result < 0) {
7848 $error++;
7849 }
7850 }
7851
7852 // Update also the user of last modification in parent table
7853 if (!$error && !empty($this->fields['fk_user_modif'])) {
7854 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7855 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7856 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7857 $this->db->query($sql);
7858 }
7859
7860 if (!$error) {
7861 $parameters = array('key' => $key);
7862 global $action;
7863 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7864 if ($reshook < 0) {
7865 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7866 }
7867 }
7868
7869 if (!$error && $trigger) {
7870 // Call trigger
7871 $this->context = array('extrafieldupdate' => 1);
7872 $result = $this->call_trigger($trigger, $userused);
7873 if ($result < 0) {
7874 $error++;
7875 }
7876 // End call trigger
7877 }
7878
7879 if ($error) {
7880 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7881 $this->db->rollback();
7882 return -1;
7883 } else {
7884 $this->db->commit();
7885 return 1;
7886 }
7887 } else {
7888 return 0;
7889 }
7890 }
7891
7898 public function getExtraField($key)
7899 {
7900 return $this->array_options['options_'.$key] ?? null;
7901 }
7902
7910 public function setExtraField($key, $value)
7911 {
7912 $this->array_options['options_'.$key] = $value;
7913 }
7914
7925 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7926 {
7927 global $user;
7928
7929 if (empty($userused)) {
7930 $userused = $user;
7931 }
7932
7933 //$error = 0;
7934
7935 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7936 return 0; // For avoid conflicts if trigger used
7937 }
7938
7939 return 0;
7940 }
7941
7942
7957 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7958 {
7959 global $conf, $langs, $form;
7960
7961
7962 // 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...)
7963
7964 if (!is_object($form)) {
7965 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7966 $form = new Form($this->db);
7967 }
7968
7969 if (!empty($this->fields)) {
7970 $val = $this->fields[$key];
7971 }
7972
7973 // Validation tests and output
7974 $fieldValidationErrorMsg = '';
7975 $validationClass = '';
7976 $fieldValidationErrorMsg = $this->getFieldError($key);
7977 if (!empty($fieldValidationErrorMsg)) {
7978 $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
7979 } else {
7980 $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
7981 }
7982
7983 //$valuemultiselectinput = array();
7984 $out = '';
7985 $type = '';
7986 $isDependList = 0;
7987 $param = array();
7988 $param['options'] = array();
7989 $reg = array();
7990 // @phan-suppress-next-line PhanTypeMismatchProperty
7991 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7992 // Because we work on extrafields
7993 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7994 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7995 $type = 'link';
7996 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7997 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7998 $type = 'link';
7999 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
8000 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
8001 $type = 'link';
8002 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8003 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
8004 $type = 'sellist';
8005 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8006 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8007 $type = 'sellist';
8008 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
8009 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
8010 $type = 'sellist';
8011 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8012 $param['options'] = array($reg[1] => 'N');
8013 $type = 'chkbxlst';
8014 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
8015 $param['options'] = array();
8016 $type = 'varchar';
8017 $size = $reg[1];
8018 } elseif (preg_match('/varchar/', $val['type'])) {
8019 $param['options'] = array();
8020 $type = 'varchar';
8021 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8022 $param['options'] = array();
8023 $type = 'stars';
8024 $size = $reg[1];
8025 } else {
8026 $param['options'] = array();
8027 $type = $this->fields[$key]['type'];
8028 }
8029 //var_dump($type); var_dump($param['options']);
8030
8031 // Special case that force options and type ($type can be integer, varchar, ...)
8032 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
8033 $param['options'] = $this->fields[$key]['arrayofkeyval'];
8034 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
8035 if (empty($this->fields[$key]['multiinput'])) {
8036 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8037 }
8038 }
8039
8040 $label = $this->fields[$key]['label'];
8041 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
8042 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8043 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
8044 // @phan-suppress-next-line PhanTypeMismatchProperty
8045 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
8046 // @phan-suppress-next-line PhanTypeMismatchProperty
8047 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
8048 // @phan-suppress-next-line PhanTypeMismatchProperty
8049 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
8050 // @phan-suppress-next-line PhanTypeMismatchProperty
8051 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
8052 // @phan-suppress-next-line PhanTypeMismatchProperty
8053 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
8054
8055 // @phan-suppress-next-line PhanTypeMismatchProperty
8056 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
8057 // @phan-suppress-next-line PhanTypeMismatchProperty
8058 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
8059 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
8060
8061 $objectid = $this->id;
8062
8063 if ($computed) {
8064 if (!preg_match('/^search_/', $keyprefix)) {
8065 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
8066 } else {
8067 return '';
8068 }
8069 }
8070
8071 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
8072 if (empty($morecss) && !empty($val['css'])) {
8073 $morecss = $val['css'];
8074 } elseif (empty($morecss)) {
8075 if ($type == 'date') {
8076 $morecss = 'minwidth100imp';
8077 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
8078 $morecss = 'minwidth200imp';
8079 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8080 $morecss = 'maxwidth75';
8081 } elseif ($type == 'url') {
8082 $morecss = 'minwidth400';
8083 } elseif ($type == 'boolean') {
8084 $morecss = '';
8085 } else {
8086 if (is_numeric($size) && round((float) $size) < 12) {
8087 $morecss = 'minwidth100';
8088 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8089 $morecss = 'minwidth200';
8090 } else {
8091 $morecss = 'minwidth400';
8092 }
8093 }
8094 }
8095
8096 // Add validation state class
8097 if (!empty($validationClass)) {
8098 $morecss .= $validationClass;
8099 }
8100
8101 if (in_array($type, array('date'))) {
8102 $tmp = explode(',', $size);
8103 $newsize = $tmp[0];
8104 $showtime = 0;
8105
8106 // Do not show current date when field not required (see selectDate() method)
8107 if (!$required && $value == '') {
8108 $value = '-1';
8109 }
8110
8111 // TODO Must also support $moreparam
8112 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
8113 } elseif (in_array($type, array('datetime'))) {
8114 $tmp = explode(',', $size);
8115 $newsize = $tmp[0];
8116 $showtime = 1;
8117
8118 // Do not show current date when field not required (see selectDate() method)
8119 if (!$required && $value == '') {
8120 $value = '-1';
8121 }
8122
8123 // TODO Must also support $moreparam
8124 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
8125 } elseif (in_array($type, array('duration'))) {
8126 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
8127 } elseif (in_array($type, array('int', 'integer'))) {
8128 $tmp = explode(',', $size);
8129 $newsize = $tmp[0];
8130 $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' : '').'>';
8131 } elseif (in_array($type, array('real'))) {
8132 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8133 } elseif (preg_match('/varchar/', (string) $type)) {
8134 $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' : '').'>';
8135 } elseif ($type == 'phone' && !preg_match('/search_/', $keyprefix)) {
8136 $out = $form->showPhoneInput($value, $keyprefix.$key.$keysuffix, !empty($this->country_id) ? $this->country_id : 0);
8137 } elseif (in_array($type, array('email', 'mail', 'url', 'ip'))) {
8138 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8139 } elseif (preg_match('/^text/', (string) $type)) {
8140 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8141 if (!empty($param['options'])) {
8142 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
8143 //var_dump($param['options']);
8144 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
8145 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
8146 $out .= "<script>";
8147 $out .= '
8148 function handlemultiinputdisabling(htmlname){
8149 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
8150 multiinput = $("#"+htmlname+"_multiinput");
8151 multiinput.find("option").each(function(){
8152 tmpval = $("#"+htmlname).val();
8153 tmpvalarray = tmpval.split("\n");
8154 valtotest = $(this).val();
8155 if(tmpvalarray.includes(valtotest)){
8156 $(this).prop("disabled",true);
8157 } else {
8158 if($(this).prop("disabled") == true){
8159 console.log(valtotest)
8160 $(this).prop("disabled", false);
8161 }
8162 }
8163 });
8164 }
8165
8166 $(document).ready(function () {
8167 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
8168 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
8169 tmpvalarray = tmpval.split(",");
8170 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
8171 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8172 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
8173 if(tmpval == ""){
8174 tmpval = valtotest;
8175 } else {
8176 tmpval = tmpval + "\n" + valtotest;
8177 }
8178 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
8179 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8180 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
8181 } else {
8182 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
8183 }
8184 });
8185 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
8186 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8187 });
8188 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8189 })';
8190 $out .= "</script>";
8191 $value = str_replace(',', "\n", $value);
8192 }
8193 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8194 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8195 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
8196 } else {
8197 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8198 }
8199 } elseif (preg_match('/^html/', (string) $type)) {
8200 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8201 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8202 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8203 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8204 } else {
8205 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8206 }
8207 } elseif ($type == 'boolean') {
8208 $checked = '';
8209 if (!empty($value)) {
8210 $checked = ' checked value="1" ';
8211 } else {
8212 $checked = ' value="1" ';
8213 }
8214 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
8215 } elseif ($type == 'price') {
8216 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8217 $value = price($value);
8218 }
8219 $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>';
8220 } elseif ($type == 'stars') {
8221 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8222 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8223 $i = 1;
8224 while ($i <= $size) {
8225 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8226 $i++;
8227 }
8228 $out .= '</div>';
8229 $out .= '<script>
8230 jQuery(function($) { /* commonobject.class.php 1 */
8231 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8232 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8233 container.find(".star").each(function() {
8234 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8235 });
8236 container.find(".star").on("mouseover", function() {
8237 let selectedStar = $(this).data("value");
8238 container.find(".star").each(function() {
8239 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8240 });
8241 });
8242 container.on("mouseout", function() {
8243 container.find(".star").each(function() {
8244 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8245 });
8246 });
8247 container.find(".star").off("click").on("click", function() {
8248 selectedStars = $(this).data("value");
8249 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8250 selectedStars = 0;
8251 }
8252 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8253 container.find(".star").each(function() {
8254 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8255 });
8256 });
8257 });
8258 </script>';
8259 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8260 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8261 $value = price($value);
8262 }
8263 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8264 } elseif ($type == 'select') { // combo list
8265 $out = '';
8266 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8267 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8268 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8269 }
8270
8271 $tmpselect = '';
8272 $nbchoice = 0;
8273
8274 foreach ($param['options'] as $keyb => $valb) {
8275 if ((string) $keyb == '') {
8276 continue;
8277 }
8278 if (strpos($valb, "|") !== false) {
8279 list($valb, $parent) = explode('|', $valb);
8280 }
8281 $nbchoice++;
8282 $tmpselect .= '<option value="'.$keyb.'"';
8283 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8284 if (!empty($parent)) {
8285 $isDependList = 1;
8286 }
8287 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8288 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8289 }
8290
8291 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8292 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8293 $out .= '<option value="0">&nbsp;</option>';
8294 }
8295 $out .= $tmpselect;
8296 $out .= '</select>';
8297 } elseif ($type == 'sellist') {
8298 $out = '';
8299 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8300 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8301 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8302 }
8303
8304 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8305 if (is_array($param['options'])) {
8306 $tmpparamoptions = array_keys($param['options']);
8307 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8308
8309 $InfoFieldList = explode(":", $paramoptions[0], 5);
8310 // 0 : tableName
8311 // 1 : label field name
8312 // 2 : key fields name (if different of rowid)
8313 // optional parameters...
8314 // 3 : key field parent (for dependent lists). How this is used ?
8315 // 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".
8316 // 5 : string category type. This replace the filter.
8317 // 6 : ids categories list separated by comma for category root. This replace the filter.
8318 // 7 : sort field
8319
8320 // If there is filter
8321 if (! empty($InfoFieldList[4])) {
8322 $pos = 0;
8323 $parenthesisopen = 0;
8324 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8325 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8326 $parenthesisopen++;
8327 }
8328 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8329 $parenthesisopen--;
8330 }
8331 $pos++;
8332 }
8333 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8334 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8335 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8336 $InfoFieldList[4] = $tmpbefore;
8337 if ($tmpafter !== '') {
8338 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8339 }
8340 //var_dump($InfoFieldList);
8341
8342 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8343 $reg = array();
8344 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8345 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8346 }
8347
8348 //var_dump($InfoFieldList);
8349 }
8350
8351 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8352
8353 $parentName = '';
8354 $parentField = '';
8355
8356 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8357
8358 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8359 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8360 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8361 } else {
8362 $keyList = $InfoFieldList[2].' as rowid';
8363 }
8364 }
8365 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8366 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8367 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8368 $keyList .= ', main.'.$parentField;
8369 } else {
8370 $keyList .= ', '.$parentField;
8371 }
8372 }
8373
8374 $filter_categorie = false;
8375 if (count($InfoFieldList) > 5) {
8376 if ($InfoFieldList[0] == 'categorie') {
8377 $filter_categorie = true;
8378 }
8379 }
8380
8381 if (!$filter_categorie) {
8382 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8383 if (!empty($fields_label)) {
8384 $keyList .= ', ';
8385 $keyList .= implode(', ', $fields_label);
8386 }
8387
8388 $sqlwhere = '';
8389 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8390 $sql .= " FROM " . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8391
8392 if (!empty($InfoFieldList[4])) {
8393 // can use SELECT request
8394 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8395 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8396 }
8397
8398 // current object id can be use into filter
8399 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8400 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8401 } else {
8402 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8403 }
8404
8405 // We have to join on extrafield table
8406 $errstr = '';
8407 if (strpos($InfoFieldList[4], 'extra') !== false) {
8408 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8409 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8410 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8411 } else {
8412 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8413 }
8414 } else {
8415 $sqlwhere .= ' WHERE 1=1';
8416 }
8417
8418 // Add Usf filter on second line
8419 /*
8420 if ($Usf) {
8421 $errorstr = '';
8422 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8423 if (!$errorstr) {
8424 $sqlwhere .= $sqlusf;
8425 } else {
8426 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8427 }
8428 }
8429 */
8430
8431 // Some tables may have field, some other not. For the moment we disable it.
8432 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8433 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8434 }
8435 $sql .= $sqlwhere;
8436
8437 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8438 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8439 $sql .= " ORDER BY ".$this->db->sanitize($InfoFieldList[7]);
8440 } else {
8441 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8442 }
8443 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8444 // print $sql;
8445
8446 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8447 $resql = $this->db->query($sql);
8448 if ($resql) {
8449 $out .= '<option value="0">&nbsp;</option>';
8450 $num = $this->db->num_rows($resql);
8451 $i = 0;
8452 while ($i < $num) {
8453 $labeltoshow = '';
8454 $obj = $this->db->fetch_object($resql);
8455
8456 // Several field into label (eq table:code|libelle:rowid)
8457 $notrans = false;
8458 $fields_label = explode('|', $InfoFieldList[1]);
8459 if (count($fields_label) > 1) {
8460 $notrans = true;
8461 foreach ($fields_label as $field_toshow) {
8462 $labeltoshow .= $obj->$field_toshow . ' ';
8463 }
8464 } else {
8465 $labeltoshow = $obj->{$InfoFieldList[1]};
8466 }
8467 $labeltoshow = dol_trunc($labeltoshow, 45);
8468
8469 if ($value == $obj->rowid) {
8470 foreach ($fields_label as $field_toshow) {
8471 $translabel = $langs->trans($obj->$field_toshow);
8472 if ($translabel != $obj->$field_toshow) {
8473 $labeltoshow = dol_trunc($translabel) . ' ';
8474 } else {
8475 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8476 }
8477 }
8478 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8479 } else {
8480 if (!$notrans) {
8481 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8482 if ($translabel != $obj->{$InfoFieldList[1]}) {
8483 $labeltoshow = dol_trunc($translabel, 18);
8484 } else {
8485 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8486 }
8487 }
8488 if (empty($labeltoshow)) {
8489 $labeltoshow = '(not defined)';
8490 }
8491 if ($value == $obj->rowid) {
8492 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8493 }
8494
8495 if (!empty($InfoFieldList[3]) && $parentField) {
8496 $parent = $parentName . ':' . $obj->{$parentField};
8497 $isDependList = 1;
8498 }
8499
8500 $out .= '<option value="' . $obj->rowid . '"';
8501 $out .= ($value == $obj->rowid ? ' selected' : '');
8502 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8503 $out .= '>' . $labeltoshow . '</option>';
8504 }
8505
8506 $i++;
8507 }
8508 $this->db->free($resql);
8509 } else {
8510 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8511 }
8512 } else {
8513 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8514 $categcode = $InfoFieldList[5];
8515 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8516 $tmpcategory = new Categorie($this->db);
8517 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8518 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8519 }
8520
8521 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8522 $out .= '<option value="0">&nbsp;</option>';
8523 foreach ($data as $data_key => $data_value) {
8524 $out .= '<option value="' . $data_key . '"';
8525 $out .= ($value == $data_key ? ' selected' : '');
8526 $out .= '>' . $data_value . '</option>';
8527 }
8528 }
8529 }
8530 $out .= '</select>';
8531 } elseif ($type == 'checkbox') {
8532 $value_arr = explode(',', $value);
8533 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8534 } elseif ($type == 'radio') {
8535 $out = '';
8536 foreach ($param['options'] as $keyopt => $valopt) {
8537 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8538 $out .= ' value="'.$keyopt.'"';
8539 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8540 $out .= ($value == $keyopt ? 'checked' : '');
8541 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8542 }
8543 } elseif ($type == 'chkbxlst') {
8544 if (is_array($value)) {
8545 $value_arr = $value;
8546 } else {
8547 $value_arr = explode(',', $value);
8548 }
8549
8550 if (is_array($param['options'])) {
8551 $tmpparamoptions = array_keys($param['options']);
8552 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8553
8554 $InfoFieldList = explode(":", $paramoptions[0], 5);
8555 // 0 : tableName
8556 // 1 : label field name
8557 // 2 : key fields name (if different of rowid)
8558 // optional parameters...
8559 // 3 : key field parent (for dependent lists). How this is used ?
8560 // 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".
8561 // 5 : string category type. This replace the filter.
8562 // 6 : ids categories list separated by comma for category root. This replace the filter.
8563 // 7 : sort field
8564
8565 // If there is a filter
8566 if (! empty($InfoFieldList[4])) {
8567 $pos = 0;
8568 $parenthesisopen = 0;
8569 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8570 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8571 $parenthesisopen++;
8572 }
8573 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8574 $parenthesisopen--;
8575 }
8576 $pos++;
8577 }
8578 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8579 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8580 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8581 $InfoFieldList[4] = $tmpbefore;
8582 if ($tmpafter !== '') {
8583 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8584 }
8585
8586 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8587 $reg = array();
8588 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8589 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8590 }
8591
8592 //var_dump($InfoFieldList);
8593 }
8594
8595 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8596
8597 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8598
8599 $parentName = '';
8600 $parentField = '';
8601
8602 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8603
8604 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8605 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8606 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8607 } else {
8608 $keyList = $InfoFieldList[2].' as rowid';
8609 }
8610 }
8611 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8612 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8613 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8614 $keyList .= ', main.'.$parentField;
8615 } else {
8616 $keyList .= ', '.$parentField;
8617 }
8618 }
8619
8620 $filter_categorie = false;
8621 if (count($InfoFieldList) > 5) {
8622 if ($InfoFieldList[0] == 'categorie') {
8623 $filter_categorie = true;
8624 }
8625 }
8626
8627 // Common filter
8628 if (!$filter_categorie) {
8629 $fields_label = explode('|', $InfoFieldList[1]);
8630 if (is_array($fields_label)) {
8631 $keyList .= ', ';
8632 $keyList .= implode(', ', $fields_label);
8633 }
8634
8635 $sqlwhere = '';
8636 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8637 $sql .= ' FROM ' . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8638
8639 if (!empty($InfoFieldList[4])) {
8640 // can use SELECT request
8641 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8642 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8643 }
8644
8645 // current object id can be use into filter
8646 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8647 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8648 } else {
8649 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8650 }
8651
8652 // We have to join on extrafield table
8653 $errstr = '';
8654 if (strpos($InfoFieldList[4], 'extra') !== false) {
8655 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8656 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8657 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8658 } else {
8659 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8660 }
8661 } else {
8662 $sqlwhere .= ' WHERE 1=1';
8663 }
8664
8665 // Add Usf filter on second line
8666 /*
8667 if ($Usf) {
8668 $errorstr = '';
8669 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8670 if (!$errorstr) {
8671 $sqlwhere .= $sqlusf;
8672 } else {
8673 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8674 }
8675 }
8676 */
8677
8678 // Some tables may have field, some other not. For the moment we disable it.
8679 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8680 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8681 }
8682 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8683 // print $sql;
8684
8685 $sql .= $sqlwhere;
8686
8687 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8688
8689 $resql = $this->db->query($sql);
8690 if ($resql) {
8691 $num = $this->db->num_rows($resql);
8692 $i = 0;
8693
8694 $data = array();
8695
8696 while ($i < $num) {
8697 $labeltoshow = '';
8698 $obj = $this->db->fetch_object($resql);
8699
8700 $notrans = false;
8701 // Several field into label (eq table:code|libelle:rowid)
8702 $fields_label = explode('|', $InfoFieldList[1]);
8703 if (count($fields_label) > 1) {
8704 $notrans = true;
8705 foreach ($fields_label as $field_toshow) {
8706 $labeltoshow .= $obj->$field_toshow . ' ';
8707 }
8708 } else {
8709 $labeltoshow = $obj->{$InfoFieldList[1]};
8710 }
8711 $labeltoshow = dol_trunc($labeltoshow, 45);
8712
8713 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8714 foreach ($fields_label as $field_toshow) {
8715 $translabel = $langs->trans($obj->$field_toshow);
8716 if ($translabel != $obj->$field_toshow) {
8717 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8718 } else {
8719 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8720 }
8721 }
8722
8723 $data[$obj->rowid] = $labeltoshow;
8724 } else {
8725 if (!$notrans) {
8726 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8727 if ($translabel != $obj->{$InfoFieldList[1]}) {
8728 $labeltoshow = dol_trunc($translabel, 18);
8729 } else {
8730 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8731 }
8732 }
8733 if (empty($labeltoshow)) {
8734 $labeltoshow = '(not defined)';
8735 }
8736
8737 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8738 $data[$obj->rowid] = $labeltoshow;
8739 }
8740
8741 if (!empty($InfoFieldList[3]) && $parentField) {
8742 $parent = $parentName . ':' . $obj->{$parentField};
8743 $isDependList = 1;
8744 }
8745
8746 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8747 }
8748
8749 $i++;
8750 }
8751 $this->db->free($resql);
8752
8753 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8754 } else {
8755 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8756 }
8757 } else {
8758 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8759 $categcode = $InfoFieldList[5];
8760 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8761 $tmpcategory = new Categorie($this->db);
8762 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8763 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8764 }
8765
8766 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8767 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8768 }
8769 }
8770 } elseif ($type == 'link') {
8771 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8772 // Filter can contains some ':' inside.
8773 $param_list = array_keys($param['options']);
8774 $param_list_array = explode(':', $param_list[0], 4);
8775
8776 $showempty = (($required && $default != '') ? 0 : 1);
8777
8778 if (!preg_match('/search_/', $keyprefix)) {
8779 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8780 // @phan-suppress-next-line PhanTypeMismatchProperty
8781 if (!empty($this->fields[$key]['picto'])) {
8782 $morecss .= ' widthcentpercentminusxx';
8783 } else {
8784 $morecss .= ' widthcentpercentminusx';
8785 }
8786 } else {
8787 // @phan-suppress-next-line PhanTypeMismatchProperty
8788 if (!empty($this->fields[$key]['picto'])) {
8789 $morecss .= ' widthcentpercentminusx';
8790 }
8791 }
8792 }
8793
8794 // $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.
8795
8796 // $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.
8797
8798 $objectfield = $val;
8799
8800 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8801 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8802
8803 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8804 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8805 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8806 list($class, $classfile) = explode(':', $param_list[0]);
8807 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8808 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8809 } else {
8810 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8811 }
8812 $paramforthenewlink = '';
8813 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8814 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8815 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8816 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8817 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8818 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8819 $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>';
8820 }
8821 }
8822 } elseif ($type == 'password') {
8823 // If prefix is 'search_', field is used as a filter, we use a common text field.
8824 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8825 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8826 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8827 } else {
8828 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8829 }
8830 } elseif ($type == 'array') {
8831 $newval = $val;
8832 $newval['type'] = 'varchar(256)';
8833
8834 $out = '';
8835 if (!empty($value)) {
8836 foreach ($value as $option) {
8837 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8838 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8839 }
8840 }
8841 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8842
8843 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8844 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8845
8846 if (!empty($conf->use_javascript_ajax)) {
8847 $out .= '
8848 <script nonce="'.getNonce().'">
8849 $(document).ready(function() {
8850 $(\'a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add\').click(function() {
8851 $(\''.dol_escape_js($newInput).'\').insertBefore(this);
8852 });
8853
8854 $(document).on("click", \'a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del\', function() {
8855 $(this).parent().remove();
8856 });
8857 });
8858 </script>';
8859 }
8860 }
8861 if (!empty($hidden)) {
8862 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8863 }
8864
8865 if ($isDependList == 1) {
8866 $out .= $this->getJSListDependancies('_common');
8867 }
8868 /* Add comments
8869 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8870 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8871 */
8872
8873 // Display error message for field
8874 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8875 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8876 }
8877
8878 return $out;
8879 }
8880
8894 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8895 {
8896 global $conf, $langs, $form;
8897
8898 // 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...)
8899
8900 if (!is_object($form)) {
8901 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8902 $form = new Form($this->db);
8903 }
8904
8905 //$label = empty($val['label']) ? '' : $val['label'];
8906 $type = empty($val['type']) ? '' : $val['type'];
8907 $size = empty($val['css']) ? '' : $val['css'];
8908
8909 $reg = array();
8910
8911 // Convert var to be able to share same code than showOutputField of extrafields
8912 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8913 $type = 'varchar'; // convert varchar(xx) int varchar
8914 $size = $reg[1];
8915 } elseif (preg_match('/varchar/', $type)) {
8916 $type = 'varchar'; // convert varchar(xx) int varchar
8917 }
8918 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8919 // @phan-suppress-next-line PhanTypeMismatchProperty
8920 if (empty($this->fields[$key]['multiinput'])) {
8921 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8922 }
8923 }
8924 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8925 $type = 'link';
8926 }
8927
8928 $default = empty($val['default']) ? '' : $val['default'];
8929 $computed = empty($val['computed']) ? '' : $val['computed'];
8930 $unique = empty($val['unique']) ? '' : $val['unique'];
8931 $required = empty($val['required']) ? '' : $val['required'];
8932 $param = array();
8933 $param['options'] = array();
8934
8935 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8936 $param['options'] = $val['arrayofkeyval'];
8937 }
8938 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8939 $type = 'link';
8940 $stringforoptions = $reg[1].':'.$reg[2];
8941 // Special case: Force addition of getnomurlparam1 to -1 for users
8942 if ($reg[1] == 'User') {
8943 $stringforoptions .= ':#getnomurlparam1=-1';
8944 }
8945 $param['options'] = array($stringforoptions => $stringforoptions);
8946 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8947 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8948 $type = 'sellist';
8949 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8950 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8951 $type = 'sellist';
8952 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8953 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8954 $type = 'sellist';
8955 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8956 $param['options'] = array($reg[1] => 'N');
8957 $type = 'chkbxlst';
8958 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8959 $param['options'] = array();
8960 $type = 'stars';
8961 $size = $reg[1];
8962 }
8963
8964 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8965 $list = (empty($val['list']) ? '' : $val['list']);
8966 $help = (empty($val['help']) ? '' : $val['help']);
8967 $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)
8968
8969 if ($hidden) {
8970 return '';
8971 }
8972
8973 // If field is a computed field, value must become result of compute
8974 if ($computed) {
8975 // Make the eval of compute string
8976 //var_dump($computed);
8977 $value = dol_eval((string) $computed, 1, 0, '2');
8978 }
8979
8980 if (empty($morecss)) {
8981 if ($type == 'date') {
8982 $morecss = 'minwidth100imp';
8983 } elseif ($type == 'datetime' || $type == 'timestamp') {
8984 $morecss = 'minwidth200imp';
8985 } elseif (in_array($type, array('int', 'double', 'price'))) {
8986 $morecss = 'maxwidth75';
8987 } elseif ($type == 'url') {
8988 $morecss = 'minwidth400';
8989 } elseif ($type == 'boolean') {
8990 $morecss = '';
8991 } else {
8992 if (is_numeric($size) && round((float) $size) < 12) {
8993 $morecss = 'minwidth100';
8994 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8995 $morecss = 'minwidth200';
8996 } else {
8997 $morecss = 'minwidth400';
8998 }
8999 }
9000 }
9001
9002 // Format output value differently according to properties of field
9003 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
9004 // @phan-suppress-next-line PhanTypeMismatchProperty
9005 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.
9006 $value = $this->getNomUrl(1, '', 0, '', 1);
9007 }
9008 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
9009 $value = $this->getLibStatut(3);
9010 } elseif ($type == 'date') {
9011 if (!empty($value)) {
9012 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
9013 } else {
9014 $value = '';
9015 }
9016 } elseif ($type == 'datetime' || $type == 'timestamp') {
9017 if (!empty($value)) {
9018 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
9019 } else {
9020 $value = '';
9021 }
9022 } elseif ($type == 'duration') {
9023 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
9024 if (!is_null($value) && $value !== '') {
9025 $value = convertSecondToTime((int) $value, 'allhourmin');
9026 }
9027 } elseif ($type == 'double' || $type == 'real') {
9028 if (!is_null($value) && $value !== '') {
9029 $value = price($value);
9030 }
9031 } elseif ($type == 'boolean') {
9032 $checked = '';
9033 if (!empty($value)) {
9034 $checked = ' checked ';
9035 }
9036 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
9037 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
9038 } else {
9039 $value = yn($value ? 1 : 0);
9040 }
9041 } elseif ($type == 'mail' || $type == 'email') {
9042 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
9043 } elseif ($type == 'url') {
9044 $value = dol_print_url((string) $value, '_blank', 32, 1);
9045 } elseif ($type == 'phone') {
9046 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
9047 } elseif ($type == 'ip') {
9048 $value = dol_print_ip((string) $value, 0);
9049 } elseif ($type == 'stars') {
9050 $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 : '').'>';
9051 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
9052 $i = 1;
9053 while ($i <= $size) {
9054 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
9055 $i++;
9056 }
9057 $value .= '</div>';
9058 $value .= '<script>
9059 $(document).ready(function() { /* commonobject.class.php 2 */
9060 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
9061 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
9062 container.find(".star").each(function() {
9063 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9064 });
9065 container.find(".star").on("mouseover", function() {
9066 let selectedStar = $(this).data("value");
9067 container.find(".star").each(function() {
9068 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
9069 });
9070 });
9071 container.on("mouseout", function() {
9072 container.find(".star").each(function() {
9073 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9074 });
9075 });
9076 container.find(".star").off("click").on("click", function() {
9077 selectedStars = $(this).data("value");
9078 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
9079 selectedStars = 0;
9080 }
9081 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
9082 container.find(".star").each(function() {
9083 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9084 });
9085 $.ajax({
9086 url: "ajax/'.$this->element.'.php",
9087 method: "POST",
9088 data: {
9089 objectId: "'.$this->id.'",
9090 field: "'.$keyprefix.$key.$keysuffix.'",
9091 value: selectedStars,
9092 token: "'.newToken().'"
9093 },
9094 success: function(response) {
9095 var res = JSON.parse(response);
9096 console[res.status === "success" ? "log" : "error"](res.message);
9097 },
9098 error: function(xhr, status, error) {
9099 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
9100 }
9101 });
9102 });
9103 });
9104 </script>';
9105 } elseif ($type == 'price') {
9106 if (!is_null($value) && $value !== '') {
9107 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
9108 }
9109 } elseif ($type == 'select') {
9110 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
9111 if (strpos($value, "|") !== false) {
9112 $value = $langs->trans(explode('|', $value)[0]);
9113 } elseif (! is_numeric($value)) {
9114 $value = $langs->trans($value);
9115 }
9116 } elseif ($type == 'sellist') {
9117 $param_list = array_keys($param['options']);
9118 $InfoFieldList = explode(":", $param_list[0]);
9119
9120 $selectkey = "rowid";
9121 $keyList = 'rowid';
9122
9123 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
9124 $selectkey = $InfoFieldList[2];
9125 $keyList = $InfoFieldList[2].' as rowid';
9126 }
9127
9128 $fields_label = explode('|', $InfoFieldList[1]);
9129 if (is_array($fields_label)) {
9130 $keyList .= ', ';
9131 $keyList .= implode(', ', $fields_label);
9132 }
9133
9134 $filter_categorie = false;
9135 if (count($InfoFieldList) > 5) {
9136 if ($InfoFieldList[0] == 'categorie') {
9137 $filter_categorie = true;
9138 }
9139 }
9140
9141 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9142 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9143 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9144 $sql .= ' as main';
9145 }
9146 if ($selectkey == 'rowid' && empty($value)) {
9147 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = 0";
9148 } elseif ($selectkey == 'rowid') {
9149 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = ".((int) $value);
9150 } else {
9151 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = '".$this->db->escape((string) $value)."'";
9152 }
9153
9154 //$sql.= ' AND entity = '.((int) $conf->entity);
9155
9156 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
9157 $resql = $this->db->query($sql);
9158 if ($resql) {
9159 if (!$filter_categorie) {
9160 $value = ''; // value was used, so now we reset it to use it to build final output
9161 $numrows = $this->db->num_rows($resql);
9162 if ($numrows) {
9163 $obj = $this->db->fetch_object($resql);
9164
9165 // Several field into label (eq table:code|libelle:rowid)
9166 $fields_label = explode('|', $InfoFieldList[1]);
9167
9168 if (is_array($fields_label) && count($fields_label) > 1) {
9169 foreach ($fields_label as $field_toshow) {
9170 $translabel = '';
9171 if (!empty($obj->$field_toshow)) {
9172 $translabel = $langs->trans($obj->$field_toshow);
9173 }
9174 if ($translabel != $field_toshow) {
9175 $value .= dol_trunc($translabel, 18) . ' ';
9176 } else {
9177 $value .= $obj->$field_toshow . ' ';
9178 }
9179 }
9180 } else {
9181 $translabel = '';
9182 if (!empty($obj->{$InfoFieldList[1]})) {
9183 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9184 }
9185 if ($translabel != $obj->{$InfoFieldList[1]}) {
9186 $value = dol_trunc($translabel, 18);
9187 } else {
9188 $value = $obj->{$InfoFieldList[1]};
9189 }
9190 }
9191 }
9192 } else {
9193 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9194
9195 $toprint = array();
9196 $obj = $this->db->fetch_object($resql);
9197 $c = new Categorie($this->db);
9198 $c->fetch($obj->rowid);
9199 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9200 foreach ($ways as $way) {
9201 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9202 }
9203 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9204 }
9205 } else {
9206 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9207 }
9208 } elseif ($type == 'radio') {
9209 $value = $param['options'][(string) $value];
9210 } elseif ($type == 'checkbox') {
9211 $value_arr = explode(',', (string) $value);
9212 $value = '';
9213 if (is_array($value_arr) && count($value_arr) > 0) {
9214 $toprint = array();
9215 foreach ($value_arr as $keyval => $valueval) {
9216 if (!empty($valueval)) {
9217 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9218 }
9219 }
9220 if (!empty($toprint)) {
9221 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9222 }
9223 }
9224 } elseif ($type == 'chkbxlst') {
9225 $value_arr = (isset($value) ? explode(',', $value) : array());
9226
9227 $param_list = array_keys($param['options']);
9228 $InfoFieldList = explode(":", $param_list[0]);
9229
9230 $selectkey = "rowid";
9231 $keyList = 'rowid';
9232
9233 if (count($InfoFieldList) >= 3) {
9234 $selectkey = $InfoFieldList[2];
9235 $keyList = $InfoFieldList[2].' as rowid';
9236 }
9237
9238 $fields_label = explode('|', $InfoFieldList[1]);
9239 if (is_array($fields_label)) {
9240 $keyList .= ', ';
9241 $keyList .= implode(', ', $fields_label);
9242 }
9243
9244 $filter_categorie = false;
9245 if (count($InfoFieldList) > 5) {
9246 if ($InfoFieldList[0] == 'categorie') {
9247 $filter_categorie = true;
9248 }
9249 }
9250
9251 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9252 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9253 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9254 $sql .= ' as main';
9255 }
9256 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9257 // $sql.= ' AND entity = '.((int) $conf->entity);
9258
9259 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9260 $resql = $this->db->query($sql);
9261 if ($resql) {
9262 if (!$filter_categorie) {
9263 $value = ''; // value was used, so now we reset it to use it to build final output
9264 $toprint = array();
9265 while ($obj = $this->db->fetch_object($resql)) {
9266 // Several field into label (eq table:code|libelle:rowid)
9267 $fields_label = explode('|', $InfoFieldList[1]);
9268 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9269 if (is_array($fields_label) && count($fields_label) > 1) {
9270 foreach ($fields_label as $field_toshow) {
9271 $translabel = '';
9272 if (!empty($obj->$field_toshow)) {
9273 $translabel = $langs->trans($obj->$field_toshow);
9274 }
9275 if ($translabel != $field_toshow) {
9276 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9277 } else {
9278 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9279 }
9280 }
9281 } else {
9282 $translabel = '';
9283 if (!empty($obj->{$InfoFieldList[1]})) {
9284 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9285 }
9286 if ($translabel != $obj->{$InfoFieldList[1]}) {
9287 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9288 } else {
9289 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9290 }
9291 }
9292 }
9293 }
9294 } else {
9295 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9296
9297 $toprint = array();
9298 while ($obj = $this->db->fetch_object($resql)) {
9299 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9300 $c = new Categorie($this->db);
9301 $c->fetch($obj->rowid);
9302 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9303 foreach ($ways as $way) {
9304 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9305 }
9306 }
9307 }
9308 }
9309 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9310 } else {
9311 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9312 }
9313 } elseif ($type == 'link') {
9314 $out = '';
9315
9316 // only if something to display (perf)
9317 if ($value) {
9318 $param_list = array_keys($param['options']);
9319 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9320 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9321
9322 $InfoFieldList = explode(":", $param_list[0]);
9323
9324 $classname = $InfoFieldList[0];
9325 $classpath = $InfoFieldList[1];
9326
9327 // Set $getnomurlparam1 et getnomurlparam2
9328 $getnomurlparam = 3;
9329 $getnomurlparam2 = '';
9330 $regtmp = array();
9331 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9332 $getnomurlparam = $regtmp[1];
9333 }
9334 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9335 $getnomurlparam2 = $regtmp[1];
9336 }
9337
9338 if (!empty($classpath)) {
9339 dol_include_once($InfoFieldList[1]);
9340
9341 if ($classname && !class_exists($classname)) {
9342 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9343 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9344 $element_prop = getElementProperties($classname);
9345 if ($element_prop) {
9346 $classname = $element_prop['classname'];
9347 }
9348 }
9349
9350
9351 if ($classname && class_exists($classname)) {
9352 $object = new $classname($this->db);
9353 '@phan-var-force CommonObject $object';
9354 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9355 '@phan-var-force Product $object';
9356 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9357 } else {
9358 $result = $object->fetch($value);
9359 }
9360 if ($result > 0) {
9361 if ($object->element === 'product') {
9362 '@phan-var-force Product $object';
9363 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9364 if (isset($val['get_name_url_params'])) {
9365 $get_name_url_params = explode(':', $val['get_name_url_params']);
9366 if (!empty($get_name_url_params)) {
9367 $param_num_max = count($get_name_url_param_arr) - 1;
9368 foreach ($get_name_url_params as $param_num => $param_value) {
9369 if ($param_num > $param_num_max) {
9370 break;
9371 }
9372 $get_name_url_param_arr[$param_num] = $param_value;
9373 }
9374 }
9375 }
9376
9380 $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]);
9381 } else {
9382 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9383 }
9384 } else {
9385 $value = '';
9386 }
9387 }
9388 } else {
9389 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9390 return 'Error bad setup of extrafield';
9391 }
9392 } else {
9393 $value = '';
9394 }
9395 } elseif ($type == 'password') {
9396 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9397 //$value = preg_replace('/./i', '*', $value);
9398 } elseif ($type == 'array') {
9399 if (is_array($value)) {
9400 $value = implode('<br>', $value);
9401 } else {
9402 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9403 return 'Error unexpected result from code evaluation';
9404 }
9405 } else { // text|html|varchar
9406 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9407 $value = str_replace(',', "\n", $value);
9408 }
9409 $value = dol_htmlentitiesbr((string) $value);
9410 }
9411
9412 //print $type.'-'.$size.'-'.$value;
9413 $out = $value;
9414
9415 return is_null($out) ? '' : $out;
9416 }
9417
9424 public function clearFieldError($fieldKey)
9425 {
9426 $this->error = '';
9427 unset($this->validateFieldsErrors[$fieldKey]);
9428 }
9429
9437 public function setFieldError($fieldKey, $msg = '')
9438 {
9439 global $langs;
9440 if (empty($msg)) {
9441 $msg = $langs->trans("UnknownError");
9442 }
9443
9444 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9445 }
9446
9453 public function getFieldError($fieldKey)
9454 {
9455 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9456 return $this->validateFieldsErrors[$fieldKey];
9457 }
9458 return '';
9459 }
9460
9469 public function validateField($fields, $fieldKey, $fieldValue)
9470 {
9471 global $langs;
9472
9473 if (!class_exists('Validate')) {
9474 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9475 }
9476
9477 $this->clearFieldError($fieldKey);
9478
9479 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9480 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9481 return false;
9482 }
9483
9484 $val = $fields[$fieldKey];
9485
9486 $param = array();
9487 $param['options'] = array();
9488 $type = $val['type'];
9489
9490 $required = false;
9491 if (isset($val['notnull']) && $val['notnull'] === 1) {
9492 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9493 $required = true;
9494 }
9495
9496 $maxSize = 0;
9497 $minSize = 0;
9498
9499 //
9500 // PREPARE Elements
9501 //
9502 $reg = array();
9503
9504 // Convert var to be able to share same code than showOutputField of extrafields
9505 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9506 $type = 'varchar'; // convert varchar(xx) int varchar
9507 $maxSize = (int) $reg[1];
9508 } elseif (preg_match('/varchar/', $type)) {
9509 $type = 'varchar'; // convert varchar(xx) int varchar
9510 }
9511
9512 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9513 $type = 'select';
9514 }
9515
9516 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9517 $type = 'link';
9518 }
9519
9520 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9521 $param['options'] = $val['arrayofkeyval'];
9522 }
9523
9524 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9525 $type = 'link';
9526 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9527 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9528 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9529 $type = 'sellist';
9530 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9531 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9532 $type = 'sellist';
9533 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9534 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9535 $type = 'sellist';
9536 }
9537
9538 //
9539 // TEST Value
9540 //
9541
9542 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9543 $validate = new Validate($this->db, $langs);
9544
9545
9546 // little trick : to perform tests with good performances sort tests by quick to low
9547
9548 //
9549 // COMMON TESTS
9550 //
9551
9552 // Required test and empty value
9553 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9554 $this->setFieldError($fieldKey, $validate->error);
9555 return false;
9556 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9557 // if no value sent and the field is not mandatory, no need to perform tests
9558 return true;
9559 }
9560
9561 // MAX Size test
9562 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9563 $this->setFieldError($fieldKey, $validate->error);
9564 return false;
9565 }
9566
9567 // MIN Size test
9568 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9569 $this->setFieldError($fieldKey, $validate->error);
9570 return false;
9571 }
9572
9573 //
9574 // TESTS for TYPE
9575 //
9576
9577 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9578 if (!$validate->isTimestamp($fieldValue)) {
9579 $this->setFieldError($fieldKey, $validate->error);
9580 return false;
9581 } else {
9582 return true;
9583 }
9584 } elseif ($type == 'duration') {
9585 if (!$validate->isDuration($fieldValue)) {
9586 $this->setFieldError($fieldKey, $validate->error);
9587 return false;
9588 } else {
9589 return true;
9590 }
9591 } elseif (in_array($type, array('double', 'real', 'price'))) {
9592 // is numeric
9593 if (!$validate->isNumeric($fieldValue)) {
9594 $this->setFieldError($fieldKey, $validate->error);
9595 return false;
9596 } else {
9597 return true;
9598 }
9599 } elseif ($type == 'boolean') {
9600 if (!$validate->isBool($fieldValue)) {
9601 $this->setFieldError($fieldKey, $validate->error);
9602 return false;
9603 } else {
9604 return true;
9605 }
9606 } elseif ($type == 'mail' || $type == 'email') {
9607 if (!$validate->isEmail($fieldValue)) {
9608 $this->setFieldError($fieldKey, $validate->error);
9609 return false;
9610 }
9611 } elseif ($type == 'url') {
9612 if (!$validate->isUrl($fieldValue)) {
9613 $this->setFieldError($fieldKey, $validate->error);
9614 return false;
9615 } else {
9616 return true;
9617 }
9618 } elseif ($type == 'phone') {
9619 if (!$validate->isPhone($fieldValue)) {
9620 $this->setFieldError($fieldKey, $validate->error);
9621 return false;
9622 } else {
9623 return true;
9624 }
9625 } elseif ($type == 'select' || $type == 'radio') {
9626 if (!isset($param['options'][$fieldValue])) {
9627 $this->error = $langs->trans('RequireValidValue');
9628 return false;
9629 } else {
9630 return true;
9631 }
9632 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9633 $param_list = array_keys($param['options']);
9634 $InfoFieldList = explode(":", $param_list[0]);
9635 $value_arr = explode(',', $fieldValue);
9636
9637 $selectkey = "rowid";
9638 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9639 $selectkey = $InfoFieldList[2];
9640 }
9641
9642 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9643 $this->setFieldError($fieldKey, $validate->error);
9644 return false;
9645 } else {
9646 return true;
9647 }
9648 } elseif ($type == 'link') {
9649 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9650 $InfoFieldList = explode(":", $param_list[0]);
9651 $classname = $InfoFieldList[0];
9652 $classpath = $InfoFieldList[1];
9653 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9654 $lastIsFetchableError = $validate->error;
9655
9656 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9657 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9658 return true;
9659 }
9660
9661 $this->setFieldError($fieldKey, $lastIsFetchableError);
9662 return false;
9663 } else {
9664 return true;
9665 }
9666 }
9667
9668 // if no test failed all is ok
9669 return true;
9670 }
9671
9685 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9686 {
9687 global $db, $conf, $langs, $action, $form, $hookmanager;
9688 global $objectoffield;
9689
9690 if (!is_object($form)) {
9691 $form = new Form($db);
9692 }
9693 if (!is_object($extrafields)) {
9694 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9695 return 'Bad parameter extrafields for showOptionals';
9696 }
9697 if (!is_array($extrafields->attributes[$this->table_element])) {
9698 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9699 }
9700
9701 // Ensure $objectoffield is available for dol_eval visibility formulas.
9702 $objectoffield = $this;
9703
9704 $out = '';
9705
9706 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9707 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9708 $hookResPrint = $hookmanager->resPrint;
9709
9710 if (empty($reshook)) {
9711 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) {
9712 $out .= "\n";
9713 $out .= '<!-- commonobject:showOptionals --> ';
9714 $out .= "\n";
9715
9716 $nbofextrafieldsshown = 0;
9717 $e = 0; // var to manage the modulo (odd/even)
9718
9719 $lastseparatorkeyfound = '';
9720 $extrafields_collapse_num = '';
9721 $extrafields_collapse_num_old = '';
9722 $i = 0;
9723
9724 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9725 $i++;
9726
9727 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9728 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9729 continue;
9730 }
9731
9732 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9733 $enabled = 1;
9734 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9735 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9736 }
9737 if (empty($enabled)) {
9738 continue;
9739 }
9740
9741 $visibility = 1;
9742 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9743 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9744 }
9745
9746 $perms = 1;
9747 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9748 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9749 }
9750
9751 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9752 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9753 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9754 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9755 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9756 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9757 $ef_name = 'options_' . $key;
9758 $ef_value = $this->array_options[$ef_name] ?? '';
9759 $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).
9760 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9761 } elseif ($mode == 'view' && empty($visibility)) {
9762 continue;
9763 }
9764 if (empty($perms)) {
9765 continue;
9766 }
9767
9768 // Load language if required
9769 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9770 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9771 }
9772
9773 $colspan = 0;
9774 $value = null;
9775 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9776 if (array_key_exists('cols', $params)) {
9777 $colspan = $params['cols'];
9778 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9779 $reg = array();
9780 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9781 $colspan = $reg[1];
9782 } else {
9783 $colspan = $params['colspan'];
9784 }
9785 }
9786 }
9787 $colspan = intval($colspan);
9788
9789 switch ($mode) {
9790 case "view":
9791 $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
9792 break;
9793 case "create":
9794 case "edit":
9795 // We get the value of property found with GETPOST so it takes into account:
9796 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9797 $check = 'alphanohtml';
9798 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9799 $check = 'restricthtml';
9800 }
9801 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9802 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9803 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9804 if (is_array($getposttemp)) {
9805 // $getposttemp is an array but following code expects a comma separated string
9806 $value = implode(",", $getposttemp);
9807 } else {
9808 $value = $getposttemp;
9809 }
9810 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9811 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9812 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('varchar', 'char'))) {
9813 $value = (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] !== '') ? $this->array_options["options_".$key] : '';
9814 } else {
9815 $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.
9816 }
9817 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9818 break;
9819 }
9820
9821 $nbofextrafieldsshown++;
9822
9823 // Output value of the current field
9824 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9825 $extrafields_collapse_num = $key;
9826 /*
9827 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9828 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9829 $extrafield_param_list = array_keys($extrafield_param['options']);
9830
9831 if (count($extrafield_param_list) > 0) {
9832 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9833
9834 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9835 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9836 $extrafields_collapse_num = $key;
9837 }
9838 }
9839 }
9840 */
9841
9842 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9843 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9844
9845 $lastseparatorkeyfound = $key;
9846 } else {
9847 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9848
9849 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9850 $csstyle = '';
9851 if (is_array($params) && count($params) > 0) {
9852 if (array_key_exists('class', $params)) {
9853 $class .= $params['class'].' ';
9854 }
9855 if (array_key_exists('style', $params)) {
9856 $csstyle = $params['style'];
9857 }
9858 }
9859
9860 // add html5 elements
9861 $domData = ' data-element="extrafield"';
9862 $domData .= ' data-targetelement="'.$this->element.'"';
9863 $domData .= ' data-targetid="'.$this->id.'"';
9864
9865 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9866 if ($display_type == 'card') {
9867 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9868 $colspan = 0;
9869 }
9870
9871 if ($action == 'selectlines') {
9872 $colspan++;
9873 }
9874 }
9875
9876 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9877 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9878 $force_values_on_change_company = (
9879 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9880 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9881 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9882 );
9883 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9884 $value = $this->array_options['options_'.$key] ?? $value;
9885 }
9886
9887 // Convert date into timestamp format (value in memory must be a timestamp)
9888 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9889 $datenotinstring = null;
9890 if (array_key_exists('options_'.$key, $this->array_options)) {
9891 $datenotinstring = $this->array_options['options_'.$key];
9892 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9893 $datenotinstring = $this->db->jdate($datenotinstring);
9894 }
9895 }
9896 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9897 $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;
9898 }
9899 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9900 $datenotinstring = null;
9901 if (array_key_exists('options_'.$key, $this->array_options)) {
9902 $datenotinstring = $this->array_options['options_'.$key];
9903 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9904 $datenotinstring = $this->db->jdate($datenotinstring);
9905 }
9906 }
9907 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9908 $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;
9909 }
9910 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9911 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9912 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9913 $value = price2num($value);
9914 } elseif (isset($this->array_options['options_'.$key])) {
9915 $value = $this->array_options['options_'.$key];
9916 }
9917 }
9918
9919 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9920 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9921 if ($action == 'create' || $mode == 'create') {
9922 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9923 }
9924 }
9925
9926 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9927 if ($action == 'create') {
9928 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9929 }
9930 }
9931
9932
9933 $labeltoshow = $langs->trans($label);
9934 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9935 if ($display_type == 'card') {
9936 $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.' >';
9937 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9938 $out .= '<td></td>';
9939 }
9940 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9941 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9942 $out .= ' tdtop';
9943 }
9944 } elseif ($display_type == 'line') {
9945 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9946 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9947 }
9948 //$out .= "titlefield";
9949 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9950 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9951 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9952 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9953 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9954 $out .= ' fieldrequired';
9955 }
9956 }
9957 $out .= '">';
9958 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9959 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9960 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9961 } else {
9962 $out .= $labeltoshow;
9963 }
9964 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9965 $out .= '&nbsp;<span style="color: red">*</span>';
9966 }
9967 } else {
9968 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9969 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9970 } else {
9971 $out .= $labeltoshow;
9972 }
9973 }
9974
9975 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9976
9977 // Second column
9978 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9979 if ($display_type == 'card') {
9980 // 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
9981 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9982 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9983 $out .= '>';
9984 } elseif ($display_type == 'line') {
9985 $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].'">';
9986 }
9987
9988 switch ($mode) {
9989 case "view":
9990 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9991 break;
9992 case "create":
9993 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,ip,password'));
9994 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9995 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9996 }
9997 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9998 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9999 break;
10000 case "edit":
10001 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
10002 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
10003 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
10004 }
10005 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
10006 break;
10007 }
10008
10009 $out .= ($display_type == 'card' ? '</td>' : '</div>');
10010 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
10011 $e++;
10012 }
10013 }
10014 $out .= "\n";
10015 // Add code to manage list depending on others
10016 if (!empty($conf->use_javascript_ajax)) {
10017 $out .= $this->getJSListDependancies();
10018 }
10019
10020 $out .= '<!-- commonobject:showOptionals end --> '."\n";
10021
10022 if (empty($nbofextrafieldsshown)) {
10023 $out = '';
10024 }
10025 }
10026 }
10027
10028 $out .= $hookResPrint;
10029
10030 return $out;
10031 }
10032
10037 public function getJSListDependancies($type = '_extra')
10038 {
10039 $out = '
10040 <script nonce="'.getNonce().'">
10041 jQuery(document).ready(function() {
10042 function showOptions'.$type.'(child_list, parent_list, orig_select)
10043 {
10044 var val = $("select[name=\""+parent_list+"\"]").val();
10045 var parentVal = parent_list + ":" + val;
10046 if(typeof val == "string"){
10047 if(val != "") {
10048 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
10049 $("select[name=\""+child_list+"\"] option[parent]").remove();
10050 $("select[name=\""+child_list+"\"]").append(options);
10051 } else {
10052 var options = orig_select.find("option[parent]").clone();
10053 $("select[name=\""+child_list+"\"] option[parent]").remove();
10054 $("select[name=\""+child_list+"\"]").append(options);
10055 }
10056 } else if(val > 0) {
10057 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
10058 $("select[name=\""+child_list+"\"] option[parent]").remove();
10059 $("select[name=\""+child_list+"\"]").append(options);
10060 } else {
10061 var options = orig_select.find("option[parent]").clone();
10062 $("select[name=\""+child_list+"\"] option[parent]").remove();
10063 $("select[name=\""+child_list+"\"]").append(options);
10064 }
10065 }
10066 function setListDependencies'.$type.'() {
10067 jQuery("select option[parent]").parent().each(function() {
10068 var orig_select = {};
10069 var child_list = $(this).attr("name");
10070 orig_select[child_list] = $(this).clone();
10071 var parent = $(this).find("option[parent]:first").attr("parent");
10072 var infos = parent.split(":");
10073 var parent_list = infos[0];
10074
10075 //Hide daughters lists
10076 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
10077 $("#"+child_list).hide();
10078 //Show mother lists
10079 } else if ($("#"+parent_list).val() != 0){
10080 $("#"+parent_list).show();
10081 }
10082 //Show the child list if the parent list value is selected
10083 $("select[name=\""+parent_list+"\"]").click(function() {
10084 if ($(this).val() != 0){
10085 $("#"+child_list).show()
10086 }
10087 });
10088
10089 //When we change parent list
10090 $("select[name=\""+parent_list+"\"]").change(function() {
10091 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
10092 //Select the value 0 on child list after a change on the parent list
10093 $("#"+child_list).val(0).trigger("change");
10094 //Hide child lists if the parent value is set to 0
10095 if ($(this).val() == 0){
10096 $("#"+child_list).hide();
10097 }
10098 });
10099 });
10100 }
10101
10102 setListDependencies'.$type.'();
10103 });
10104 </script>'."\n";
10105 return $out;
10106 }
10107
10113 public function getRights()
10114 {
10115 global $user;
10116
10117 $module = empty($this->module) ? '' : $this->module;
10118 $element = $this->element;
10119
10120 if ($element == 'facturerec') {
10121 $element = 'facture';
10122 } elseif ($element == 'invoice_supplier_rec') {
10123 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
10124 } elseif ($module && $user->hasRight($module, $element)) {
10125 // for modules built with ModuleBuilder
10126 return $user->hasRight($module, $element);
10127 }
10128
10129 return isset($user->rights->$element) ? $user->rights->$element : null;
10130 }
10131
10144 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10145 {
10146 global $hookmanager;
10147
10148 $parameters = array(
10149 'origin_id' => $origin_id,
10150 'dest_id' => $dest_id,
10151 'tables' => $tables,
10152 );
10153 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
10154 if ($reshook) {
10155 return true; // replacement code
10156 } elseif ($reshook < 0) {
10157 return $ignoreerrors === 1; // failure
10158 } // reshook = 0 => execute normal code
10159
10160 foreach ($tables as $table) {
10161 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
10162
10163 if (!$dbs->query($sql)) {
10164 if ($ignoreerrors) {
10165 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.
10166 }
10167 //$this->errors = $db->lasterror();
10168 return false;
10169 }
10170 }
10171
10172 return true;
10173 }
10174
10187 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10188 {
10189 foreach ($tables as $table) {
10190 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
10191
10192 if (!$dbs->query($sql)) {
10193 if ($ignoreerrors) {
10194 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.
10195 }
10196 //$this->errors = $db->lasterror();
10197 return false;
10198 }
10199 }
10200
10201 return true;
10202 }
10203
10218 public static function commonReplaceContact(DoliDB $dbs, $origin_id, $dest_id, array $tables, $fieldname = 'fk_socpeople', $ignoreerrors = 0)
10219 {
10220 global $hookmanager;
10221
10222 // Table and column names are concatenated into the SQL, so they are validated as a defence in
10223 // depth: this method is public and static, hence callable from any module.
10224 if (!preg_match('/^[a-z0-9_]+$/', $fieldname)) {
10225 dol_syslog(__METHOD__.' Refused an invalid column name: '.$fieldname, LOG_ERR);
10226 return false;
10227 }
10228
10229 $parameters = array(
10230 'origin_id' => $origin_id,
10231 'dest_id' => $dest_id,
10232 'tables' => $tables,
10233 'fieldname' => $fieldname,
10234 );
10235 $reshook = $hookmanager->executeHooks('commonReplaceContact', $parameters);
10236 if ($reshook > 0) {
10237 return true; // replacement code
10238 } elseif ($reshook < 0) {
10239 return $ignoreerrors === 1; // failure
10240 } // reshook = 0 => execute normal code
10241
10242 foreach ($tables as $table) {
10243 if (!preg_match('/^[a-z0-9_]+$/', $table)) {
10244 dol_syslog(__METHOD__.' Refused an invalid table name: '.$table, LOG_ERR);
10245 return false;
10246 }
10247
10248 $sanitizedtable = $dbs->sanitize($table);
10249 $sanitizedfieldname = $dbs->sanitize($fieldname);
10250
10251 $sql = "UPDATE ".$dbs->prefix().$sanitizedtable;
10252 $sql .= " SET ".$sanitizedfieldname." = ".((int) $dest_id);
10253 $sql .= " WHERE ".$sanitizedfieldname." = ".((int) $origin_id);
10254
10255 if (!$dbs->query($sql)) {
10256 if ($ignoreerrors) {
10257 return true;
10258 }
10259 return false;
10260 }
10261 }
10262
10263 return true;
10264 }
10265
10279 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0, $qty = 0)
10280 {
10281 global $conf;
10282
10283 $buyPrice = 0;
10284
10285 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10286 // When ForceBuyingPriceIfNull is set
10287 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10288 } else {
10289 // Get cost price for margin calculation
10290 if (!empty($fk_product) && $fk_product > 0) {
10291 $result = 0;
10292 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10293 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10294 $product = new Product($this->db);
10295 $result = $product->fetch($fk_product);
10296 if ($result <= 0) {
10297 $this->errors[] = 'ErrorProductIdDoesNotExists';
10298 return -1;
10299 }
10300 if ($product->cost_price > 0) {
10301 $buyPrice = $product->cost_price;
10302 } elseif ($product->pmp > 0) {
10303 $buyPrice = $product->pmp;
10304 }
10305 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10306 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10307 $product = new Product($this->db);
10308 $result = $product->fetch($fk_product);
10309 if ($result <= 0) {
10310 $this->errors[] = 'ErrorProductIdDoesNotExists';
10311 return -1;
10312 }
10313 if ($product->pmp > 0) {
10314 $buyPrice = $product->pmp;
10315 }
10316 }
10317
10318 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10319 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10320 $productFournisseur = new ProductFournisseur($this->db);
10321 $result = $productFournisseur->find_min_price_product_fournisseur($fk_product, $qty);
10322 if ($result == 0 && $qty > 0) {
10323 // No supplier price defined for such a low quantity, fall back on the lowest known price
10324 $result = $productFournisseur->find_min_price_product_fournisseur($fk_product);
10325 }
10326 if ($result > 0) {
10327 $buyPrice = $productFournisseur->fourn_unitprice;
10328 } elseif ($result < 0) {
10329 $this->errors[] = $productFournisseur->error;
10330 return -2;
10331 }
10332 }
10333 }
10334 }
10335
10336 return (float) $buyPrice;
10337 }
10338
10346 public function getDataToShowPhoto($modulepart, $imagesize)
10347 {
10348 // See getDataToShowPhoto() implemented by Product for example.
10349 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10350 }
10351
10352
10353 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10373 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')
10374 {
10375 // phpcs:enable
10376 global $user, $langs;
10377
10378 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10379 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10380 include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
10381
10382 $sortfield = 'position_name';
10383 $sortorder = 'asc';
10384
10385 $dir = $sdir.'/';
10386 $pdir = '/';
10387
10388 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10389 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10390
10391 // For backward compatibility
10392 if ($modulepart == 'product') {
10393 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10394 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10395 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10396 }
10397 }
10398 if ($modulepart == 'category') {
10399 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10400 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10401 }
10402
10403 // Defined relative dir to DOL_DATA_ROOT
10404 $relativedir = '';
10405 if ($dir) {
10406 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10407 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10408 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10409 }
10410
10411 $dirthumb = $dir.'thumbs/';
10412 $pdirthumb = $pdir.'thumbs/';
10413
10414 $return = '<!-- Photo -->'."\n";
10415 $nbphoto = 0;
10416
10417 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10418
10419 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10420 {
10421 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10422 $filearray=array_merge($filearray, $filearrayold);
10423 }*/
10424
10425 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10426 '@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';
10427
10428 $useLinkPathPhoto = false;
10429
10430 if (getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO')) {
10431 $link = new Link($this->db);
10432 $links = array();
10433 $link->fetchAll($links, 'product', $this->id);
10434
10435 if (count($links) > 0) {
10436 $useLinkPathPhoto = true;
10437 }
10438
10439 if ($useLinkPathPhoto) {
10440 // Start table or div based on nbbyrow
10441 if ($nbbyrow > 0) {
10442 $return .= '<table class="valigntop center centpercent" style="border:0; padding:2px; border-spacing:2px; border-collapse: separate;">';
10443 }
10444
10445 $i = 0;
10446 foreach ($links as $link) {
10447 if (!empty($link->url) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $link->url)) {
10448 $url = dol_escape_htmltag($link->url);
10449 $i++;
10450 $nbphoto++;
10451
10452 if ($nbbyrow > 0 && ($i % $nbbyrow == 1)) {
10453 $return .= '<tr class="center valignmiddle">';
10454 }
10455
10456 if ($nbbyrow > 0) {
10457 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%" class="photo">';
10458 } else {
10459 $return .= '<div class="inline-block">';
10460 }
10461
10462 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.$url.'" title="External image">';
10463
10464 if ($nbbyrow > 0) {
10465 $return .= '</td>';
10466 if ($i % $nbbyrow == 0) {
10467 $return .= '</tr>';
10468 }
10469 } else {
10470 $return .= '</div>';
10471 }
10472
10473 if ($nbmax && $nbphoto >= $nbmax) {
10474 break;
10475 }
10476 }
10477 }
10478
10479 if ($nbbyrow > 0) {
10480 while ($i % $nbbyrow) {
10481 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10482 $i++;
10483 }
10484 if ($nbphoto) {
10485 $return .= '</table>';
10486 }
10487 }
10488 }
10489 }
10490
10491 if (count($filearray)) {
10492 if ($sortfield && $sortorder) {
10493 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10494 }
10495
10496 foreach ($filearray as $key => $val) {
10497 $photo = '';
10498 $file = $val['name'];
10499
10500 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10501 if (image_format_supported($file) >= 0) {
10502 $nbphoto++;
10503 $photo = $file;
10504 $viewfilename = $file;
10505
10506 if ($size == 1 || $size == 'small') { // Format vignette
10507 // Find name of thumb file
10508 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10509 if (!dol_is_file($dirthumb.$photo_vignette)) {
10510 // The thumb does not exists, so we will use the original file
10511 $dirthumb = $dir;
10512 $pdirthumb = $pdir;
10513 $photo_vignette = basename($file);
10514 }
10515
10516 // Get filesize of original file
10517 $imgarray = dol_getImageSize($dir.$photo);
10518
10519 if ($nbbyrow > 0) {
10520 if ($nbphoto == 1) {
10521 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10522 }
10523
10524 if ($nbphoto % $nbbyrow == 1) {
10525 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10526 }
10527 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10528 } elseif ($nbbyrow < 0) {
10529 $return .= '<div class="inline-block">'."\n";
10530 }
10531
10532 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10533 if (empty($nolink)) {
10534 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10535 if ($urladvanced) {
10536 $return .= '<a href="'.$urladvanced.'">';
10537 } else {
10538 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10539 }
10540 }
10541
10542 // Show image (width height=$maxHeight)
10543 // If thumb file available and image source is too large, we use the thumb, otherwise we use the original photo
10544 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10545 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10546 if ($overwritetitle) {
10547 if (is_numeric($overwritetitle)) {
10548 $alt = '';
10549 } else {
10550 $alt = $overwritetitle;
10551 }
10552 }
10553 if (empty($cache) && !empty($val['label'])) {
10554 // label is md5 of file
10555 // use it in url to say we want to cache this version of the file
10556 $cache = $val['label'];
10557 }
10558 if ($usesharelink) {
10559 if (array_key_exists('share', $val) && $val['share']) {
10560 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10561 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10562 $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).'">';
10563 } else {
10564 $return .= '<!-- Show original file -->';
10565 $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).'">';
10566 }
10567 } else {
10568 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10569 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10570 }
10571 } else {
10572 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10573 $return .= '<!-- Show thumb -->';
10574 $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).'">';
10575 } else {
10576 $return .= '<!-- Show original file -->';
10577 $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).'">';
10578 }
10579 }
10580
10581 if (empty($nolink)) {
10582 $return .= '</a>';
10583 }
10584
10585 if ($showfilename) {
10586 $return .= '<br>'.$viewfilename;
10587 }
10588 if ($showaction) {
10589 $return .= '<br>';
10590 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10591 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10592 $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>';
10593 }
10594 // Special case for product
10595 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10596 // Link to resize
10597 $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; ';
10598
10599 // Link to delete
10600 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10601 $return .= img_delete().'</a>';
10602 }
10603 }
10604 $return .= "\n";
10605
10606 if ($nbbyrow > 0) {
10607 $return .= '</td>';
10608 if (($nbphoto % $nbbyrow) == 0) {
10609 $return .= '</tr>';
10610 }
10611 } elseif ($nbbyrow < 0) {
10612 $return .= '</div>'."\n";
10613 }
10614 }
10615
10616 if (empty($size)) { // Format origine
10617 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10618
10619 if ($showfilename) {
10620 $return .= '<br>'.$viewfilename;
10621 }
10622 if ($showaction) {
10623 // Special case for product
10624 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10625 // Link to resize
10626 $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; ';
10627
10628 // Link to delete
10629 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10630 $return .= img_delete().'</a>';
10631 }
10632 }
10633 }
10634
10635 // On continue ou on arrete de boucler ?
10636 if ($nbmax && $nbphoto >= $nbmax) {
10637 break;
10638 }
10639 }
10640 }
10641
10642 if ($size == 1 || $size == 'small') {
10643 if ($nbbyrow > 0) {
10644 // Ferme tableau
10645 while ($nbphoto % $nbbyrow) {
10646 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10647 $nbphoto++;
10648 }
10649
10650 if ($nbphoto) {
10651 $return .= '</table>';
10652 }
10653 }
10654 }
10655 }
10656
10657 $this->nbphoto = $nbphoto;
10658
10659 return $return;
10660 }
10661
10662
10669 protected function isArray($info)
10670 {
10671 if (is_array($info)) {
10672 if (isset($info['type']) && $info['type'] == 'array') {
10673 return true;
10674 } else {
10675 return false;
10676 }
10677 }
10678 return false;
10679 }
10680
10687 public function isDate($info)
10688 {
10689 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10690 return true;
10691 }
10692 return false;
10693 }
10694
10701 public function isDuration($info)
10702 {
10703 if (is_array($info)) {
10704 if (isset($info['type']) && ($info['type'] == 'duration')) {
10705 return true;
10706 } else {
10707 return false;
10708 }
10709 } else {
10710 return false;
10711 }
10712 }
10713
10720 public function isInt($info)
10721 {
10722 if (is_array($info)) {
10723 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10724 return true;
10725 } else {
10726 return false;
10727 }
10728 } else {
10729 return false;
10730 }
10731 }
10732
10739 public function isFloat($info)
10740 {
10741 if (is_array($info)) {
10742 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10743 return true;
10744 } else {
10745 return false;
10746 }
10747 }
10748 return false;
10749 }
10750
10757 public function isText($info)
10758 {
10759 if (is_array($info)) {
10760 if (isset($info['type']) && $info['type'] == 'text') {
10761 return true;
10762 } else {
10763 return false;
10764 }
10765 }
10766 return false;
10767 }
10768
10775 protected function canBeNull($info)
10776 {
10777 if (is_array($info)) {
10778 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10779 return true;
10780 } else {
10781 return false;
10782 }
10783 }
10784 return true;
10785 }
10786
10793 protected function isForcedToNullIfZero($info)
10794 {
10795 if (is_array($info)) {
10796 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10797 return true;
10798 } else {
10799 return false;
10800 }
10801 }
10802 return false;
10803 }
10804
10811 protected function isIndex($info)
10812 {
10813 if (is_array($info)) {
10814 if (array_key_exists('index', $info) && $info['index'] == true) {
10815 return true;
10816 } else {
10817 return false;
10818 }
10819 }
10820 return false;
10821 }
10822
10823
10833 protected function setSaveQuery()
10834 {
10835 global $conf;
10836
10837 $queryarray = array();
10838 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10839 // Depending on field type ('datetime', ...)
10840 if ($this->isDate($info)) {
10841 if (empty($this->{$field})) {
10842 $queryarray[$field] = null;
10843 } else {
10844 $queryarray[$field] = $this->db->idate($this->{$field});
10845 }
10846 } elseif ($this->isDuration($info)) {
10847 // $this->{$field} may be null, '', 0, '0', 123, '123'
10848 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10849 if (!isset($this->{$field})) {
10850 if (!empty($info['default'])) {
10851 $queryarray[$field] = $info['default']; // From safe source @phan-suppress-current-line SqlInjection
10852 } else {
10853 $queryarray[$field] = 0;
10854 }
10855 } else {
10856 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10857 }
10858 } else {
10859 $queryarray[$field] = null;
10860 }
10861 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10862 if ($field == 'entity' && is_null($this->{$field})) {
10863 $queryarray[$field] = ((int) $conf->entity);
10864 } else {
10865 // $this->{$field} may be null, '', 0, '0', 123, '123'
10866 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10867 if (!isset($this->{$field})) {
10868 $queryarray[$field] = 0;
10869 } elseif ($this->isInt($info)) {
10870 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10871 } elseif ($this->isFloat($info)) {
10872 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10873 }
10874 } else {
10875 $queryarray[$field] = null;
10876 }
10877 }
10878 } else {
10879 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10880 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10881 $queryarray[$field] = $this->{$field}; // @phan-suppress-current-line SqlInjection
10882 }
10883
10884 if (is_array($info) && array_key_exists('type', $info) && !empty($info['type']) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10885 unset($queryarray[$field]);
10886 }
10887 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10888 $queryarray[$field] = null; // May force 0 to null
10889 }
10890 }
10891
10892 return $queryarray;
10893 }
10894
10901 public function setVarsFromFetchObj(&$obj)
10902 {
10903 global $db;
10904
10905 foreach ($this->fields as $field => $info) {
10906 if ($this->isDate($info)) {
10907 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') {
10908 $this->$field = '';
10909 } else {
10910 $this->$field = $db->jdate($obj->$field);
10911 }
10912 } elseif ($this->isInt($info)) {
10913 if ($field == 'rowid') {
10914 $this->id = (int) $obj->$field;
10915 } else {
10916 if ($this->isForcedToNullIfZero($info)) {
10917 if (empty($obj->$field)) {
10918 $this->$field = null;
10919 } else {
10920 $this->$field = (int) $obj->$field;
10921 }
10922 } else {
10923 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10924 $this->$field = (int) $obj->$field;
10925 } else {
10926 $this->$field = null;
10927 }
10928 }
10929 }
10930 } elseif ($this->isFloat($info)) {
10931 if ($this->isForcedToNullIfZero($info)) {
10932 if (empty($obj->$field)) {
10933 $this->$field = null;
10934 } else {
10935 $this->$field = (float) $obj->$field;
10936 }
10937 } else {
10938 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10939 $this->$field = (float) $obj->$field;
10940 } else {
10941 $this->$field = null;
10942 }
10943 }
10944 } else {
10945 $this->$field = isset($obj->$field) ? $obj->$field : null;
10946 }
10947 }
10948
10949 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10950 if (!isset($this->fields['ref']) && isset($this->id)) {
10951 $this->ref = (string) $this->id;
10952 }
10953 }
10954
10959 public function emtpyObjectVars()
10960 {
10961 foreach ($this->fields as $field => $arr) {
10962 $this->$field = null;
10963 }
10964 }
10965
10973 public function getFieldList($alias = '', $excludefields = array())
10974 {
10975 $keys = array_keys($this->fields);
10976 if (!empty($alias)) {
10977 $keys_with_alias = array();
10978 foreach ($keys as $fieldname) {
10979 if (!empty($excludefields)) {
10980 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10981 continue;
10982 }
10983 }
10984 $keys_with_alias[] = $this->db->sanitize($alias . '.' . $fieldname);
10985 }
10986 return implode(', ', $keys_with_alias);
10987 } else {
10988 return implode(', ', $keys);
10989 }
10990 }
10991
10999 protected function quote($value, $fieldsentry)
11000 {
11001 if (is_null($value)) {
11002 return 'NULL';
11003 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
11004 return price2num((string) $value);
11005 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
11006 return (int) $value;
11007 } elseif ($fieldsentry['type'] == 'boolean') {
11008 if ($value) {
11009 return 'true';
11010 } else {
11011 return 'false';
11012 }
11013 } else {
11014 return "'".$this->db->escape($value)."'";
11015 }
11016 }
11017
11018
11026 public function createCommon(User $user, $notrigger = 0)
11027 {
11028 global $conf;
11029 //global $langs; // Should be able to work with $langs loaded
11030
11031 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
11032
11033 $error = 0;
11034
11035 $now = dol_now();
11036
11037 $fieldvalues = $this->setSaveQuery();
11038
11039 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11040
11041 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
11042 $fieldvalues['date_creation'] = $this->db->idate($now);
11043 $this->date_creation = $this->db->idate($now);
11044 }
11045 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
11046 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
11047 $fieldvalues['fk_user_creat'] = $user->id;
11048 $this->fk_user_creat = $user->id;
11049 }
11050 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
11051 $fieldvalues['user_creation_id'] = $user->id;
11052 $this->user_creation_id = $user->id;
11053 }
11054 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
11055 // @phan-suppress-next-line PhanUndeclaredProperty
11056 $tmparray = dol_hash($this->pass, '0', 0, 1);
11057 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11058 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11059 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11060 }
11061 }
11062 if (array_key_exists('ref', $fieldvalues)) {
11063 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11064 }
11065 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
11066 $fieldvalues['tms'] = $this->db->idate($now);
11067 }
11068
11069 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
11070
11071 $sanitized_keys = array();
11072 $sanitized_values = array(); // Array to store string forged for SQL syntax
11073 foreach ($fieldvalues as $k => $v) {
11074 $sanitized_keys[$k] = $k; // Safe source @phan-suppress-current-line SqlInjection
11075 $value = $this->fields[$k];
11076 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11077 $sanitized_values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
11078 }
11079
11080 // Clean and check mandatory
11081 foreach ($sanitized_keys as $key) {
11082 if (!isset($this->fields[$key])) {
11083 continue;
11084 }
11085 $key_fields = $this->fields[$key];
11086
11087 // If field is an implicit foreign key field (so type = 'integer:...')
11088 if (preg_match('/^integer:/i', $key_fields['type']) && $sanitized_values[$key] == '-1') {
11089 $sanitized_values[$key] = '';
11090 }
11091 if (!empty($key_fields['foreignkey']) && $sanitized_values[$key] == '-1') {
11092 $sanitized_values[$key] = '';
11093 }
11094
11095 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']))) {
11096 $error++;
11097 global $langs;
11098 if (empty($langs)) {
11099 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
11100 $langs = new Translate('', $conf);
11101 $langs->setDefaultLang();
11102 $langs->load("errors");
11103 }
11104 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
11105 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
11106 }
11107
11108 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
11109 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'])) {
11110 $sanitized_values[$key] = $this->quote($key_fields['default'], $key_fields);
11111 }
11112
11113 // If field is an implicit foreign key field (so type = 'integer:...')
11114 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($sanitized_values[$key])) {
11115 if (isset($key_fields['default'])) {
11116 $sanitized_values[$key] = ((int) $key_fields['default']);
11117 } else {
11118 $sanitized_values[$key] = 'null';
11119 }
11120 }
11121 if (!empty($key_fields['foreignkey']) && empty($sanitized_values[$key])) {
11122 $sanitized_values[$key] = 'null';
11123 }
11124 }
11125
11126 if ($error) {
11127 return -1;
11128 }
11129
11130 $this->db->begin();
11131
11132 if (!$error) {
11133 $sql = "INSERT INTO ".$this->db->prefix().$this->db->sanitize($this->table_element);
11134 $sql .= " (".implode(", ", $sanitized_keys).')';
11135 $sql .= " VALUES (".implode(", ", $sanitized_values).")"; // $sanitized_values can contains 'abc' or 123
11136
11137 $res = $this->db->query($sql);
11138 if (!$res) {
11139 $error++;
11140 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
11141 $this->errors[] = "ErrorRefAlreadyExists";
11142 } else {
11143 $this->errors[] = $this->db->lasterror();
11144 }
11145 }
11146 }
11147
11148 if (!$error) {
11149 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
11150 }
11151
11152 // If we have a field ref with a default value of (PROV)
11153 if (!$error) {
11154 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
11155 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)') {
11156 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11157 $sql .= " SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
11158 $resqlupdate = $this->db->query($sql);
11159
11160 if ($resqlupdate === false) {
11161 $error++;
11162 $this->errors[] = $this->db->lasterror();
11163 } else {
11164 $this->ref = '(PROV'.$this->id.')';
11165 }
11166 }
11167 }
11168
11169 // Create extrafields
11170 if (!$error) {
11171 $result = $this->insertExtraFields();
11172 if ($result < 0) {
11173 $error++;
11174 }
11175 }
11176
11177 // Create lines
11178 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
11179 foreach ($this->lines as $line) {
11180 $keyforparent = $this->fk_element;
11181 $line->$keyforparent = $this->id;
11182
11183 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
11184 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
11185 if (!is_object($line)) {
11186 $line = (object) $line;
11187 }
11188
11189 $result = 0;
11190 if (method_exists($line, 'insert')) {
11191 $result = $line->insert($user, 1);
11192 } elseif (method_exists($line, 'create')) {
11193 $result = $line->create($user, 1);
11194 }
11195 if ($result < 0) {
11196 $this->error = $line->error;
11197 $this->db->rollback();
11198 return -1;
11199 }
11200 }
11201 }
11202
11203 // Triggers
11204 if (!$error && !$notrigger) {
11205 // Call triggers
11206 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
11207 $result = $this->call_trigger($triggerPrefix.'_CREATE', $user);
11208 if ($result < 0) {
11209 $error++;
11210 }
11211 // End call triggers
11212 }
11213
11214 // Commit or rollback
11215 if ($error) {
11216 $this->db->rollback();
11217 return -1;
11218 } else {
11219 $this->db->commit();
11220 return $this->id;
11221 }
11222 }
11223
11224
11234 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
11235 {
11236 if (empty($id) && empty($ref) && empty($morewhere)) {
11237 return -1;
11238 }
11239
11240 $fieldlist = $this->getFieldList('t');
11241 if (empty($fieldlist)) {
11242 return 0;
11243 }
11244
11245 $sql = "SELECT ".$this->db->sanitize($fieldlist, 0, 0, 1);
11246 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
11247
11248 if (!empty($id)) {
11249 $sql .= ' WHERE t.rowid = '.((int) $id);
11250 } elseif (!empty($ref)) {
11251 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
11252 } else {
11253 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
11254 }
11255 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
11256 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
11257 }
11258 if ($morewhere) {
11259 $sql .= $morewhere;
11260 }
11261 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
11262
11263 $res = $this->db->query($sql);
11264 if ($res) {
11265 $obj = $this->db->fetch_object($res);
11266 if ($obj) {
11267 $this->setVarsFromFetchObj($obj);
11268
11269 // Retrieve all extrafield
11270 // fetch optionals attributes and labels
11271 if (empty($noextrafields)) {
11272 $result = $this->fetch_optionals();
11273 if ($result < 0) {
11274 $this->error = $this->db->lasterror();
11275 $this->errors[] = $this->error;
11276 return -4;
11277 }
11278 }
11279
11280 return $this->id;
11281 } else {
11282 return 0;
11283 }
11284 } else {
11285 $this->error = $this->db->lasterror();
11286 $this->errors[] = $this->error;
11287 return -1;
11288 }
11289 }
11290
11298 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
11299 {
11300 $objectlineclassname = get_class($this).'Line';
11301 if (!class_exists($objectlineclassname)) {
11302 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
11303 return -1;
11304 }
11305
11306 $objectline = new $objectlineclassname($this->db);
11307 '@phan-var-force CommonObjectLine $objectline';
11308
11309 $sql = "SELECT ".$objectline->getFieldList('l');
11310 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($objectline->table_element)." as l";
11311 $sql .= " WHERE l.fk_".$this->db->sanitize($this->element)." = ".((int) $this->id);
11312 if ($morewhere) {
11313 $sql .= $morewhere;
11314 }
11315 if (isset($objectline->fields['position'])) {
11316 $sql .= $this->db->order('position', 'ASC');
11317 }
11318
11319 $resql = $this->db->query($sql);
11320 if ($resql) {
11321 $num_rows = $this->db->num_rows($resql);
11322 $i = 0;
11323 $this->lines = array();
11324 while ($i < $num_rows) {
11325 $obj = $this->db->fetch_object($resql);
11326 if ($obj) {
11327 $newline = new $objectlineclassname($this->db);
11328 '@phan-var-force CommonObjectLine $newline';
11329 $newline->setVarsFromFetchObj($obj);
11330
11331 // Load also extrafields for the line
11332 if (empty($noextrafields)) {
11333 $newline->fetch_optionals();
11334 }
11335
11336 $this->lines[] = $newline;
11337 }
11338 $i++;
11339 }
11340
11341 return 1;
11342 } else {
11343 $this->error = $this->db->lasterror();
11344 $this->errors[] = $this->error;
11345 return -1;
11346 }
11347 }
11348
11356 public function updateCommon(User $user, $notrigger = 0)
11357 {
11358 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11359
11360 $error = 0;
11361
11362 $now = dol_now();
11363
11364 // $this->oldcopy should have been set by the caller of update
11365 //if (empty($this->oldcopy)) {
11366 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11367 // $this->oldcopy = dol_clone($this, 2);
11368 //}
11369
11370 $fieldvalues = $this->setSaveQuery();
11371
11372 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11373
11374 if (array_key_exists('date_modification', $fieldvalues)) {
11375 $fieldvalues['date_modification'] = $this->db->idate($now);
11376 }
11377 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
11378 $fieldvalues['tms'] = $this->db->idate($now);
11379 }
11380 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11381 $fieldvalues['fk_user_modif'] = $user->id;
11382 }
11383 if (array_key_exists('user_modification_id', $fieldvalues)) {
11384 $fieldvalues['user_modification_id'] = $user->id;
11385 }
11386 // @phan-suppress-next-line PhanUndeclaredProperty
11387 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11388 // @phan-suppress-next-line PhanUndeclaredProperty
11389 $tmparray = dol_hash($this->pass, '0', 0, 1);
11390 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11391 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11392 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11393 }
11394 }
11395 if (array_key_exists('ref', $fieldvalues)) {
11396 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11397 }
11398
11399 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11400
11401 // Add quotes and escape on fields with type string
11402 $keys = array();
11403 $values = array();
11404 $sanitized_tmp = array();
11405 foreach ($fieldvalues as $k => $v) {
11406 $keys[$k] = $k;
11407 $value = $this->fields[$k];
11408 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11409 $values[$k] = $this->quote($v, $value);
11410 if ((!empty($value["type"]) && $value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11411 // Clean values for text with selectbox
11412 $v = preg_replace('/\s/', ',', $v);
11413 $v = preg_replace('/,+/', ',', $v);
11414 }
11415 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, SqlInjection
11416 $sanitized_tmp[] = $this->db->sanitize($k).'='.$this->quote($v, $this->fields[$k]);
11417 }
11418
11419 // Clean and check mandatory fields
11420 foreach ($keys as $key) {
11421 if (!empty($this->fields[$key]['type']) && preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11422 $values[$key] = ''; // This is an implicit foreign key field
11423 }
11424 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11425 $values[$key] = ''; // This is an explicit foreign key field
11426 }
11427
11428 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11429 /*
11430 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11431 {
11432 $error++;
11433 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11434 }*/
11435 }
11436
11437 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11438 $sql .= " SET ".implode(', ', $sanitized_tmp);
11439 $sql .= " WHERE rowid = ".((int) $this->id);
11440
11441 $this->db->begin();
11442
11443 if (!$error) {
11444 $res = $this->db->query($sql);
11445 if (!$res) {
11446 $error++;
11447 $this->errors[] = $this->db->lasterror();
11448 }
11449 }
11450
11451 // Update extrafield
11452 if (!$error) {
11453 $result = $this->insertExtraFields(); // This update extrafields
11454 if ($result < 0) {
11455 $error++;
11456 }
11457 }
11458
11459 // Triggers
11460 if (!$error && !$notrigger) {
11461 // Call triggers
11462 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
11463 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $user);
11464 if ($result < 0) {
11465 $error++;
11466 } //Do also here what you must do to rollback action if trigger fail
11467 // End call triggers
11468 }
11469
11470 // Commit or rollback
11471 if ($error) {
11472 $this->db->rollback();
11473 return -1;
11474 } else {
11475 $this->db->commit();
11476 return $this->id;
11477 }
11478 }
11479
11488 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11489 {
11490 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11491
11492 $error = 0;
11493
11494 $this->db->begin();
11495
11496 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11497 foreach ($this->childtables as $table) {
11498 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($table);
11499 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11500 $resql = $this->db->query($sql);
11501 if (!$resql) {
11502 $this->error = $this->db->lasterror();
11503 $this->errors[] = $this->error;
11504 $this->db->rollback();
11505 return -1;
11506 }
11507 }
11508 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11509 $objectisused = $this->isObjectUsed($this->id);
11510 if (!empty($objectisused)) {
11511 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11512 $this->error = 'ErrorRecordHasChildren';
11513 $this->errors[] = $this->error;
11514 $this->db->rollback();
11515 return 0;
11516 }
11517 }
11518
11519 // Delete cascade first
11520 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11521 foreach ($this->childtablesoncascade as $tabletodelete) {
11522 $deleteFromObject = explode(':', $tabletodelete, 4);
11523 if (count($deleteFromObject) >= 2) {
11524 $className = str_replace('@', '', $deleteFromObject[0]);
11525 $filePath = $deleteFromObject[1];
11526 $columnName = $deleteFromObject[2];
11527 $filter = '';
11528 if (!empty($deleteFromObject[3])) {
11529 $filter = $deleteFromObject[3];
11530 }
11531
11532 if (dol_include_once($filePath)) {
11533 $childObject = new $className($this->db);
11534 if (method_exists($childObject, 'deleteByParentField')) {
11535 '@phan-var-force CommonObject $childObject';
11536 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11537 if ($result < 0) {
11538 $error++;
11539 $this->errors[] = $childObject->error;
11540 break;
11541 }
11542 } else {
11543 $error++;
11544 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11545 break;
11546 }
11547 } else {
11548 $error++;
11549 $this->errors[] = 'Cannot include child class file '.$filePath;
11550 break;
11551 }
11552 } else {
11553 // Delete record in child table
11554 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($tabletodelete)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11555
11556 $resql = $this->db->query($sql);
11557 if (!$resql) {
11558 $error++;
11559 $this->error = $this->db->lasterror();
11560 $this->errors[] = $this->error;
11561 break;
11562 }
11563 }
11564 }
11565 }
11566
11567 if (!$error) {
11568 if (!$notrigger) {
11569 // Call triggers
11570 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
11571 $result = $this->call_trigger($triggerPrefix.'_DELETE', $user);
11572 if ($result < 0) {
11573 $error++;
11574 } // Do also here what you must do to rollback action if trigger fail
11575 // End call triggers
11576 }
11577 }
11578
11579 // Delete llx_ecm_files
11580 if (!$error) {
11581 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11582 if (!$res) {
11583 $error++;
11584 }
11585 }
11586
11587 if (!$error) {
11588 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11589 // For remove dir
11590 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11591 if (dol_is_dir($dir)) {
11592 if (!dol_delete_dir_recursive($dir)) {
11593 $this->errors[] = 'ErrorFailToDeleteDir';
11594 }
11595 }
11596 }
11597
11598 // Delete linked object
11599 $res = $this->deleteObjectLinked();
11600 if ($res < 0) {
11601 $error++;
11602 }
11603
11604 if (!$error && !empty($this->isextrafieldmanaged)) {
11605 $result = $this->deleteExtraFields();
11606 if ($result < 0) {
11607 $error++;
11608 }
11609 }
11610
11611 if (!$error) {
11612 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11613 $sql .= " WHERE rowid = ".((int) $this->id);
11614
11615 $resql = $this->db->query($sql);
11616 if (!$resql) {
11617 $error++;
11618 $this->errors[] = $this->db->lasterror();
11619 }
11620 }
11621
11622 // Commit or rollback
11623 if ($error) {
11624 $this->db->rollback();
11625 return -1;
11626 } else {
11627 $this->db->commit();
11628 return 1;
11629 }
11630 }
11631
11643 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11644 {
11645 global $user;
11646
11647 $error = 0;
11648 $deleted = 0;
11649
11650 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11651
11652 if (!empty($parentId) && !empty($parentField)) {
11653 if (empty($this->table_element)) {
11654 $this->error = 'Property table_element for object is not defined';
11655 $this->errors[] = $this->error;
11656 $error++;
11657 return -1;
11658 }
11659 if (!method_exists($this, 'fetch')) {
11660 $this->error = 'Method fetch for object is not defined';
11661 $this->errors[] = $this->error;
11662 $error++;
11663 return -1;
11664 }
11665 if (!method_exists($this, 'delete')) {
11666 $this->error = 'Method delete for object is not defined';
11667 $this->errors[] = $this->error;
11668 $error++;
11669 return -1;
11670 }
11671
11672 $this->db->begin();
11673
11674 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11675 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11676
11677 // Manage filter
11678 $errormessage = '';
11679 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11680 if ($errormessage) {
11681 $this->errors[] = $errormessage;
11682 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11683 return -1;
11684 }
11685
11686 $resql = $this->db->query($sql);
11687 if (!$resql) {
11688 $this->errors[] = $this->db->lasterror();
11689 $error++;
11690 } else {
11691 while ($obj = $this->db->fetch_object($resql)) {
11692 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11693 if ($result < 0) {
11694 $error++;
11695 $this->errors[] = $this->error;
11696 } else {
11697 $result = $this->delete($user); // @phpstan-ignore-line
11698 if ($result < 0) {
11699 $error++;
11700 $this->errors[] = $this->error;
11701 } else {
11702 $deleted++;
11703 }
11704 }
11705 }
11706 }
11707
11708 if (empty($error)) {
11709 $this->db->commit();
11710 return $deleted;
11711 } else {
11712 $this->error = implode(', ', $this->errors);
11713 $this->db->rollback();
11714 return $error * -1;
11715 }
11716 }
11717
11718 return $deleted;
11719 }
11720
11728 public function isLineOfObject($lineid)
11729 {
11730 if (!($this->id > 0) || empty($this->table_element_line) || empty($this->fk_element)) {
11731 return true;
11732 }
11733
11734 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
11735 $sql .= " WHERE rowid = ".((int) $lineid)." AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11736 $resql = $this->db->query($sql);
11737
11738 return ($resql && $this->db->num_rows($resql) > 0);
11739 }
11740
11749 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11750 {
11751 $error = 0;
11752
11753 $tmpforobjectclass = get_class($this);
11754 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11755
11756 if ($this->id > 0 && !empty($this->fk_element)) {
11757 // The line must belong to this object
11758 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
11759 $sql .= " WHERE rowid = ".((int) $idline)." AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11760 $resql = $this->db->query($sql);
11761 if (!$resql || !$this->db->num_rows($resql)) {
11762 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
11763 return -1;
11764 }
11765 }
11766
11767 $this->db->begin();
11768
11769 // Call trigger
11770 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11771 if ($result < 0) {
11772 $error++;
11773 }
11774 // End call triggers
11775
11776 if (empty($error)) {
11777 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
11778 $sql .= " WHERE rowid = ".((int) $idline);
11779
11780 $resql = $this->db->query($sql);
11781 if (!$resql) {
11782 $this->error = "Error ".$this->db->lasterror();
11783 $error++;
11784 }
11785 }
11786
11787 if (empty($error)) {
11788 // Remove extrafields
11789 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11790 '@phan-var-force CommonObjectLine $tmpobjectline';
11791 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11792 $tmpobjectline->id = $idline;
11793 $result = $tmpobjectline->deleteExtraFields();
11794 if ($result < 0) {
11795 $error++;
11796 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11797 }
11798 }
11799 }
11800
11801 if (empty($error)) {
11802 $this->db->commit();
11803 return 1;
11804 } else {
11805 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11806 $this->db->rollback();
11807 return -1;
11808 }
11809 }
11810
11811
11822 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11823 {
11824 $error = 0;
11825
11826 $this->db->begin();
11827
11828 $statusfield = 'status';
11829 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11830 $statusfield = 'fk_statut';
11831 }
11832
11833 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11834 $sql .= " SET ".$this->db->sanitize($statusfield)." = ".((int) $status);
11835 $sql .= " WHERE rowid = ".((int) $this->id);
11836
11837 if ($this->db->query($sql)) {
11838 if (!$error) {
11839 $this->oldcopy = clone $this;
11840 }
11841
11842 if (!$error && !$notrigger) {
11843 // Call trigger
11844 $result = $this->call_trigger($triggercode, $user);
11845 if ($result < 0) {
11846 $error++;
11847 }
11848 }
11849
11850 if (!$error) {
11851 $this->status = $status;
11852 if (property_exists($this, 'statut')) { // For backward compatibility
11853 $this->statut = $status;
11854 }
11855 $this->db->commit();
11856 return 1;
11857 } else {
11858 $this->db->rollback();
11859 return -1;
11860 }
11861 } else {
11862 $this->error = $this->db->error();
11863 $this->db->rollback();
11864 return -1;
11865 }
11866 }
11867
11874 public function initAsSpecimenCommon()
11875 {
11876 global $user;
11877
11878 $this->id = 0;
11879 $this->specimen = 1;
11880 $fields = array(
11881 'label' => 'This is label',
11882 'ref' => 'ABCD1234',
11883 'description' => 'This is a description',
11884 'qty' => 123.12,
11885 'note_public' => 'Public note',
11886 'note_private' => 'Private note',
11887 'date_creation' => (dol_now() - 3600 * 48),
11888 'date_modification' => (dol_now() - 3600 * 24),
11889 'fk_user_creat' => $user->id,
11890 'fk_user_modif' => $user->id,
11891 'date' => dol_now(),
11892 );
11893 foreach ($fields as $key => $value) {
11894 if (array_key_exists($key, $this->fields)) {
11895 $this->{$key} = $value; // @phpstan-ignore-line
11896 }
11897 }
11898
11899 // Force values to default values when known
11900 if (property_exists($this, 'fields')) {
11901 foreach ($this->fields as $key => $value) {
11902 // If fields are already set, do nothing
11903 if (array_key_exists($key, $fields)) {
11904 continue;
11905 }
11906
11907 if (!empty($value['default'])) {
11908 $this->$key = $value['default'];
11909 }
11910 }
11911 }
11912
11913 return 1;
11914 }
11915
11916
11917 /* Part for comments */
11918
11924 public function fetchComments()
11925 {
11926 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11927
11928 $comment = new Comment($this->db);
11929 $result = $comment->fetchAllFor($this->element, $this->id);
11930 if ($result < 0) {
11931 $this->setErrorsFromObject($comment);
11932 return -1;
11933 } else {
11934 $this->comments = $comment->comments;
11935 }
11936 return count($this->comments);
11937 }
11938
11944 public function getNbComments()
11945 {
11946 return count($this->comments);
11947 }
11948
11955 public function trimParameters($parameters)
11956 {
11957 if (!is_array($parameters)) {
11958 return;
11959 }
11960 foreach ($parameters as $parameter) {
11961 if (isset($this->$parameter)) {
11962 $this->$parameter = trim($this->$parameter);
11963 }
11964 }
11965 }
11966
11967 /* Part for categories/tags */
11968
11979 public function getCategoriesCommon($type_categ)
11980 {
11981 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11982
11983 // Get current categories
11984 $c = new Categorie($this->db);
11985 $existing = $c->containing($this->id, $type_categ, 'id');
11986
11987 return $existing;
11988 }
11989
12002 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
12003 {
12004 // Handle single category
12005 if (!is_array($categories)) {
12006 $categories = array($categories);
12007 }
12008
12009 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
12010
12011 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
12012
12013 if (empty($type_categ)) {
12014 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
12015 return -1;
12016 }
12017
12018 // Get current categories
12019 $c = new Categorie($this->db);
12020 $existing = $c->containing($this->id, $type_categ, 'id');
12021 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
12022 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
12023 if (!is_array($existing)) {
12024 $existing = array();
12025 }
12026 if ($remove_existing) {
12027 // Diff
12028 $to_del = array_diff($existing, $categories);
12029 $to_add = array_diff($categories, $existing);
12030 } else {
12031 $to_del = array(); // Nothing to delete
12032 $to_add = array_diff($categories, $existing);
12033 }
12034
12035 $error = 0;
12036 $ok = 0;
12037
12038 // Process
12039 foreach ($to_del as $del) {
12040 if ($c->fetch($del) > 0) {
12041 $result = $c->del_type($this, $type_categ);
12042 if ($result < 0) {
12043 $error++;
12044 $this->error = $c->error;
12045 $this->errors = $c->errors;
12046 break;
12047 } else {
12048 $ok += $result;
12049 }
12050 }
12051 }
12052 foreach ($to_add as $add) {
12053 if ($c->fetch($add) > 0) {
12054 $result = $c->add_type($this, $type_categ);
12055 if ($result < 0) {
12056 $error++;
12057 $this->error = $c->error;
12058 $this->errors = $c->errors;
12059 break;
12060 } else {
12061 $ok += $result;
12062 }
12063 }
12064 }
12065
12066 return $error ? (-1 * $error) : $ok;
12067 }
12068
12077 public function cloneCategories($fromId, $toId, $type = '')
12078 {
12079 $this->db->begin();
12080
12081 if (empty($type)) {
12082 $type = $this->table_element;
12083 }
12084
12085 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
12086 $categorystatic = new Categorie($this->db);
12087
12088 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
12089 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
12090
12091 $sql = "INSERT INTO ".$this->db->sanitize($tablename)." (fk_categorie, ".$this->db->sanitize($fkname).")";
12092 $sql .= " SELECT fk_categorie, ".((int) $toId)." FROM ".$this->db->sanitize($tablename);
12093 $sql .= " WHERE ".$this->db->sanitize($fkname)." = ".((int) $fromId);
12094
12095 if (!$this->db->query($sql)) {
12096 $this->error = $this->db->lasterror();
12097 $this->db->rollback();
12098 return -1;
12099 }
12100
12101 $this->db->commit();
12102 return 1;
12103 }
12104
12111 public function deleteEcmFiles($mode = 0)
12112 {
12113 global $conf;
12114
12115 $this->db->begin();
12116
12117 // Delete in database with mode 0
12118 if ($mode == 0) {
12119 switch ($this->element) {
12120 case 'propal':
12121 $element = 'propale';
12122 break;
12123 case 'product':
12124 $element = 'produit';
12125 break;
12126 case 'order_supplier':
12127 $element = 'fournisseur/commande';
12128 break;
12129 case 'invoice_supplier':
12130 // Special cases that need to use get_exdir to get real dir of object
12131 // In future, all object should use this to define path of documents.
12132 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
12133 break;
12134 case 'shipping':
12135 $element = 'expedition/sending';
12136 break;
12137 case 'task':
12138 case 'project_task':
12139 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
12140
12141 $project_result = $this->fetchProject();
12142 if ($project_result >= 0) {
12143 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
12144 }
12145 // no break
12146 case 'contrat':
12147 $element = 'contract';
12148 break;
12149 default:
12150 $element = $this->element;
12151 }
12152 '@phan-var-force string $element';
12153
12154 // Delete ecm_files_extrafields with mode 0 (using name)
12155 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
12156 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
12157 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
12158 $sql .= ")";
12159
12160 if (!$this->db->query($sql)) {
12161 $this->error = $this->db->lasterror();
12162 $this->db->rollback();
12163 return false;
12164 }
12165
12166 // Delete ecm_files with mode 0 (using name)
12167 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
12168 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
12169 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
12170
12171 if (!$this->db->query($sql)) {
12172 $this->error = $this->db->lasterror();
12173 $this->db->rollback();
12174 return false;
12175 }
12176 }
12177
12178 // Delete in database with mode 1
12179 if ($mode == 1) {
12180 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
12181 $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).")";
12182 $resql = $this->db->query($sql);
12183 if (!$resql) {
12184 $this->error = $this->db->lasterror();
12185 $this->db->rollback();
12186 return false;
12187 }
12188
12189 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
12190 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
12191 $resql = $this->db->query($sql);
12192 if (!$resql) {
12193 $this->error = $this->db->lasterror();
12194 $this->db->rollback();
12195 return false;
12196 }
12197 }
12198
12199 $this->db->commit();
12200 return true;
12201 }
12202
12211 public function checkActiveProductInLines($status = 'onsale')
12212 {
12213 global $langs;
12214
12215 if (isModEnabled('product') || isModEnabled('service')) {
12216 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
12217
12218 $ret = true;
12219 $tmpproduct = new Product($this->db);
12220 foreach ($this->lines as $line) {
12221 if ($line->fk_product > 0) {
12222 $tmpproduct->fetch($line->fk_product);
12223 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
12224 if (!$tmpproduct->$statustotest) {
12225 $langs->load('products');
12226 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
12227 $ret = false;
12228 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
12229 break;
12230 }
12231 }
12232 }
12233 if (!$ret) {
12234 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
12235 }
12236 return $ret;
12237 } else {
12238 return true;
12239 }
12240 }
12241
12248 public function setErrorWithoutLog($message)
12249 {
12250 $this->error = $message;
12251 $this->errors[] = $message;
12252 }
12253
12261 public function setErrorWithLog($message, $loglevel = LOG_ERR)
12262 {
12263 global $dolibarr_main_document_root;
12264
12265 $this->setErrorWithoutLog($message);
12266 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
12267 $file = str_replace($dolibarr_main_document_root, '', $trace[0]['file'] ?? 'file unknown');
12268 $line = $trace[0]['line'] ?? 'line unknown';
12269 $syslogMessage = sprintf('%s:%s %s', $file, $line, $message);
12270 dol_syslog($syslogMessage, $loglevel);
12271 }
12272}
$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
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition propal.php:280
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='', $placeholder='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:477
$c
Definition line.php:346
$object ref
Definition info.php:90
Class to manage members of a foundation.
Class to manage categories.
Class to manage comment.
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...
fetchWarehouse($force_warehouse_id=0)
Load the warehouse of object, from id $this->warehouse_id or $this->fk_warehouse, into this->warehous...
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.
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.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
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)
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
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.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0, $qty=0)
Get buy price to use for margin calculation.
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.
static commonReplaceContact(DoliDB $dbs, $origin_id, $dest_id, array $tables, $fieldname='fk_socpeople', $ignoreerrors=0)
Function used to replace a contact id with another one.
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.
sanitize($stringtosanitize, $allowsimplequote=0, $allowsequals=0, $allowsspace=0, $allowschars=1)
Sanitize a string for SQL forging.
Class to manage donations.
Definition don.class.php:41
Class to manage ECM files.
Class to manage warehouses.
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
if(! $sortfield) if(! $sortorder) $module
Definition list.php:193
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:253
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
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:65
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.
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.
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.
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.
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.
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.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
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_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.
getPictoForType($key, $morecss='')
Return the picto for a data type.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png, bmp and webp)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $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:141
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.