dolibarr 24.0.2
commonobject.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2015 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
5 * Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
6 * Copyright (C) 2011-2022 Philippe Grand <philippe.grand@atoo-net.com>
7 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
8 * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
10 * Copyright (C) 2015-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
11 * Copyright (C) 2016 Bahfir abbes <bafbes@gmail.com>
12 * Copyright (C) 2017 ATM Consulting <support@atm-consulting.fr>
13 * Copyright (C) 2017-2026 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2017 Rui Strecht <rui.strecht@aliartalentos.com>
15 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
16 * Copyright (C) 2018 Josep Lluís Amador <joseplluis@lliuretic.cat>
17 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
18 * Copyright (C) 2021 Grégory Blémand <gregory.blemand@atm-consulting.fr>
19 * Copyright (C) 2023 Lenin Rivas <lenin.rivas777@gmail.com>
20 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
21 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
22 * Copyright (C) 2025 Alexandre Janniaux <alexandre.janniaux@gmail.com>
23 * Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
24 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
25*
26 * This program is free software; you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation; either version 3 of the License, or
29 * (at your option) any later version.
30 *
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
35 *
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <https://www.gnu.org/licenses/>.
38 */
39
46require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
47require_once DOL_DOCUMENT_ROOT.'/core/class/commontrigger.class.php';
48
54abstract class CommonObject
55{
56 use DolDeprecationHandler;
57 use CommonTrigger;
58
62 public $module;
63
67 public $db;
68
72 public $id;
73
77 public $entity;
78
83 public $error;
84
88 public $errorhidden;
89
93 public $errors = array();
94
99 public $warning;
100
104 public $warnings = array();
105
109 private $validateFieldsErrors = array();
110
114 public $element;
115
120 public $fk_element;
121
127 public $element_for_permission;
128
132 public $table_element;
133
137 public $table_rowid;
138
142 public $table_element_line = '';
143
148 public $ismultientitymanaged;
149
153 public $import_key;
154
158 public $array_options = array();
159
160
239 public $fields = array();
240
246 public $array_languages = null; // Value is array() when load already tried
247
251 public $contacts_ids;
252
256 public $contacts_ids_internal;
257
261 public $linked_objects;
262
266 public $linkedObjectsIds;
267
271 public $linkedObjects;
272
276 private $linkedObjectsFullLoaded = array();
277
281 public $oldcopy;
282
286 public $oldref;
287
291 protected $table_ref_field = '';
292
296 public $restrictiononfksoc = 0;
297
298
299 // The following vars are used by some objects only.
300 // We keep these properties in CommonObject in order to provide common methods using them.
301
305 public $context = array();
306
310 public $actionmsg;
314 public $actionmsg2;
315
319 public $canvas;
320
325 public $project;
326
331 public $fk_project;
332
338 public $fk_projet;
339
344 public $contact;
345
350 public $contact_id;
351
356 public $thirdparty;
357
362 public $user;
363
368 public $product;
369
374 public $origin_type;
375
380 public $origin_id;
381
386
392 public $origin;
393
402 private $expedition;
403
409 private $livraison;
410
416 private $commandeFournisseur;
417
418
422 public $ref;
423
427 public $ref_ext;
428
432 public $ref_previous;
433
437 public $ref_next;
438
442 public $newref;
443
450 public $statut;
451
460 public $status;
461
466 public $country;
467
472 public $country_id;
473
478 public $country_code;
479
484 public $state;
485
490 public $state_id;
491
496 public $state_code;
497
502 public $region_id;
503
508 public $region_code;
509
514 public $region;
515
516
521 public $barcode_type;
522
527 public $barcode_type_code;
528
533 public $barcode_type_label;
534
539 public $barcode_type_coder;
540
545 public $mode_reglement_id;
546
551 public $cond_reglement_id;
552
556 public $demand_reason_id;
557
562 public $transport_mode_id;
563
571 private $cond_reglement; // Private to call DolDeprecationHandler
575 protected $depr_cond_reglement; // Internal value for deprecation
576
582 public $fk_delivery_address;
583
588 public $shipping_method_id;
589
594 public $shipping_method;
595
596 // Multicurrency
600 public $fk_multicurrency;
601
606 public $multicurrency_code;
607
612 public $multicurrency_tx;
613
617 public $multicurrency_total_ht;
618
622 public $multicurrency_total_tva;
623
627 public $multicurrency_total_localtax1; // not in database
628
632 public $multicurrency_total_localtax2; // not in database
633
637 public $multicurrency_total_ttc;
638
639
644 public $model_pdf;
645
650 public $last_main_doc;
651
657 public $fk_bank;
658
663 public $fk_account;
664
669 public $note_public;
670
675 public $note_private;
676
682 public $note;
683
688 public $total_ht;
689
694 public $total_tva;
695
700 public $total_localtax1;
701
706 public $total_localtax2;
707
712 public $total_ttc;
713
714
718 public $lines;
719
723 public $actiontypecode;
724
729 public $comments = array();
730
734 public $name;
735
739 public $lastname;
740
744 public $firstname;
745
750 public $civility_id;
751
755 public $civility_code;
756
757 // Dates
761 public $date_creation;
762
766 public $date_validation;
767
771 public $date_modification;
772
778 public $tms;
779
785 private $date_update;
786
790 public $date_cloture;
791
795 public $user_creation_id;
796
800 public $user_validation_id;
801
805 public $user_closing_id;
806
810 public $user_modification_id;
811
816 public $fk_user_creat;
817
822 public $fk_user_modif;
823
824
828 public $next_prev_filter;
829
833 public $specimen = 0;
834
838 public $sendtoid;
839
840
845 public $alreadypaid;
846
851 public $totalpaid;
852
857 public $totalpaid_multicurrency;
858
859
863 public $labelStatus = array();
864
868 public $labelStatusShort = array();
869
873 public $tpl;
874
875
879 public $showphoto_on_popup;
880
884 public $nb = array();
885
889 public $nbphoto;
890
894 public $output;
895
899 public $extraparams = array();
900
904 protected $childtables = array();
905
911 protected $childtablesoncascade = array();
912
916 public $cond_reglement_supplier_id;
917
923 public $deposit_percent;
924
925
929 public $warehouse_id;
930
934 public $isextrafieldmanaged = 0;
935
944 public $attachedfiles;
945
946
947
948 // No constructor as it is an abstract class
949
955 protected function deprecatedProperties()
956 {
957 return array(
958 'alreadypaid' => 'totalpaid',
959 'cond_reglement' => 'depr_cond_reglement',
960 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
961 'commandeFournisseur' => 'origin_object',
962 'expedition' => 'origin_object',
963 'fk_project' => 'fk_project',
964 'livraison' => 'origin_object',
965 'projet' => 'project',
966 'statut' => 'status',
967 );
968 }
969
970
981 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
982 {
983 global $db, $conf;
984
985 $sql = "SELECT rowid";
986 $sql .= " FROM ".$db->prefix().$element;
987 $sql .= " WHERE entity IN (".getEntity($element).")";
988
989 if ($id > 0) {
990 $sql .= " AND rowid = ".((int) $id);
991 } elseif ($ref) {
992 $sql .= " AND ref = '".$db->escape($ref)."'";
993 } elseif ($ref_ext) {
994 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
995 } else {
996 $error = 'ErrorWrongParameters';
997 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
998 return -1;
999 }
1000 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
1001 $sql .= " AND entity = ".((int) $conf->entity);
1002 }
1003
1004 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
1005 $resql = $db->query($sql);
1006 if ($resql) {
1007 $num = $db->num_rows($resql);
1008 if ($num > 0) {
1009 return 1;
1010 } else {
1011 return 0;
1012 }
1013 }
1014 return -1;
1015 }
1016
1023 public function isEmpty()
1024 {
1025 return (empty($this->id));
1026 }
1027
1035 {
1036 if (!empty($object->error)) {
1037 $this->error = $object->error;
1038 }
1039 if (!empty($object->errors)) {
1040 $this->errors = array_merge($this->errors, $object->errors);
1041 }
1042 }
1043
1051 public function getTooltipContentArray($params)
1052 {
1053 return [];
1054 }
1055
1063 public function getTooltipContent($params)
1064 {
1065 global $action, $extrafields, $langs, $hookmanager;
1066
1067 // If there is too much extrafields, we do not include them into tooltip
1068 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
1069
1070 $data = $this->getTooltipContentArray($params);
1071 $count = 0;
1072
1073 // Add extrafields
1074 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1075 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1076 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1077 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1078 continue;
1079 }
1080
1081 if (empty($extrafields->attributes[$this->table_element]['showintooltip'][$key])) {
1082 continue;
1083 }
1084
1085
1086 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1087 $data['more_extrafields'] = '<br>...';
1088 break;
1089 }
1090 $enabled = 1;
1091 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1092 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1093 }
1094 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1095 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1096 }
1097 $perms = 1;
1098 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1099 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1100 }
1101 if (empty($enabled)) {
1102 continue; // 0 = Never visible field
1103 }
1104 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1105 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1106 }
1107 if (empty($perms)) {
1108 continue; // 0 = Not visible
1109 }
1110 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1111 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1112 }
1113 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1114 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1115 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1116 } else {
1117 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1118 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1119 $count++;
1120 }
1121 }
1122 $data['closedivextra'] = '</div>';
1123 }
1124
1125 $hookmanager->initHooks(array($this->element . 'dao'));
1126 $parameters = array(
1127 'tooltipcontentarray' => &$data,
1128 'params' => $params,
1129 );
1130 // Note that $action and $object may have been modified by some hooks
1131 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1132
1133 //var_dump($data);
1134 $label = implode($data);
1135
1136 return $label;
1137 }
1138
1139
1145 public function errorsToString()
1146 {
1147 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1148 }
1149
1150
1157 public function getFormatedCustomerRef($objref)
1158 {
1159 global $hookmanager;
1160
1161 $parameters = array('objref' => $objref);
1162 $action = '';
1163 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1164 if ($reshook > 0) {
1165 return $hookmanager->resArray['objref'];
1166 }
1167 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1168 }
1169
1176 public function getFormatedSupplierRef($objref)
1177 {
1178 global $hookmanager;
1179
1180 $parameters = array('objref' => $objref);
1181 $action = '';
1182 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1183 if ($reshook > 0) {
1184 return $hookmanager->resArray['objref'];
1185 }
1186 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1187 }
1188
1198 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1199 {
1200 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1201 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1202 $tmparray = getCountry($this->country_id, 'all');
1203 $this->country_code = $tmparray['code'];
1204 $this->country = $tmparray['label'];
1205 }
1206
1207 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1208 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1209 $tmparray = getState($this->state_id, 'all', null, 1);
1210 $this->state_code = $tmparray['code'];
1211 $this->state = $tmparray['label'];
1212 $this->region_code = $tmparray['region_code'];
1213 $this->region = $tmparray['region'];
1214 }
1215
1216 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1217 }
1218
1219
1228 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1229 {
1230 global $user, $dolibarr_main_url_root;
1231
1232 if (empty($this->last_main_doc)) {
1233 return ''; // No way to known which document name to use
1234 }
1235
1236 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1237 $ecmfile = new EcmFiles($this->db);
1238 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1239 if ($result < 0) {
1240 $this->error = $ecmfile->error;
1241 $this->errors = $ecmfile->errors;
1242 return -1;
1243 }
1244
1245 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1246 // Add entry into index
1247 if ($initsharekey) {
1248 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1249
1250 // 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
1251 /*
1252 $ecmfile->filepath = $rel_dir;
1253 $ecmfile->filename = $filename;
1254 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1255 $ecmfile->fullpath_orig = '';
1256 $ecmfile->gen_or_uploaded = 'generated';
1257 $ecmfile->description = ''; // indexed content
1258 $ecmfile->keywords = ''; // keyword content
1259 $ecmfile->share = getRandomPassword(true);
1260 $result = $ecmfile->create($user);
1261 if ($result < 0)
1262 {
1263 $this->error = $ecmfile->error;
1264 $this->errors = $ecmfile->errors;
1265 }
1266 */
1267 } else {
1268 return '';
1269 }
1270 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1271 // Add entry into index
1272 if ($initsharekey) {
1273 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1274 $ecmfile->share = getRandomPassword(true);
1275 $ecmfile->update($user);
1276 } else {
1277 return '';
1278 }
1279 }
1280 // Define $urlwithroot
1281 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1282 // This is to use external domain name found into config file
1283 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1284 //else
1285 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1286 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1287
1288 $forcedownload = 0;
1289
1290 $paramlink = '';
1291 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1292 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1293 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1294 if (!empty($ecmfile->share)) {
1295 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1296 }
1297 if ($forcedownload) {
1298 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1299 }
1300
1301 if ($relativelink) {
1302 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1303 } else {
1304 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1305 }
1306
1307 // Here $ecmfile->share is defined
1308 return $linktoreturn;
1309 }
1310
1311
1312 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1322 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1323 {
1324 // phpcs:enable
1325 global $user, $langs;
1326
1327 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1328
1329 // Check parameters
1330 if ($fk_socpeople <= 0) {
1331 $langs->load("errors");
1332 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1333 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1334 return -1;
1335 }
1336
1337 // Check that the contact actually exists in database
1338 $sql_check = "SELECT rowid FROM ".$this->db->prefix().($source == 'internal' ? "user" : "socpeople")." WHERE rowid = ".((int) $fk_socpeople);
1339 $resql_check = $this->db->query($sql_check);
1340 if ($resql_check) {
1341 if (!$this->db->num_rows($resql_check)) {
1342 $langs->load("errors");
1343 $this->error = $langs->trans("ErrorRecordNotFound");
1344 dol_syslog(get_class($this)."::add_contact Contact/user id ".$fk_socpeople." does not exist", LOG_ERR);
1345 return -1;
1346 }
1347 } else {
1348 $this->error = $this->db->lasterror();
1349 return -1;
1350 }
1351
1352 if (!$type_contact) {
1353 $langs->load("errors");
1354 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1355 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1356 return -2;
1357 }
1358
1359 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && $user->id > 0 && !$user->hasRight('societe', 'client', 'voir')) {
1360 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1361 $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid;
1362 $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id;
1363
1364 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1365
1366 if (!$resql_allowed_contacts) {
1367 $this->errors[] = $this->db->lasterror();
1368 return -3;
1369 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1370 if ($obj->cnt == 0) {
1371 $langs->load("companies");
1372 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1373 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1374 return -4;
1375 }
1376 }
1377 }
1378
1379 $id_type_contact = 0;
1380 if (is_numeric($type_contact)) {
1381 $id_type_contact = $type_contact;
1382 } else {
1383 // We look for id type_contact
1384 $sql = "SELECT tc.rowid";
1385 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1386 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1387 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1388 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1389 //print $sql;
1390 $resql = $this->db->query($sql);
1391 if ($resql) {
1392 $obj = $this->db->fetch_object($resql);
1393 if ($obj) {
1394 $id_type_contact = $obj->rowid;
1395 }
1396 }
1397 }
1398 if ($id_type_contact == 0) {
1399 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");
1400 return 0;
1401 }
1402
1403 $datecreate = dol_now();
1404
1405 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1406 $TListeContacts = $this->liste_contact(-1, $source);
1407 $already_added = false;
1408 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1409 foreach ($TListeContacts as $array_contact) {
1410 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1411 $already_added = true;
1412 break;
1413 }
1414 }
1415 }
1416
1417 if (!$already_added) {
1418 $this->db->begin();
1419
1420 // Insert into database
1421 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1422 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1423 $sql .= " VALUES (".((int) $this->id).", ".((int) $fk_socpeople)." , ";
1424 $sql .= "'".$this->db->idate($datecreate)."'";
1425 $sql .= ", 4, ".((int) $id_type_contact);
1426 $sql .= ")";
1427
1428 $resql = $this->db->query($sql);
1429 if ($resql) {
1430 if (!$notrigger) {
1431 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1432 $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user);
1433 if ($result < 0) {
1434 $this->db->rollback();
1435 return -5;
1436 }
1437 }
1438
1439 $this->db->commit();
1440 return 1;
1441 } else {
1442 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1443 $this->error = $this->db->errno();
1444 $this->db->rollback();
1445 return -6;
1446 } else {
1447 $this->error = $this->db->lasterror();
1448 $this->db->rollback();
1449 return -7;
1450 }
1451 }
1452 } else {
1453 return 0;
1454 }
1455 }
1456
1457 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1465 public function copy_linked_contact($objFrom, $source = 'internal')
1466 {
1467 // phpcs:enable
1468 $contacts = $objFrom->liste_contact(-1, $source);
1469 foreach ($contacts as $contact) {
1470 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1471 return -1;
1472 }
1473 }
1474 return 1;
1475 }
1476
1477 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1487 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1488 {
1489 // phpcs:enable
1490 // Insert into database
1491 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1492 $sql .= " statut = ".((int) $statut);
1493 if ($type_contact_id) {
1494 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1495 }
1496 if ($fk_socpeople) {
1497 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1498 }
1499 $sql .= " where rowid = ".((int) $rowid);
1500
1501 $resql = $this->db->query($sql);
1502 if ($resql) {
1503 return 0;
1504 } else {
1505 $this->error = $this->db->lasterror();
1506 return -1;
1507 }
1508 }
1509
1510 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1518 public function delete_contact($rowid, $notrigger = 0)
1519 {
1520 // phpcs:enable
1521 global $user;
1522
1523 $error = 0;
1524
1525 // When called on a loaded object, the link must be one of its own contacts (same filter as liste_contact())
1526 if ($this->id > 0) {
1527 $sql = "SELECT ec.rowid FROM ".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc";
1528 $sql .= " WHERE ec.rowid = ".((int) $rowid)." AND ec.element_id = ".((int) $this->id);
1529 $sql .= " AND ec.fk_c_type_contact = tc.rowid AND tc.element = '".$this->db->escape($this->element)."'";
1530 $resql = $this->db->query($sql);
1531 if (!$resql || !$this->db->num_rows($resql)) {
1532 $this->error = 'ErrorRecordNotFound';
1533 return -1;
1534 }
1535 }
1536
1537 $this->db->begin();
1538
1539 if (!$error && empty($notrigger)) {
1540 // Call trigger
1541 $this->context['contact_id'] = ((int) $rowid);
1542 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1543 $result = $this->call_trigger($triggerPrefix.'_DELETE_CONTACT', $user);
1544 if ($result < 0) {
1545 $error++;
1546 }
1547 // End call triggers
1548 }
1549
1550 if (!$error) {
1551 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1552
1553 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1554 $sql .= " WHERE rowid = ".((int) $rowid);
1555
1556 $result = $this->db->query($sql);
1557 if (!$result) {
1558 $error++;
1559 $this->errors[] = $this->db->lasterror();
1560 }
1561 }
1562
1563 if (!$error) {
1564 $this->db->commit();
1565 return 1;
1566 } else {
1567 $this->error = $this->db->lasterror();
1568 $this->db->rollback();
1569 return -1;
1570 }
1571 }
1572
1573 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1581 public function delete_linked_contact($source = '', $code = '')
1582 {
1583 // phpcs:enable
1584 $listId = '';
1585 $temp = array();
1586 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1587
1588 if (!empty($typeContact)) {
1589 foreach ($typeContact as $key => $value) {
1590 array_push($temp, $key);
1591 }
1592 $listId = implode(",", $temp);
1593 }
1594
1595 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1596 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1597 if (empty($listId)) {
1598 return 0;
1599 }
1600
1601 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1602 $sql .= " WHERE element_id = ".((int) $this->id);
1603 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1604
1605 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1606 if ($this->db->query($sql)) {
1607 return 1;
1608 } else {
1609 $this->error = $this->db->lasterror();
1610 return -1;
1611 }
1612 }
1613
1614 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1626 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1627 {
1628 // phpcs:enable
1629 global $langs;
1630
1631 $tab = array();
1632
1633 $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
1634 if ($source == 'internal') {
1635 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1636 }
1637 if ($source == 'external' || $source == 'thirdparty') {
1638 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1639 }
1640 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1641 if (empty($arrayoftcids)) {
1642 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1643 }
1644 $sql .= " FROM";
1645 if (empty($arrayoftcids)) {
1646 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1647 }
1648 $sql .= " ".$this->db->prefix()."element_contact as ec";
1649 if ($source == 'internal') { // internal contact (user)
1650 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1651 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1652 }
1653 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1654 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1655 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1656 }
1657 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1658 if (empty($arrayoftcids)) {
1659 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1660 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1661 if ($code) {
1662 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1663 }
1664 if ($source == 'internal') {
1665 $sql .= " AND tc.source = 'internal'";
1666 }
1667 if ($source == 'external' || $source == 'thirdparty') {
1668 $sql .= " AND tc.source = 'external'";
1669 }
1670 $sql .= " AND tc.active = 1";
1671 } else {
1672 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1673 }
1674 if ($status >= 0) {
1675 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1676 }
1677 if ($statusoflink >= 0) {
1678 $sql .= " AND ec.statut = ".((int) $statusoflink);
1679 }
1680 $sql .= " ORDER BY t.lastname ASC";
1681
1682 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1683 $resql = $this->db->query($sql);
1684 if ($resql) {
1685 $num = $this->db->num_rows($resql);
1686 $i = 0;
1687 while ($i < $num) {
1688 $obj = $this->db->fetch_object($resql);
1689
1690 if (!$list) {
1691 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1692 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1693 $tab[$i] = array(
1694 'parentId' => $this->id,
1695 'source' => $obj->source,
1696 'socid' => $obj->socid,
1697 'id' => $obj->id,
1698 'nom' => $obj->lastname, // For backward compatibility
1699 'civility' => $obj->civility,
1700 'lastname' => $obj->lastname,
1701 'firstname' => $obj->firstname,
1702 'email' => $obj->email,
1703 'address' => $obj->address,
1704 'zip' => $obj->zip,
1705 'town' => $obj->town,
1706 'country_id' => $obj->country_id,
1707 'country' => $obj->country,
1708 'login' => (empty($obj->login) ? '' : $obj->login),
1709 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1710 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1711 'statuscontact' => $obj->statuscontact,
1712 'rowid' => $obj->rowid,
1713 'code' => $obj->code,
1714 'libelle' => $libelle_type,
1715 'status' => (int) $obj->statuslink,
1716 'fk_c_type_contact' => $obj->fk_c_type_contact
1717 );
1718 } else {
1719 $tab[$i] = $obj->id;
1720 }
1721
1722 $i++;
1723 }
1724
1725 return $tab;
1726 } else {
1727 $this->error = $this->db->lasterror();
1728 dol_print_error($this->db);
1729 return -1;
1730 }
1731 }
1732
1733
1740 public function swapContactStatus($rowid)
1741 {
1742 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1743 $sql .= " tc.code, tc.libelle as type_label";
1744 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1745 $sql .= " WHERE ec.rowid =".((int) $rowid);
1746 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1747 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1748
1749 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1750 $resql = $this->db->query($sql);
1751 if ($resql) {
1752 $obj = $this->db->fetch_object($resql);
1753 $newstatut = ($obj->statut == 4) ? 5 : 4;
1754 $result = $this->update_contact($rowid, $newstatut);
1755 $this->db->free($resql);
1756 return $result;
1757 } else {
1758 $this->error = $this->db->error();
1759 dol_print_error($this->db);
1760 return -1;
1761 }
1762 }
1763
1764 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1775 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1776 {
1777 // phpcs:enable
1778 global $langs;
1779
1780 $sanitized_order = $order; // @phan-suppress-current-line SqlInjection
1781
1782 if (empty($sanitized_order)) {
1783 $sanitized_order = 'position';
1784 }
1785 if ($sanitized_order == 'position') {
1786 $sanitized_order .= ',code';
1787 }
1788
1789 $tab = array();
1790
1791 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1792 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1793 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1794 if ($activeonly == 1) {
1795 $sql .= " AND tc.active = 1"; // only the active types
1796 }
1797 if (!empty($source) && $source != 'all') {
1798 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1799 }
1800 if (!empty($code)) {
1801 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1802 }
1803 $sql .= $this->db->order($sanitized_order, 'ASC');
1804
1805 //print "sql=".$sql;
1806 $resql = $this->db->query($sql);
1807 if (!$resql) {
1808 $this->error = $this->db->lasterror();
1809 //dol_print_error($this->db);
1810 return null;
1811 }
1812
1813 $num = $this->db->num_rows($resql);
1814 $i = 0;
1815 while ($i < $num) {
1816 $obj = $this->db->fetch_object($resql);
1817
1818 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1819 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1820 if (empty($option)) {
1821 $tab[$obj->rowid] = $libelle_type;
1822 } elseif ($option == 1) {
1823 $tab[$obj->code] = $libelle_type;
1824 } else {
1825 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1826 }
1827 $i++;
1828 }
1829
1830 return $tab;
1831 }
1832
1844 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1845 {
1846 global $langs;
1847
1848 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1849
1850 $tab = array();
1851
1852 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1853 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1854
1855 $sqlWhere = array();
1856 if (!empty($element)) {
1857 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1858 }
1859 if (!empty($excludeelement)) {
1860 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1861 }
1862
1863 if ($activeonly == 1) {
1864 $sqlWhere[] = " tc.active=1"; // only the active types
1865 }
1866
1867 if (!empty($source) && $source != 'all') {
1868 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1869 }
1870
1871 if (!empty($code)) {
1872 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1873 }
1874
1875 if (count($sqlWhere) > 0) {
1876 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1877 }
1878
1879 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1880
1881 dol_syslog(__METHOD__, LOG_DEBUG);
1882 $resql = $this->db->query($sql);
1883 if ($resql) {
1884 $num = $this->db->num_rows($resql);
1885 if ($num > 0) {
1886 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1887
1888 while ($obj = $this->db->fetch_object($resql)) {
1889 $modulename = $obj->module ?? $obj->element;
1890 if (strpos($obj->element, 'project') !== false) {
1891 $modulename = 'projet';
1892 } elseif ($obj->element == 'contrat') {
1893 $element = 'contract';
1894 } elseif ($obj->element == 'action') {
1895 $modulename = 'agenda';
1896 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1897 $modulename = 'fournisseur';
1898 }
1899 if (isModEnabled($modulename)) {
1900 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1901 $tmpelement = $obj->element;
1902 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1903 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1904 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1905 }
1906 }
1907 }
1908 return $tab;
1909 } else {
1910 $this->error = $this->db->lasterror();
1911 return null;
1912 }
1913 }
1914
1926 public function getIdContact($source, $code, $status = 0)
1927 {
1928 $result = array();
1929 $i = 0;
1930 // Particular case for shipping
1931 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1932 $id = $this->origin_id;
1933 $element = 'commande';
1934 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1935 $id = $this->origin_id;
1936 $element = 'order_supplier';
1937 } else {
1938 $id = $this->id;
1939 $element = $this->element;
1940 }
1941
1942 $sql = "SELECT ec.fk_socpeople";
1943 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1944 if ($source == 'internal') {
1945 $sql .= " ".$this->db->prefix()."user as c,";
1946 }
1947 if ($source == 'external') {
1948 $sql .= " ".$this->db->prefix()."socpeople as c,";
1949 }
1950 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1951 $sql .= " WHERE ec.element_id = ".((int) $id);
1952 $sql .= " AND ec.fk_socpeople = c.rowid";
1953 if ($source == 'internal') {
1954 $sql .= " AND c.entity IN (".getEntity('user').")";
1955 }
1956 if ($source == 'external') {
1957 $sql .= " AND c.entity IN (".getEntity('societe').")";
1958 }
1959 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1960 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1961 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1962 if ($code) {
1963 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1964 }
1965 $sql .= " AND tc.active = 1";
1966 if ($status) {
1967 $sql .= " AND ec.statut = ".((int) $status);
1968 }
1969
1970 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1971 $resql = $this->db->query($sql);
1972 if ($resql) {
1973 while ($obj = $this->db->fetch_object($resql)) {
1974 $result[$i] = $obj->fk_socpeople;
1975 $i++;
1976 }
1977 } else {
1978 $this->error = $this->db->error();
1979 return null;
1980 }
1981
1982 return $result;
1983 }
1984
1985 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1992 public function fetch_contact($contactid = null)
1993 {
1994 // phpcs:enable
1995 if (empty($contactid)) {
1996 $contactid = $this->contact_id;
1997 }
1998
1999 if (empty($contactid)) {
2000 return 0;
2001 }
2002
2003 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
2004 $contact = new Contact($this->db);
2005 $result = $contact->fetch($contactid);
2006 $this->contact = $contact;
2007 return $result;
2008 }
2009
2010 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018 public function fetch_thirdparty($force_thirdparty_id = 0)
2019 {
2020 // phpcs:enable
2021 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
2022 return 0;
2023 }
2024
2025 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
2026
2027 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2028 if ($force_thirdparty_id) {
2029 $idtofetch = $force_thirdparty_id;
2030 }
2031
2032 if ($idtofetch) {
2033 $thirdparty = new Societe($this->db);
2034 $result = $thirdparty->fetch($idtofetch);
2035 if ($result < 0) {
2036 $this->errors = array_merge($this->errors, $thirdparty->errors);
2037 }
2038 $this->thirdparty = $thirdparty;
2039
2040 // Use first price level if level not defined for third party
2041 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
2042 $this->thirdparty->price_level = 1;
2043 }
2044
2045 return $result;
2046 } else {
2047 return -1;
2048 }
2049 }
2050
2051
2059 public function fetchOneLike($ref)
2060 {
2061 if (!$this->table_ref_field) {
2062 return 0;
2063 }
2064
2065 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
2066 $sql .= " WHERE ".$this->db->sanitize($this->table_ref_field)." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
2067 $sql .= " LIMIT 1";
2068
2069 $query = $this->db->query($sql);
2070
2071 if (!$this->db->num_rows($query)) {
2072 return 0;
2073 }
2074
2075 $result = $this->db->fetch_object($query);
2076
2077 if (method_exists($this, 'fetch')) {
2078 return $this->fetch($result->rowid);
2079 } else {
2080 $this->error = 'Fetch method not implemented on '.get_class($this);
2081 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2082 array_push($this->errors, $this->error);
2083 return -1;
2084 }
2085 }
2086
2087 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2096 public function fetch_barcode()
2097 {
2098 // phpcs:enable
2099 return $this->fetchBarCode();
2100 }
2101
2109 public function fetchBarCode()
2110 {
2111 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2112
2113 $idtype = $this->barcode_type;
2114 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
2115 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2116 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2117 } elseif ($this->element == 'societe') {
2118 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2119 } else {
2120 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2121 }
2122 }
2123
2124 if ($idtype > 0) {
2125 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
2126 $sql = "SELECT rowid, code, libelle as label, coder";
2127 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2128 $sql .= " WHERE rowid = ".((int) $idtype);
2129
2130 $resql = $this->db->query($sql);
2131 if ($resql) {
2132 $obj = $this->db->fetch_object($resql);
2133
2134 $this->barcode_type = $obj->rowid;
2135 $this->barcode_type_code = $obj->code;
2136 $this->barcode_type_label = $obj->label;
2137 $this->barcode_type_coder = $obj->coder;
2138 return 1;
2139 } else {
2140 dol_print_error($this->db);
2141 return -1;
2142 }
2143 }
2144 }
2145 return 0;
2146 }
2147
2153 public function fetchProject()
2154 {
2155 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2156
2157 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2158 $this->fk_project = $this->fk_projet; // For backward compatibility
2159 }
2160 if (empty($this->fk_project)) {
2161 return 0;
2162 }
2163
2164 $project = new Project($this->db);
2165 $result = $project->fetch($this->fk_project);
2166
2167 $this->project = $project;
2168
2169 return $result;
2170 }
2171
2172 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2180 public function fetch_project()
2181 {
2182 // phpcs:enable
2183 return $this->fetchProject();
2184 }
2185
2186 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2194 public function fetch_projet()
2195 {
2196 // phpcs:enable
2197 return $this->fetchProject();
2198 }
2199
2200 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2206 public function fetch_product()
2207 {
2208 // phpcs:enable
2209 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2210
2211 // @phan-suppress-next-line PhanUndeclaredProperty
2212 if (empty($this->fk_product)) {
2213 return 0;
2214 }
2215
2216 $product = new Product($this->db);
2217 // @phan-suppress-next-line PhanUndeclaredProperty
2218 $result = $product->fetch($this->fk_product);
2219
2220 $this->product = $product;
2221 return $result;
2222 }
2223
2224 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2231 public function fetch_user($userid)
2232 {
2233 // phpcs:enable
2234 $user = new User($this->db);
2235 $result = $user->fetch($userid);
2236 $this->user = $user;
2237 return $result;
2238 }
2239
2240 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2247 public function fetch_origin()
2248 {
2249 // phpcs:enable
2250 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2251
2252 // Manage classes with non standard name
2253 if ($tmpclassname == 'shipping') {
2254 $tmpclassname = 'Expedition';
2255 }
2256 if ($tmpclassname == 'delivery') {
2257 $tmpclassname = 'Livraison';
2258 }
2259 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2260 $tmpclassname = 'CommandeFournisseur';
2261 }
2262
2263 $classname = ucfirst($tmpclassname);
2264
2265 $this->origin_object = new $classname($this->db);
2266 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2267 $this->origin_object->fetch($this->origin_id);
2268 }
2269
2279 public function fetchObjectFrom($table, $field, $key, $element = null)
2280 {
2281 global $conf;
2282
2283 $result = false;
2284
2285 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2286 $sql .= " WHERE ".$this->db->sanitize($field)." = '".$this->db->escape($key)."'";
2287 if (!empty($element)) {
2288 $sql .= " AND entity IN (".getEntity($element).")";
2289 } else {
2290 $sql .= " AND entity = ".((int) $conf->entity);
2291 }
2292
2293 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2294 $resql = $this->db->query($sql);
2295 if ($resql) {
2296 $obj = $this->db->fetch_object($resql);
2297 // Test for avoid error -1
2298 if ($obj) {
2299 if (method_exists($this, 'fetch')) {
2300 $result = $this->fetch($obj->rowid);
2301 } else {
2302 $this->error = 'fetch() method not implemented on '.get_class($this);
2303 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2304 array_push($this->errors, $this->error);
2305 $result = -1;
2306 }
2307 }
2308 }
2309
2310 return $result;
2311 }
2312
2321 public function getValueFrom($table, $id, $field)
2322 {
2323 $result = false;
2324 if (!empty($id) && !empty($field) && !empty($table)) {
2325 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2326 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2327 return -1;
2328 }
2329 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2330 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2331 return -1;
2332 }
2333
2334 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2335 $sql .= " WHERE rowid = ".((int) $id);
2336
2337 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2338 $resql = $this->db->query($sql);
2339 if ($resql) {
2340 $row = $this->db->fetch_row($resql);
2341 $result = $row[0];
2342 }
2343 }
2344 return $result;
2345 }
2346
2363 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2364 {
2365 global $user;
2366
2367 if (empty($table)) {
2368 $table = $this->table_element;
2369 }
2370 if (empty($id)) {
2371 $id = $this->id;
2372 }
2373 if (empty($format)) {
2374 $format = 'text';
2375 }
2376 if (empty($id_field)) {
2377 $id_field = 'rowid';
2378 }
2379
2380 $propfield = $field;
2381
2382 // Special case
2383 if ($table == 'product') {
2384 if ($field == 'note_private') {
2385 $field = 'note';
2386 $propfield = 'note_private';
2387 } elseif ($field == 'status') {
2388 $field = 'tosell';
2389 $propfield = 'status';
2390 } elseif ($field == 'status_buy') {
2391 $field = 'tobuy';
2392 $propfield = 'status_buy';
2393 } elseif ($field == 'status_batch') {
2394 $field = 'tobatch';
2395 $propfield = 'status_batch';
2396 }
2397 }
2398
2399 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2400 $fk_user_field = 'fk_user_mod';
2401 }
2402 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2403 $fk_user_field = '';
2404 }
2405
2406 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2407 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2408 return -1;
2409 }
2410 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2411 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2412 return -1;
2413 }
2414
2415 $oldvalue = null;
2416 if ($trigkey) {
2417 $sql = "SELECT " . $this->db->sanitize($field);
2418 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2419 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2420
2421 $resql = $this->db->query($sql);
2422 if ($resql) {
2423 if ($obj = $this->db->fetch_object($resql)) {
2424 if ($format == 'date') {
2425 $oldvalue = $this->db->jdate($obj->$field);
2426 } else {
2427 $oldvalue = $obj->$field;
2428 }
2429 }
2430 } else {
2431 $this->error = $this->db->lasterror();
2432 return -1;
2433 }
2434 }
2435
2436 $error = 0;
2437
2438 dol_syslog(__METHOD__, LOG_DEBUG);
2439
2440 $this->db->begin();
2441
2442 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2443
2444 if ($format == 'text') {
2445 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2446 } elseif ($format == 'int') {
2447 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2448 } elseif ($format == 'date') {
2449 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2450 } elseif ($format == 'dategmt') {
2451 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2452 }
2453
2454 if ($fk_user_field) {
2455 if (!empty($fuser) && is_object($fuser)) {
2456 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2457 } elseif (empty($fuser) || $fuser != 'none') {
2458 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2459 }
2460 }
2461
2462 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2463
2464 $resql = $this->db->query($sql);
2465 if ($resql) {
2466 if ($trigkey) {
2467 // call trigger with updated object values
2468 if (method_exists($this, 'fetch')) {
2469 $result = $this->fetch($id);
2470 } else {
2471 $result = $this->fetchCommon($id);
2472 }
2473
2474 $this->oldcopy = clone $this;
2475 if (property_exists($this->oldcopy, $field)) {
2476 $this->oldcopy->$field = $oldvalue;
2477 }
2478 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2479 $this->oldcopy->$propfield = $oldvalue;
2480 }
2481
2482 if (empty($this->context['actionmsgmore'])) {
2483 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2484 }
2485
2486 if ($result >= 0) {
2487 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2488 }
2489 if ($result < 0) {
2490 $error++;
2491 }
2492 } else {
2493 if (property_exists($this, $field)) {
2494 $this->$field = $value;
2495 }
2496 if ($propfield != $field && property_exists($this, $propfield)) {
2497 $this->$propfield = $value;
2498 }
2499 }
2500
2501 if (!$error) {
2502 $this->db->commit();
2503 return 1;
2504 } else {
2505 if (property_exists($this, $field)) {
2506 $this->$field = $oldvalue;
2507 }
2508 if ($propfield != $field && property_exists($this, $propfield)) {
2509 $this->$propfield = $oldvalue;
2510 }
2511 $this->db->rollback();
2512 return -2;
2513 }
2514 } else {
2515 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2516 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2517 } else {
2518 $this->error = $this->db->lasterror();
2519 }
2520 $this->db->rollback();
2521 return -1;
2522 }
2523 }
2524
2525 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2536 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2537 {
2538 // phpcs:enable
2539 global $conf, $user;
2540
2541 if (!$this->table_element) {
2542 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2543 return -1;
2544 }
2545 if ($fieldid == 'none') {
2546 return 1;
2547 }
2548
2549 // For backward compatibility
2550 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2551 $fieldid = 'titre';
2552 }
2553
2554 // Security on socid
2555 $socid = 0;
2556 if ($user->socid > 0) {
2557 $socid = $user->socid;
2558 }
2559
2560 // this->ismultientitymanaged contains
2561 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2562 $aliastablesociete = 's';
2563 if ($this->element == 'societe') {
2564 $aliastablesociete = 'te'; // te as table_element
2565 }
2566 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2567 $sql = "SELECT MAX(te.".$this->db->sanitize($fieldid).")";
2568 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2569 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2570 $tmparray = explode('@', $this->ismultientitymanaged);
2571 $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
2572 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2573 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2574 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2575 $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
2576 }
2577 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2578 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2579 }
2580 if ($fieldid == 'rowid') {
2581 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < ".((int) $this->id);
2582 } elseif ($fieldid == 'label') {
2583 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < '".$this->db->escape((string) $this->label)."'";
2584 } else { // Should be 'ref' or any other string field
2585 $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)
2586 }
2587 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2588 $sql .= " AND sc.fk_user = ".((int) $user->id);
2589 }
2590 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2591 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2592 }
2593
2594 $filtermax = $filter;
2595
2596 // Manage filter
2597 $errormessage = '';
2598 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2599 if ($errormessage) {
2600 if (!preg_match('/^\s*AND/i', $filtermax)) {
2601 $sql .= " AND ";
2602 }
2603 $sql .= $filtermax; // @phan-suppress-current-line SqlInjection
2604 } else {
2605 $sql .= $tmpsql;
2606 }
2607
2608 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2609 $tmparray = explode('@', $this->ismultientitymanaged);
2610 $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
2611 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2612 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2613 }
2614 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2615 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2616 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2617 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2618 } else {
2619 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2620 }
2621 } else {
2622 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2623 }
2624 }
2625 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2626 $tmparray = explode('@', $this->ismultientitymanaged);
2627 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2628 }
2629 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2630 $sql .= ' AND te.fk_soc = '.((int) $socid);
2631 }
2632 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2633 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2634 }
2635 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2636 $sql .= ' AND te.rowid = '.((int) $socid);
2637 }
2638 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2639
2640 $result = $this->db->query($sql);
2641 if (!$result) {
2642 $this->error = $this->db->lasterror();
2643 return -1;
2644 }
2645 $row = $this->db->fetch_row($result);
2646 $this->ref_previous = $row[0];
2647
2648 $sql = "SELECT MIN(te.".$this->db->sanitize($fieldid).")";
2649 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2650 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2651 $tmparray = explode('@', $this->ismultientitymanaged);
2652 $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
2653 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2654 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2655 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2656 $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
2657 }
2658 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2659 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2660 }
2661 if ($fieldid == 'rowid') {
2662 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > ".((int) $this->id);
2663 } elseif ($fieldid == 'label') {
2664 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > '".$this->db->escape((string) $this->label)."'";
2665 } else { // Should be 'ref' or any other string field
2666 $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)
2667 }
2668 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2669 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2670 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2671 $userschilds = $user->getAllChildIds();
2672 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2673 }
2674 $sql .= ')';
2675 }
2676 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2677 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2678 }
2679
2680 $filtermin = $filter;
2681
2682 // Manage filter
2683 $errormessage = '';
2684 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2685 if ($errormessage) {
2686 if (!preg_match('/^\s*AND/i', $filtermin)) {
2687 $sql .= " AND ";
2688 }
2689 $sql .= $filtermin; // @phan-suppress-current-line SqlInjection
2690
2691 $filtermin = '';
2692 } else {
2693 $sql .= $tmpsql;
2694 }
2695
2696 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2697 $tmparray = explode('@', $this->ismultientitymanaged);
2698 $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
2699 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2700 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2701 }
2702 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2703 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2704 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2705 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2706 } else {
2707 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2708 }
2709 } else {
2710 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2711 }
2712 }
2713 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2714 $tmparray = explode('@', $this->ismultientitymanaged);
2715 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2716 }
2717 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2718 $sql .= ' AND te.fk_soc = '.((int) $socid);
2719 }
2720 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2721 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2722 }
2723 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2724 $sql .= ' AND te.rowid = '.((int) $socid);
2725 }
2726 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2727 // 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
2728
2729 $result = $this->db->query($sql);
2730 if (!$result) {
2731 $this->error = $this->db->lasterror();
2732 return -2;
2733 }
2734 $row = $this->db->fetch_row($result);
2735 $this->ref_next = $row[0];
2736
2737 return 1;
2738 }
2739
2740
2748 public function getListContactId($source = 'external')
2749 {
2750 $contactAlreadySelected = array();
2751 $tab = $this->liste_contact(-1, $source);
2752 $num = count($tab);
2753 $i = 0;
2754 while ($i < $num) {
2755 if ($source == 'thirdparty') {
2756 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2757 } else {
2758 $contactAlreadySelected[$i] = $tab[$i]['id'];
2759 }
2760 $i++;
2761 }
2762 return $contactAlreadySelected;
2763 }
2764
2765
2773 public function setProject($projectid, $notrigger = 0)
2774 {
2775 global $user;
2776 $error = 0;
2777
2778 if (!$this->table_element) {
2779 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2780 return -1;
2781 }
2782
2783 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2784 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2785 if ($projectid) {
2786 $sql .= " SET fk_project = ".((int) $projectid);
2787 } else {
2788 $sql .= " SET fk_project = NULL";
2789 }
2790 $sql .= ' WHERE id = '.((int) $this->id);
2791 // @phan-suppress-next-line PhanTypeMismatchProperty
2792 } elseif (!empty($this->fields['fk_project'])) { // Common case
2793 if ($projectid) {
2794 $sql .= " SET fk_project = ".((int) $projectid);
2795 } else {
2796 $sql .= " SET fk_project = NULL";
2797 }
2798 $sql .= ' WHERE rowid = '.((int) $this->id);
2799 } else { // Special case for old architecture objects
2800 if ($projectid) {
2801 $sql .= ' SET fk_projet = '.((int) $projectid);
2802 } else {
2803 $sql .= ' SET fk_projet = NULL';
2804 }
2805 $sql .= " WHERE rowid = ".((int) $this->id);
2806 }
2807
2808 $this->db->begin();
2809
2810 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2811 if ($this->db->query($sql)) {
2812 $this->fk_project = ((int) $projectid);
2813 } else {
2814 dol_print_error($this->db);
2815 $error++;
2816 }
2817
2818 // Triggers
2819 if (!$error && !$notrigger) {
2820 // Call triggers
2821 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2822 if ($result < 0) {
2823 $error++;
2824 } //Do also here what you must do to rollback action if trigger fail
2825 // End call triggers
2826 }
2827
2828 // Commit or rollback
2829 if ($error) {
2830 $this->db->rollback();
2831 return -1;
2832 } else {
2833 $this->db->commit();
2834 return 1;
2835 }
2836 }
2837
2844 public function setPaymentMethods($id)
2845 {
2846 global $user;
2847
2848 $error = 0;
2849 $notrigger = 0;
2850
2851 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2852
2853 if ($this->status >= 0 || $this->element == 'societe') {
2854 $this->oldcopy = dol_clone($this, 2); // @phan-suppress-current-line PhanTypeMismatchProperty
2855
2856 // TODO uniformize field name
2857 $fieldname = 'fk_mode_reglement';
2858 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2859 if ($this->element == 'societe') {
2860 $fieldname = 'mode_reglement';
2861 $triggerName = 'COMPANY';
2862 }
2863 if (get_class($this) == 'Facture') {
2864 $triggerName = 'BILL';
2865 }
2866 if (get_class($this) == 'Fournisseur') {
2867 $fieldname = 'mode_reglement_supplier';
2868 $triggerName = 'BILL_SUPPLIER';
2869 }
2870 if (get_class($this) == 'Tva') {
2871 $fieldname = 'fk_typepayment';
2872 $triggerName = 'VAT';
2873 }
2874 if (get_class($this) == 'Salary') {
2875 $fieldname = 'fk_typepayment';
2876 }
2877
2878 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2879 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2880 $sql .= ' WHERE rowid = '.((int) $this->id);
2881
2882 if ($this->db->query($sql)) {
2883 $this->mode_reglement_id = $id;
2884 // for supplier
2885 if (get_class($this) == 'Fournisseur') {
2886 $this->mode_reglement_supplier_id = $id;
2887 }
2888
2889 // Triggers
2890 if (!$error && !$notrigger) {
2891 // Call triggers
2892 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2893 if ($result < 0) {
2894 $error++;
2895 }
2896 // End call triggers
2897 }
2898 return 1;
2899 } else {
2900 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2901 $this->error = $this->db->error();
2902 return -1;
2903 }
2904 } else {
2905 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2906 $this->error = 'Status of the object is incompatible '.$this->status;
2907 return -2;
2908 }
2909 }
2910
2917 public function setMulticurrencyCode($code)
2918 {
2919 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2920 if ($this->status >= 0 || $this->element == 'societe') {
2921 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2922 $sql .= " SET multicurrency_code = '".$this->db->escape($code)."'";
2923 $sql .= ' WHERE rowid='.((int) $this->id);
2924
2925 if ($this->db->query($sql)) {
2926 $this->multicurrency_code = $code;
2927
2928 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2929 if ($rate) {
2930 $this->setMulticurrencyRate($rate, 2);
2931 }
2932
2933 return 1;
2934 } else {
2935 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2936 $this->error = $this->db->error();
2937 return -1;
2938 }
2939 } else {
2940 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2941 $this->error = 'Status of the object is incompatible '.$this->status;
2942 return -2;
2943 }
2944 }
2945
2953 public function setMulticurrencyRate($rate, $mode = 1)
2954 {
2955 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2956 if ($this->status >= 0 || $this->element == 'societe') {
2957 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2958 $sql .= " SET multicurrency_tx = ".((float) $rate);
2959 $sql .= ' WHERE rowid='.((int) $this->id);
2960
2961 if ($this->db->query($sql)) {
2962 $this->multicurrency_tx = $rate;
2963
2964 // Update line price
2965 if (!empty($this->lines)) {
2966 foreach ($this->lines as &$line) {
2967 // A credit line (deposit, credit note, discount applied to the invoice) is not priced at the invoice rate:
2968 // both its amounts are the historical ones of the credit, so a rate change must leave it untouched.
2969 if (!empty($line->fk_remise_except)) {
2970 continue;
2971 }
2972
2973 // Amounts in company currency will be recalculated
2974 if ($mode == 1) {
2975 $line->subprice = 0;
2976 }
2977
2978 // Amounts in foreign currency will be recalculated
2979 if ($mode == 2) {
2980 $line->multicurrency_subprice = 0;
2981 }
2982
2983 switch ($this->element) {
2984 case 'propal':
2987 '@phan-var-force Propal $this';
2988 '@phan-var-force PropaleLigne $line';
2989 $this->updateline(
2990 $line->id,
2991 $line->subprice,
2992 $line->qty,
2993 $line->remise_percent,
2994 $line->tva_tx,
2995 $line->localtax1_tx,
2996 $line->localtax2_tx,
2997 ($line->description ? $line->description : $line->desc),
2998 'HT',
2999 $line->info_bits,
3000 $line->special_code,
3001 $line->fk_parent_line,
3002 $line->skip_update_total,
3003 $line->fk_fournprice,
3004 $line->pa_ht,
3005 $line->label,
3006 $line->product_type,
3007 $line->date_start,
3008 $line->date_end,
3009 $line->array_options,
3010 $line->fk_unit,
3011 $line->multicurrency_subprice
3012 );
3013 break;
3014 case 'commande':
3017 '@phan-var-force Commande $this';
3018 '@phan-var-force OrderLine $line';
3019 $this->updateline(
3020 $line->id,
3021 ($line->description ? $line->description : $line->desc),
3022 $line->subprice,
3023 $line->qty,
3024 $line->remise_percent,
3025 $line->tva_tx,
3026 $line->localtax1_tx,
3027 $line->localtax2_tx,
3028 'HT',
3029 $line->info_bits,
3030 $line->date_start,
3031 $line->date_end,
3032 $line->product_type,
3033 $line->fk_parent_line,
3034 $line->skip_update_total,
3035 $line->fk_fournprice,
3036 $line->pa_ht,
3037 $line->label,
3038 $line->special_code,
3039 $line->array_options,
3040 $line->fk_unit,
3041 $line->multicurrency_subprice
3042 );
3043 break;
3044 case 'facture':
3047 '@phan-var-force Facture $this';
3048 '@phan-var-force FactureLigne $line';
3049 $this->updateline(
3050 $line->id,
3051 ($line->description ? $line->description : $line->desc),
3052 $line->subprice,
3053 $line->qty,
3054 $line->remise_percent,
3055 $line->date_start,
3056 $line->date_end,
3057 $line->tva_tx,
3058 $line->localtax1_tx,
3059 $line->localtax2_tx,
3060 'HT',
3061 $line->info_bits,
3062 $line->product_type,
3063 $line->fk_parent_line,
3064 $line->skip_update_total,
3065 $line->fk_fournprice,
3066 $line->pa_ht,
3067 $line->label,
3068 $line->special_code,
3069 $line->array_options,
3070 $line->situation_percent,
3071 $line->fk_unit,
3072 $line->multicurrency_subprice
3073 );
3074 break;
3075 case 'facturerec':
3078 '@phan-var-force FactureRec $this';
3079 '@phan-var-force FactureLigneRec $line';
3080 $this->updateline(
3081 $line->id,
3082 ($line->description ? $line->description : $line->desc),
3083 $line->subprice,
3084 $line->qty,
3085 $line->tva_tx,
3086 $line->localtax1_tx,
3087 $line->localtax2_tx,
3088 $line->fk_product,
3089 $line->remise_percent,
3090 'HT',
3091 $line->info_bits,
3092 0,
3093 0,
3094 $line->product_type,
3095 $line->rang,
3096 $line->special_code,
3097 $line->label,
3098 $line->fk_unit,
3099 $line->multicurrency_subprice,
3100 0,
3101 $line->date_start,
3102 $line->date_end,
3103 $line->fk_fournprice,
3104 $line->pa_ht,
3105 $line->fk_parent_line
3106 );
3107 break;
3108 case 'supplier_proposal':
3111 '@phan-var-force SupplierProposal $this';
3112 '@phan-var-force SupplierProposalLine $line';
3113 $this->updateline(
3114 $line->id,
3115 $line->subprice,
3116 $line->qty,
3117 $line->remise_percent,
3118 $line->tva_tx,
3119 $line->localtax1_tx,
3120 $line->localtax2_tx,
3121 ($line->description ? $line->description : $line->desc),
3122 'HT',
3123 $line->info_bits,
3124 $line->special_code,
3125 $line->fk_parent_line,
3126 $line->skip_update_total,
3127 $line->fk_fournprice,
3128 $line->pa_ht,
3129 $line->label,
3130 $line->product_type,
3131 $line->array_options,
3132 $line->ref_fourn,
3133 (int) $line->fk_unit,
3134 $line->multicurrency_subprice
3135 );
3136 break;
3137 case 'order_supplier':
3140 '@phan-var-force CommandeFournisseur $this';
3141 '@phan-var-force CommandeFournisseurLigne $line';
3142 $this->updateline(
3143 $line->id,
3144 ($line->description ? $line->description : $line->desc),
3145 $line->subprice,
3146 $line->qty,
3147 $line->remise_percent,
3148 $line->tva_tx,
3149 $line->localtax1_tx,
3150 $line->localtax2_tx,
3151 'HT',
3152 $line->info_bits,
3153 $line->product_type,
3154 0,
3155 $line->date_start,
3156 $line->date_end,
3157 $line->array_options,
3158 $line->fk_unit,
3159 $line->multicurrency_subprice,
3160 $line->ref_supplier
3161 );
3162 break;
3163 case 'invoice_supplier':
3166 '@phan-var-force FactureFournisseur $this';
3167 '@phan-var-force SupplierInvoiceLIne $line';
3168 $this->updateline(
3169 $line->id,
3170 ($line->description ? $line->description : $line->desc),
3171 $line->subprice,
3172 $line->tva_tx,
3173 $line->localtax1_tx,
3174 $line->localtax2_tx,
3175 $line->qty,
3176 0,
3177 'HT',
3178 $line->info_bits,
3179 $line->product_type,
3180 $line->remise_percent,
3181 0,
3182 $line->date_start,
3183 $line->date_end,
3184 $line->array_options,
3185 $line->fk_unit,
3186 $line->multicurrency_subprice,
3187 $line->ref_supplier
3188 );
3189 break;
3190 default:
3191 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3192 break;
3193 }
3194 }
3195 }
3196
3197 return 1;
3198 } else {
3199 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3200 $this->error = $this->db->error();
3201 return -1;
3202 }
3203 } else {
3204 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3205 $this->error = 'Status of the object is incompatible '.$this->status;
3206 return -2;
3207 }
3208 }
3209
3217 public function setPaymentTerms($id, $deposit_percent = null)
3218 {
3219 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.formatLogObject($deposit_percent).')');
3220 if ($this->status >= 0 || $this->element == 'societe') {
3221 // TODO uniformize field name
3222 $fieldname = 'fk_cond_reglement';
3223 if ($this->element == 'societe') {
3224 $fieldname = 'cond_reglement';
3225 }
3226 if (get_class($this) == 'Fournisseur') {
3227 $fieldname = 'cond_reglement_supplier';
3228 }
3229
3230 if (empty($deposit_percent) || $deposit_percent < 0) {
3231 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3232 }
3233
3234 if ($deposit_percent > 100) {
3235 $deposit_percent = 100;
3236 }
3237
3238 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3239 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3240 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3241 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3242 }
3243 $sql .= ' WHERE rowid = '.((int) $this->id);
3244
3245 if ($this->db->query($sql)) {
3246 $this->cond_reglement_id = $id;
3247 // for supplier
3248 if (get_class($this) == 'Fournisseur') {
3249 $this->cond_reglement_supplier_id = $id;
3250 }
3251 $this->deposit_percent = $deposit_percent;
3252 return 1;
3253 } else {
3254 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3255 $this->error = $this->db->error();
3256 return -1;
3257 }
3258 } else {
3259 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3260 $this->error = 'Status of the object is incompatible '.$this->status;
3261 return -2;
3262 }
3263 }
3264
3271 public function setTransportMode($id)
3272 {
3273 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3274 if ($this->status >= 0 || $this->element == 'societe') {
3275 $fieldname = 'fk_transport_mode';
3276 if ($this->element == 'societe') {
3277 $fieldname = 'transport_mode';
3278 }
3279 if (get_class($this) == 'Fournisseur') {
3280 $fieldname = 'transport_mode_supplier';
3281 }
3282
3283 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3284 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3285 $sql .= ' WHERE rowid='.((int) $this->id);
3286
3287 if ($this->db->query($sql)) {
3288 $this->transport_mode_id = $id;
3289 // for supplier
3290 if (get_class($this) == 'Fournisseur') {
3291 $this->transport_mode_supplier_id = $id;
3292 }
3293 return 1;
3294 } else {
3295 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3296 $this->error = $this->db->error();
3297 return -1;
3298 }
3299 } else {
3300 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3301 $this->error = 'Status of the object is incompatible '.$this->status;
3302 return -2;
3303 }
3304 }
3305
3313 public function setDeliveryAddress($id)
3314 {
3315 $fieldname = 'fk_delivery_address';
3316 if ($this->element == 'delivery' || $this->element == 'shipping') {
3317 $fieldname = 'fk_address';
3318 }
3319
3320 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$this->db->sanitize($fieldname)." = ".((int) $id);
3321 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3322
3323 if ($this->db->query($sql)) {
3324 $this->fk_delivery_address = $id;
3325 return 1;
3326 } else {
3327 $this->error = $this->db->error();
3328 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3329 return -1;
3330 }
3331 }
3332
3333
3342 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3343 {
3344 global $user;
3345
3346 if (empty($userused)) {
3347 $userused = $user;
3348 }
3349
3350 $error = 0;
3351
3352 if (!$this->table_element) {
3353 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3354 return -1;
3355 }
3356
3357 $this->db->begin();
3358
3359 if ($shipping_method_id < 0) {
3360 $shipping_method_id = 'NULL';
3361 }
3362 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3363
3364 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3365 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3366 $sql .= " WHERE rowid=".((int) $this->id);
3367 $resql = $this->db->query($sql);
3368 if (!$resql) {
3369 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3370 $this->error = $this->db->lasterror();
3371 $error++;
3372 } else {
3373 if (!$notrigger) {
3374 // Call trigger
3375 $this->context = array('shippingmethodupdate' => 1);
3376 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3377 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3378 if ($result < 0) {
3379 $error++;
3380 }
3381 // End call trigger
3382 }
3383 }
3384 if ($error) {
3385 $this->db->rollback();
3386 return -1;
3387 } else {
3388 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3389 $this->db->commit();
3390 return 1;
3391 }
3392 }
3393
3394
3401 public function setWarehouse($warehouse_id)
3402 {
3403 if (!$this->table_element) {
3404 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3405 return -1;
3406 }
3407 if ($warehouse_id < 0) {
3408 $warehouse_id = 'NULL';
3409 }
3410 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3411
3412 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3413 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3414 $sql .= " WHERE rowid=".((int) $this->id);
3415
3416 if ($this->db->query($sql)) {
3417 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3418 return 1;
3419 } else {
3420 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3421 $this->error = $this->db->error();
3422 return 0;
3423 }
3424 }
3425
3426
3434 public function setDocModel($user, $modelpdf)
3435 {
3436 if (!$this->table_element) {
3437 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3438 return -1;
3439 }
3440
3441 $newmodelpdf = dol_trunc($modelpdf, 255);
3442
3443 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3444 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3445 $sql .= " WHERE rowid = ".((int) $this->id);
3446
3447 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3448 $resql = $this->db->query($sql);
3449 if ($resql) {
3450 $this->model_pdf = $modelpdf;
3451 return 1;
3452 } else {
3453 dol_print_error($this->db);
3454 return 0;
3455 }
3456 }
3457
3458
3467 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3468 {
3469 global $user;
3470
3471 if (empty($userused)) {
3472 $userused = $user;
3473 }
3474
3475 $error = 0;
3476
3477 if (!$this->table_element) {
3478 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3479 return -1;
3480 }
3481 $this->db->begin();
3482
3483 if ($fk_account < 0) {
3484 $fk_account = 'NULL';
3485 }
3486 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3487
3488 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3489 $sql .= " SET fk_account = ".((int) $fk_account);
3490 $sql .= " WHERE rowid=".((int) $this->id);
3491
3492 $resql = $this->db->query($sql);
3493 if (!$resql) {
3494 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3495 $this->error = $this->db->lasterror();
3496 $error++;
3497 } else {
3498 if (!$notrigger) {
3499 // Call trigger
3500 $this->context['bankaccountupdate'] = 1;
3501
3502 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3503 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3504 if ($result < 0) {
3505 $error++;
3506 }
3507 // End call trigger
3508 }
3509 }
3510 if ($error) {
3511 $this->db->rollback();
3512 return -1;
3513 } else {
3514 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3515 $this->db->commit();
3516 return 1;
3517 }
3518 }
3519
3520
3521 // TODO: Move line related operations to CommonObjectLine?
3522
3523 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3533 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3534 {
3535 // phpcs:enable
3536 if (!$this->table_element_line) {
3537 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3538 return -1;
3539 }
3540 if (!$this->fk_element) {
3541 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3542 return -1;
3543 }
3544
3545 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3546 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3547 $fieldposition = 'position';
3548 }
3549
3550 // Count number of lines to reorder (according to choice $renum)
3551 $nl = 0;
3552 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3553 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3554 if (!$renum) {
3555 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = 0";
3556 }
3557 if ($renum) {
3558 $sql .= " AND " . $this->db->sanitize($fieldposition) . " <> 0";
3559 }
3560
3561 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3562 $resql = $this->db->query($sql);
3563 if ($resql) {
3564 $row = $this->db->fetch_row($resql);
3565 $nl = $row[0];
3566 } else {
3567 dol_print_error($this->db);
3568 }
3569 if ($nl > 0) {
3570 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3571 $rows = array();
3572
3573 // We first search all lines that are parent lines (for multilevel details lines)
3574 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3575 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3576 if ($fk_parent_line) {
3577 $sql .= ' AND fk_parent_line IS NULL';
3578 }
3579 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC, rowid " . $this->db->sanitize($rowidorder);
3580
3581 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3582 $resql = $this->db->query($sql);
3583 if ($resql) {
3584 $i = 0;
3585 $num = $this->db->num_rows($resql);
3586 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3587 while ($i < $num) {
3588 $row = $this->db->fetch_row($resql);
3589 $rows[] = $row[0]; // Add parent line into array rows
3590 if ($fk_parent_line) {
3591 $children = $this->getChildrenOfLine($row[0], $grandchild);
3592 }
3593 if (!empty($children)) {
3594 foreach ($children as $child) {
3595 array_push($rows, $child);
3596 }
3597 }
3598 $i++;
3599 }
3600
3601 // Now we set a new number for each lines (parent and children with children included into parent tree)
3602 if (!empty($rows)) {
3603 foreach ($rows as $key => $row) {
3604 $this->updateRangOfLine($row, ($key + 1));
3605 }
3606 }
3607 } else {
3608 dol_print_error($this->db);
3609 }
3610 }
3611 return 1;
3612 }
3613
3621 public function getChildrenOfLine($id, $includealltree = 0)
3622 {
3623 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3624 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3625 $fieldposition = 'position';
3626 }
3627
3628 $rows = array();
3629
3630 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3631 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3632 $sql .= ' AND fk_parent_line = '.((int) $id);
3633 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC";
3634
3635 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3636
3637 $resql = $this->db->query($sql);
3638 if (!$resql || $this->db->num_rows($resql) <= 0) {
3639 return array();
3640 }
3641
3642 while ($row = $this->db->fetch_row($resql)) {
3643 $rows[] = $row[0];
3644 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3645 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3646 }
3647 }
3648 return $rows;
3649 }
3650
3651 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3659 public function line_up($rowid, $fk_parent_line = true)
3660 {
3661 // phpcs:enable
3662 $this->line_order(false, 'ASC', $fk_parent_line);
3663
3664 // Get rang of line
3665 $rang = $this->getRangOfLine($rowid);
3666
3667 // Update position of line
3668 $this->updateLineUp($rowid, $rang);
3669 }
3670
3671 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3679 public function line_down($rowid, $fk_parent_line = true)
3680 {
3681 // phpcs:enable
3682 $this->line_order(false, 'ASC', $fk_parent_line);
3683
3684 // Get rang of line
3685 $rang = $this->getRangOfLine($rowid);
3686
3687 // Get max value for rang
3688 $max = $this->line_max();
3689
3690 // Update position of line
3691 $this->updateLineDown($rowid, $rang, $max);
3692 }
3693
3701 public function updateRangOfLine($rowid, $rang)
3702 {
3703 global $hookmanager;
3704 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3705 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3706 $fieldposition = 'position';
3707 }
3708
3709 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3710 $sql .= ' WHERE rowid = '.((int) $rowid);
3711 if ($this->id > 0 && !empty($this->fk_element)) {
3712 // The line must belong to the object we reorder lines of
3713 $sql .= " AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3714 }
3715
3716 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3717 if (!$this->db->query($sql)) {
3718 dol_print_error($this->db);
3719 return -1;
3720 }
3721
3722 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3723 $action = '';
3724 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3725 return 1;
3726 }
3727
3728 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3735 public function line_ajaxorder($rows)
3736 {
3737 // phpcs:enable
3738 $num = count($rows);
3739 for ($i = 0; $i < $num; $i++) {
3740 $this->updateRangOfLine($rows[$i], ($i + 1));
3741 }
3742 }
3743
3751 public function updateLineUp($rowid, $rang)
3752 {
3753 if ($rang <= 1) {
3754 return -1;
3755 }
3756
3757 $fieldposition = 'rang';
3758 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3759 $fieldposition = 'position';
3760 }
3761
3762 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3763 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3764 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang - 1));
3765 if (!$this->db->query($sql)) {
3766 $this->error = $this->db->lasterror();
3767 return -1;
3768 }
3769
3770 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang - 1));
3771 $sql .= ' WHERE rowid = '.((int) $rowid);
3772 if (!$this->db->query($sql)) {
3773 $this->error = $this->db->lasterror();
3774 return -1;
3775 }
3776
3777 return 1;
3778 }
3779
3788 public function updateLineDown($rowid, $rang, $max)
3789 {
3790 if ($rang >= $max) {
3791 return -1;
3792 }
3793
3794 $fieldposition = 'rang';
3795 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3796 $fieldposition = 'position';
3797 }
3798
3799 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3800 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3801 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang + 1));
3802 if (!$this->db->query($sql)) {
3803 $this->error = $this->db->lasterror();
3804 return -1;
3805 }
3806
3807 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang + 1));
3808 $sql .= ' WHERE rowid = '.((int) $rowid);
3809 if (!$this->db->query($sql)) {
3810 $this->error = $this->db->lasterror();
3811 return -1;
3812 }
3813
3814 return 1;
3815 }
3816
3823 public function getRangOfLine($rowid)
3824 {
3825 $fieldposition = 'rang';
3826 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3827 $fieldposition = 'position';
3828 }
3829
3830 $sql = "SELECT " . $this->db->sanitize($fieldposition) . " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3831 $sql .= " WHERE rowid = ".((int) $rowid);
3832
3833 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3834 $resql = $this->db->query($sql);
3835 if (!$resql) {
3836 return 0;
3837 }
3838
3839 $row = $this->db->fetch_row($resql);
3840 return $row[0];
3841 }
3842
3849 public function getIdOfLine($rang)
3850 {
3851 $fieldposition = 'rang';
3852 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3853 $fieldposition = 'position';
3854 }
3855
3856 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3857 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3858 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = ".((int) $rang);
3859 $resql = $this->db->query($sql);
3860 if (!$resql) {
3861 return 0;
3862 }
3863
3864 $row = $this->db->fetch_row($resql);
3865 return $row[0];
3866 }
3867
3877 public function roundQtyToPackaging($qty, $packaging)
3878 {
3879 if (empty($packaging) || !is_numeric($packaging) || (float) $packaging <= 0) {
3880 return $qty;
3881 }
3882 $sign = ((float) $qty < 0 ? -1 : 1);
3883 $absqty = abs((float) $qty);
3884 if ($absqty < (float) $packaging) {
3885 return $sign * (float) $packaging;
3886 }
3887 if ((float) price2num(fmod($absqty, (float) $packaging), 'MS')) {
3888 $coeff = intval($absqty / (float) $packaging) + 1;
3889 return $sign * (float) price2num((float) $packaging * $coeff, 'MS');
3890 }
3891 return $qty;
3892 }
3893
3894 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3901 public function line_max($fk_parent_line = 0)
3902 {
3903 // phpcs:enable
3904 $positionfield = 'rang';
3905 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3906 $positionfield = 'position';
3907 }
3908
3909 $sql = "SELECT max(".$this->db->sanitize($positionfield).") FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3910 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3911
3912 // Search the last rang with fk_parent_line
3913 if ($fk_parent_line) {
3914 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3915 }
3916
3917 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3918 $resql = $this->db->query($sql);
3919 if ($resql) {
3920 $row = $this->db->fetch_row($resql);
3921
3922 if ($fk_parent_line) {
3923 if (!empty($row[0])) {
3924 return $row[0];
3925 } else {
3926 return $this->getRangOfLine($fk_parent_line);
3927 }
3928 } else {
3929 return $row[0];
3930 }
3931 }
3932
3933 return 0;
3934 }
3935
3936 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3943 public function update_ref_ext($ref_ext)
3944 {
3945 // phpcs:enable
3946 if (!$this->table_element) {
3947 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3948 return -1;
3949 }
3950
3951 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3952 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3953 // @phan-suppress-next-line PhanUndeclaredProperty
3954 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3955
3956 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3957 if ($this->db->query($sql)) {
3958 $this->ref_ext = $ref_ext;
3959 return 1;
3960 } else {
3961 $this->error = $this->db->error();
3962 return -1;
3963 }
3964 }
3965
3966 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3975 public function update_note($note, $suffix = '', $notrigger = 0)
3976 {
3977 // phpcs:enable
3978 global $user;
3979
3980 if (!$this->table_element) {
3981 $this->error = 'update_note was called on object with property table_element not defined';
3982 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3983 return -1;
3984 }
3985 if (!in_array($suffix, array('', '_public', '_private'))) {
3986 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3987 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3988 return -2;
3989 }
3990
3991 $newsuffix = $suffix;
3992
3993 // Special case
3994 if ($this->table_element == 'product' && $newsuffix == '_private') {
3995 $newsuffix = '';
3996 }
3997 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3998 $fieldusermod = "fk_user_mod";
3999 } elseif ($this->table_element == 'ecm_files') {
4000 $fieldusermod = "fk_user_m";
4001 } else {
4002 $fieldusermod = "fk_user_modif";
4003 }
4004 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
4005 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
4006 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
4007 $sql .= " WHERE rowid = ".((int) $this->id);
4008
4009 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
4010 if ($this->db->query($sql)) {
4011 if ($suffix == '_public') {
4012 $this->note_public = $note;
4013 } elseif ($suffix == '_private') {
4014 $this->note_private = $note;
4015 } else {
4016 $this->note = $note; // deprecated
4017 $this->note_private = $note;
4018 }
4019 if (empty($notrigger)) {
4020 // TODO Use the $this->TRIGGER_PREFIX when implemented
4021 switch ($this->element) {
4022 case 'societe':
4023 $triggerName = 'COMPANY_MODIFY';
4024 break;
4025 case 'facture':
4026 $triggerName = 'BILL_MODIFY';
4027 break;
4028 case 'invoice_supplier':
4029 $triggerName = 'BILL_SUPPLIER_MODIFY';
4030 break;
4031 case 'facturerec':
4032 $triggerName = 'BILLREC_MODIFIY';
4033 break;
4034 case 'expensereport':
4035 $triggerName = 'EXPENSE_REPORT_MODIFY';
4036 break;
4037 default:
4038 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
4039 }
4040 $ret = $this->call_trigger($triggerName, $user);
4041 if ($ret < 0) {
4042 return -1;
4043 }
4044 }
4045 return 1;
4046 } else {
4047 $this->error = $this->db->lasterror();
4048 return -1;
4049 }
4050 }
4051
4052 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4061 public function update_note_public($note)
4062 {
4063 // phpcs:enable
4064 return $this->update_note($note, '_public');
4065 }
4066
4067 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4078 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4079 {
4080 // phpcs:enable
4081 global $conf, $hookmanager, $action;
4082
4083 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4084 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4085 if ($reshook > 0) {
4086 return 1; // replacement code
4087 } elseif ($reshook < 0) {
4088 return -1; // failure
4089 } // reshook = 0 => execute normal code
4090
4091 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4092 $isElementForSupplier = false;
4093 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4094 $MODULE = "";
4095 if ($this->element == 'propal') {
4096 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4097 } elseif ($this->element == 'commande' || $this->element == 'order') {
4098 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4099 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4100 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4101 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4102 $isElementForSupplier = true;
4103 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4104 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4105 $isElementForSupplier = true;
4106 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4107 } elseif ($this->element == 'supplier_proposal') {
4108 $isElementForSupplier = true;
4109 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4110 }
4111 if ($isElementForSupplier) {
4112 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4113 }
4114
4115 if (!empty($MODULE)) {
4116 if (getDolGlobalString($MODULE)) {
4117 $modsactivated = explode(',', getDolGlobalString($MODULE));
4118 foreach ($modsactivated as $mod) {
4119 if (isModEnabled($mod)) {
4120 return 1; // update was disabled by specific setup
4121 }
4122 }
4123 }
4124 }
4125
4126 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4127
4128 $forcedroundingmode = $roundingadjust;
4129 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4130 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4131 } elseif ($forcedroundingmode == 'auto') {
4132 $forcedroundingmode = '0';
4133 }
4134
4135 $error = 0;
4136
4137 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4138
4139 // Define constants to find lines to sum (field name into the table_element_line not into table_element)
4140 $fieldtva = 'total_tva';
4141 $fieldlocaltax1 = 'total_localtax1';
4142 $fieldlocaltax2 = 'total_localtax2';
4143 $fieldup = 'subprice';
4144 $base_price_type = 'HT';
4145 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4146 $fieldtva = 'tva';
4147 $fieldup = 'pu_ht';
4148 }
4149 if ($this->element == 'invoice_supplier_rec') {
4150 $fieldup = 'pu_ht';
4151 }
4152 if ($this->element == 'expensereport') {
4153 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4154 // 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
4155 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4156 $forcedroundingmode = '0';
4157 $fieldup = 'value_unit';
4158 $base_price_type = 'TTC';
4159 }
4160
4161 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4162 $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,";
4163 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4164 $sql .= ' info_bits, product_type,';
4165 if ($this->element == 'expensereport') {
4166 $sql .= ' comments as description';
4167 } else {
4168 $sql .= ' description';
4169 }
4170 if ($this->table_element_line == 'facturedet') {
4171 $sql .= ', situation_percent';
4172 }
4173 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4174 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
4175 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
4176 if ($exclspec) {
4177 $product_field = 'product_type';
4178 if ($this->table_element_line == 'contratdet') {
4179 $product_field = ''; // contratdet table has no product_type field
4180 }
4181 if ($product_field) {
4182 $sql .= " AND ".$this->db->sanitize($product_field)." <> 9";
4183 }
4184 }
4185 $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
4186
4187 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4188
4189 $resql = $this->db->query($sql);
4190 if ($resql) {
4191 $this->total_ht = 0;
4192 $this->total_tva = 0;
4193 $this->total_localtax1 = 0;
4194 $this->total_localtax2 = 0;
4195 $this->total_ttc = 0;
4196 $total_ht_by_vats = array();
4197 $total_tva_by_vats = array();
4198 $total_ttc_by_vats = array();
4199 $this->multicurrency_total_ht = 0;
4200 $this->multicurrency_total_tva = 0;
4201 $this->multicurrency_total_ttc = 0;
4202
4203 $this->db->begin();
4204
4205 $num = $this->db->num_rows($resql);
4206 $i = 0;
4207 while ($i < $num) {
4208 $obj = $this->db->fetch_object($resql);
4209
4210 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4211 $parameters = array('fk_element' => $obj->rowid);
4212 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4213
4214 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'
4215 // This part of code is to fix data. We should not call it too often.
4216 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4217 $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);
4218
4219 $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.
4220 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4221 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4222 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4223
4224 if ($diff_on_current_total) {
4225 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4226 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4227 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4228 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4229 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4230 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);
4231 $resqlfix = $this->db->query($sqlfix);
4232 if (!$resqlfix) {
4233 dol_print_error($this->db, 'Failed to update line');
4234 }
4235 $obj->total_tva = $tmpcal[1];
4236 $obj->total_ttc = $tmpcal[2];
4237 $obj->multicurrency_total_tva = $tmpcal[17];
4238 $obj->multicurrency_total_ttc = $tmpcal[18];
4239 } elseif ($diff_when_using_price_ht) {
4240 // 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
4241 // 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),
4242 // so we continue only if price HT are same.
4243 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4244 // 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
4245 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4246 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4247 // 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,
4248 // 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.
4249 // 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.
4250 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4251 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4252 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4253 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4254
4255 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);
4256
4257 $resqlfix = $this->db->query($sqlfix);
4258 if (!$resqlfix) {
4259 dol_print_error($this->db, 'Failed to update line');
4260 }
4261 $obj->total_tva = $tmpcal[1];
4262 $obj->total_ttc = $tmpcal[2];
4263 $obj->multicurrency_total_tva = $tmpcal[17];
4264 $obj->multicurrency_total_ttc = $tmpcal[18];
4265 }
4266 }
4267 }
4268 }
4269
4270 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4271 $this->total_tva += $obj->total_tva;
4272 $this->total_localtax1 += $obj->total_localtax1;
4273 $this->total_localtax2 += $obj->total_localtax2;
4274 $this->total_ttc += $obj->total_ttc;
4275 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4276 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4277 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4278
4279 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4280 $total_ht_by_vats[$obj->vatrate] = 0;
4281 }
4282 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4283 $total_tva_by_vats[$obj->vatrate] = 0;
4284 }
4285 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4286 $total_ttc_by_vats[$obj->vatrate] = 0;
4287 }
4288 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4289 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4290 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4291
4292 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4293 if ($base_price_type == 'TTC') {
4294 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4295 } else {
4296 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4297 }
4298 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4299 //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";
4300 if ($diff) {
4301 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4302 if (abs((float) $diff) > $maxdiff) {
4303 // If error is more than 10 times the accuracy of rounding. This should not happen.
4304 $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.';
4305 dol_syslog($errmsg, LOG_WARNING);
4306 $this->error = $errmsg;
4307 $error++;
4308 break;
4309 }
4310
4311 if ($base_price_type == 'TTC') {
4312 $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);
4313 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);
4314 } else {
4315 $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);
4316 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);
4317 }
4318
4319 $resqlfix = $this->db->query($sqlfix);
4320
4321 if (!$resqlfix) {
4322 dol_print_error($this->db, 'Failed to update line');
4323 }
4324
4325 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4326 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4327 if ($base_price_type == 'TTC') {
4328 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4329 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4330 } else {
4331 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4332 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4333 }
4334 }
4335 }
4336
4337 $i++;
4338 }
4339
4340 // Add revenue stamp to total
4341 // @phan-suppress-next-line PhanUndeclaredProperty
4342 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4343 // @phan-suppress-next-line PhanUndeclaredProperty
4344 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4345
4346 // Situations totals
4347 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
4348 '@phan-var-force Facture $this';
4349 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
4350 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4351 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4352 $prev_sits = $this->get_prev_sits();
4353
4354 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4355 $this->total_ht -= $sit->total_ht;
4356 $this->total_tva -= $sit->total_tva;
4357 $this->total_localtax1 -= $sit->total_localtax1;
4358 $this->total_localtax2 -= $sit->total_localtax2;
4359 $this->total_ttc -= $sit->total_ttc;
4360 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4361 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4362 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4363 }
4364 }
4365 }
4366 }
4367
4368 // Clean total
4369 $this->total_ht = (float) price2num($this->total_ht);
4370 $this->total_tva = (float) price2num($this->total_tva);
4371 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4372 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4373 $this->total_ttc = (float) price2num($this->total_ttc);
4374
4375 $this->db->free($resql);
4376
4377 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4378 $fieldht = 'total_ht';
4379 $fieldtva = 'tva';
4380 $fieldlocaltax1 = 'localtax1';
4381 $fieldlocaltax2 = 'localtax2';
4382 $fieldttc = 'total_ttc';
4383 // Specific code for backward compatibility with old field names
4384 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4385 $fieldtva = 'total_tva';
4386 }
4387
4388 if (!$error && empty($nodatabaseupdate)) {
4389 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4390 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4391 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4392 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4393 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4394 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4395 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4396 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4397 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4398 $sql .= " WHERE rowid = ".((int) $this->id);
4399
4400 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4401 $resql = $this->db->query($sql);
4402
4403 if (!$resql) {
4404 $error++;
4405 $this->error = $this->db->lasterror();
4406 $this->errors[] = $this->db->lasterror();
4407 }
4408 }
4409
4410 if (!$error) {
4411 $this->db->commit();
4412 return 1;
4413 } else {
4414 $this->db->rollback();
4415 return -1;
4416 }
4417 } else {
4418 dol_print_error($this->db, 'Bad request in update_price');
4419 return -1;
4420 }
4421 }
4422
4423 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4434 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4435 {
4436 // phpcs:enable
4437 global $user, $hookmanager, $action;
4438 dol_syslog(__METHOD__, LOG_DEBUG);
4439
4440 if (empty($this->origin_type) && !empty($this->origin)) {
4441 $this->origin_type = $this->origin;
4442 }
4443 $origin = (!empty($origin) ? $origin : $this->origin_type);
4444 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4445 $f_user = isset($f_user) ? $f_user : $user;
4446
4447 // Special case
4448 if ($origin == 'order') {
4449 $origin = 'commande';
4450 }
4451 if ($origin == 'invoice') {
4452 $origin = 'facture';
4453 }
4454 if ($origin == 'invoice_template') {
4455 $origin = 'facturerec';
4456 }
4457 if ($origin == 'supplierorder') {
4458 $origin = 'order_supplier';
4459 }
4460
4461 // Add module part to target type
4462 $targettype = $this->getElementType();
4463
4464 $parameters = array('targettype' => $targettype);
4465 // Hook for explicitly set the targettype if it must be different than $this->element
4466 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4467 if ($reshook > 0) {
4468 if (!empty($hookmanager->resArray['targettype'])) {
4469 $targettype = $hookmanager->resArray['targettype'];
4470 }
4471 }
4472
4473 $this->db->begin();
4474 $error = 0;
4475
4476 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4477 $sql .= "fk_source";
4478 $sql .= ", sourcetype";
4479 $sql .= ", fk_target";
4480 $sql .= ", targettype";
4481 $sql .= ") VALUES (";
4482 $sql .= ((int) $origin_id);
4483 $sql .= ", '" . $this->db->escape($origin) . "'";
4484 $sql .= ", " . ((int) $this->id);
4485 $sql .= ", '" . $this->db->escape($targettype) . "'";
4486 $sql .= ")";
4487
4488 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4489 if ($this->db->query($sql)) {
4490 // If we link a supplier order to a supplier invoice that already uses discounts coming from other supplier invoices
4491 // (deposit invoice / credit note used as payment), also link those source invoices to the same supplier order.
4492 // This helps keep "linked elements" consistent when totals are impacted by such discounts.
4493 if ($origin === 'order_supplier' && $targettype === 'invoice_supplier' && $this->element === 'invoice_supplier' && (int) $origin_id > 0 && (int) $this->id > 0) {
4494 $sourceinvoices = array();
4495
4496 $sqlsources = "SELECT DISTINCT rc.fk_invoice_supplier_source as rowid";
4497 $sqlsources .= " FROM ".$this->db->prefix()."societe_remise_except as rc";
4498 $sqlsources .= " WHERE rc.fk_invoice_supplier = ".((int) $this->id);
4499 $sqlsources .= " AND rc.fk_invoice_supplier_source IS NOT NULL";
4500
4501 $resqlsources = $this->db->query($sqlsources);
4502 if ($resqlsources) {
4503 while ($objsrc = $this->db->fetch_object($resqlsources)) {
4504 $srcid = (int) $objsrc->rowid;
4505 if ($srcid > 0 && $srcid !== (int) $this->id) {
4506 $sourceinvoices[$srcid] = $srcid;
4507 }
4508 }
4509 } else {
4510 dol_syslog(get_class($this)."::add_object_linked Failed to read supplier invoice discounts sources", LOG_WARNING);
4511 }
4512
4513 if (!empty($sourceinvoices)) {
4514 $existing = array();
4515 $sourcelist = implode(',', $sourceinvoices);
4516
4517 $sqlexists = "SELECT fk_target";
4518 $sqlexists .= " FROM ".$this->db->prefix()."element_element";
4519 $sqlexists .= " WHERE fk_source = ".((int) $origin_id);
4520 $sqlexists .= " AND sourcetype = '".$this->db->escape($origin)."'";
4521 $sqlexists .= " AND targettype = '".$this->db->escape($targettype)."'";
4522 $sqlexists .= " AND fk_target IN (".$this->db->sanitize($sourcelist).")";
4523
4524 $resqlexists = $this->db->query($sqlexists);
4525 if ($resqlexists) {
4526 while ($objexist = $this->db->fetch_object($resqlexists)) {
4527 $existing[(int) $objexist->fk_target] = 1;
4528 }
4529 } else {
4530 dol_syslog(get_class($this)."::add_object_linked Failed to read existing linked supplier invoices", LOG_WARNING);
4531 }
4532
4533 foreach ($sourceinvoices as $srcid) {
4534 if (!empty($existing[(int) $srcid])) {
4535 continue;
4536 }
4537
4538 $sqladd = "INSERT INTO " . $this->db->prefix() . "element_element (fk_source, sourcetype, fk_target, targettype)";
4539 $sqladd .= " VALUES (".((int) $origin_id).", '".$this->db->escape($origin)."', ".((int) $srcid).", '".$this->db->escape($targettype)."')";
4540 $this->db->query($sqladd); // Best-effort: do not fail original link action
4541 }
4542 }
4543 }
4544
4545 if (!$notrigger) {
4546 // Call trigger
4547 $this->context['link_origin'] = $origin;
4548 $this->context['link_origin_id'] = $origin_id;
4549
4550 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4551 if ($result < 0) {
4552 $error++;
4553 }
4554 // End call triggers
4555 }
4556 } else {
4557 $this->error = $this->db->lasterror();
4558 $error++;
4559 }
4560
4561 if (!$error) {
4562 $this->db->commit();
4563 return 1;
4564 } else {
4565 $this->db->rollback();
4566 return 0;
4567 }
4568 }
4569
4575 public function getElementType()
4576 {
4577 // 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.
4578 // 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).
4579 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4580 // Add module part to target type if object has $module property and isn't in core modules.
4581 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4582 }
4583
4584
4607 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4608 {
4609 global $hookmanager, $action;
4610
4611 // Important for pdf generation time reduction
4612 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4613 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4614 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4615 return 1;
4616 }
4617
4618 $this->linkedObjectsIds = array();
4619 $this->linkedObjects = array();
4620
4621 // Hook for allowing modules to completely alter the behavior of the method
4622 $parameters = array(
4623 'sourceid' => $sourceid,
4624 'sourcetype' => $sourcetype,
4625 'targetid' => $targetid,
4626 'targettype' => $targettype,
4627 'clause' => $clause,
4628 'alsosametype' => $alsosametype,
4629 'orderby' => $orderby,
4630 'loadalsoobjects' => $loadalsoobjects
4631 );
4632 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4633 if ($reshook > 0) {
4634 return $reshook;
4635 }
4636
4637 $justsource = false;
4638 $justtarget = false;
4639 $withtargettype = false;
4640 $withsourcetype = false;
4641
4642 // Hook for explicitly set the targettype if it must be differtent than $this->element
4643 if (is_object($hookmanager)) {
4644 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4645 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4646 if ($reshook > 0) {
4647 if (!empty($hookmanager->resArray['sourcetype'])) {
4648 $sourcetype = $hookmanager->resArray['sourcetype'];
4649 }
4650 if (!empty($hookmanager->resArray['sourceid'])) {
4651 $sourceid = $hookmanager->resArray['sourceid'];
4652 }
4653 if (!empty($hookmanager->resArray['targettype'])) {
4654 $targettype = $hookmanager->resArray['targettype'];
4655 }
4656 if (!empty($hookmanager->resArray['targetid'])) {
4657 $targetid = $hookmanager->resArray['targetid'];
4658 }
4659 }
4660 }
4661
4662 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4663 $justsource = true; // the source (id and type) is a search criteria
4664 if (!empty($targettype)) {
4665 $withtargettype = true;
4666 }
4667 }
4668 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4669 $justtarget = true; // the target (id and type) is a search criteria
4670 if (!empty($sourcetype)) {
4671 $withsourcetype = true;
4672 }
4673 }
4674
4675 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4676 $targetid = (!empty($targetid) ? $targetid : $this->id);
4677 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4678 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4679
4680 /*if (empty($sourceid) && empty($targetid))
4681 {
4682 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4683 return -1;
4684 }*/
4685
4686 // Links between objects are stored in table element_element
4687 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4688 $sql .= " FROM ".$this->db->prefix()."element_element";
4689 $sql .= " WHERE ";
4690 if ($justsource || $justtarget) {
4691 if ($justsource) {
4692 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4693 if ($withtargettype) {
4694 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4695 }
4696 } elseif ($justtarget) {
4697 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4698 if ($withsourcetype) {
4699 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4700 }
4701 }
4702 } else {
4703 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4704 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4705 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4706 $this->linkedObjectsFullLoaded[$this->id] = true;
4707 }
4708 }
4709 $sql .= $this->db->order($orderby);
4710
4711 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4712
4713 $resql = $this->db->query($sql);
4714 if ($resql) {
4715 $num = $this->db->num_rows($resql);
4716 $i = 0;
4717 while ($i < $num) {
4718 $obj = $this->db->fetch_object($resql);
4719 if ($justsource || $justtarget) {
4720 if ($justsource) {
4721 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4722 } elseif ($justtarget) {
4723 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4724 }
4725 } else {
4726 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4727 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4728 }
4729 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4730 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4731 }
4732 }
4733 $i++;
4734 }
4735
4736 if (!empty($this->linkedObjectsIds)) {
4737 $tmparray = $this->linkedObjectsIds;
4738 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4739 $element_properties = getElementProperties($objecttype);
4740 $element = $element_properties['element'];
4741 $classPath = $element_properties['classpath'];
4742 $classFile = $element_properties['classfile'];
4743 $className = $element_properties['classname'];
4744 $module = $element_properties['module'];
4745
4746 // Here $module, $classFile and $className are set, we can use them.
4747 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4748 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4749 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4750 if (class_exists($className)) {
4751 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4752 $object = new $className($this->db);
4753 '@phan-var-force CommonObject $object';
4754 $ret = $object->fetch($objectid);
4755 if ($ret > 0) {
4756 $this->linkedObjects[$objecttype][$i] = $object;
4757 }
4758 }
4759 }
4760 }
4761 } else {
4762 unset($this->linkedObjectsIds[$objecttype]);
4763 }
4764 }
4765 }
4766 return 1;
4767 } else {
4768 dol_print_error($this->db);
4769 return -1;
4770 }
4771 }
4772
4779 public function clearObjectLinkedCache()
4780 {
4781 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4782 unset($this->linkedObjectsFullLoaded[$this->id]);
4783 }
4784
4785 return 1;
4786 }
4787
4800 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4801 {
4802 global $user;
4803 $updatesource = false;
4804 $updatetarget = false;
4805 $f_user = isset($f_user) ? $f_user : $user;
4806 dol_syslog(__METHOD__, LOG_DEBUG);
4807
4808 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4809 $updatesource = true;
4810 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4811 $updatetarget = true;
4812 }
4813
4814 $this->db->begin();
4815 $error = 0;
4816
4817 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4818 if ($updatesource) {
4819 $sql .= "fk_source = " . ((int) $sourceid);
4820 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4821 $sql .= " WHERE fk_target = " . ((int) $this->id);
4822 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4823 } elseif ($updatetarget) {
4824 $sql .= "fk_target = " . ((int) $targetid);
4825 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4826 $sql .= " WHERE fk_source = " . ((int) $this->id);
4827 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4828 }
4829
4830 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4831 if ($this->db->query($sql)) {
4832 if (!$notrigger) {
4833 // Call trigger
4834 $this->context['link_source_id'] = $sourceid;
4835 $this->context['link_source_type'] = $sourcetype;
4836 $this->context['link_target_id'] = $targetid;
4837 $this->context['link_target_type'] = $targettype;
4838
4839 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4840 if ($result < 0) {
4841 $error++;
4842 }
4843 // End call triggers
4844 }
4845 } else {
4846 $this->error = $this->db->lasterror();
4847 $error++;
4848 }
4849
4850 if (!$error) {
4851 $this->db->commit();
4852 return 1;
4853 } else {
4854 $this->db->rollback();
4855 return -1;
4856 }
4857 }
4858
4872 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4873 {
4874 global $user;
4875 $deletesource = false;
4876 $deletetarget = false;
4877 $f_user = isset($f_user) ? $f_user : $user;
4878
4879 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4880 $deletesource = true;
4881 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4882 $deletetarget = true;
4883 }
4884
4885 $element = $this->getElementType();
4886 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4887 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4888 $targetid = (!empty($targetid) ? $targetid : $this->id);
4889 $targettype = (!empty($targettype) ? $targettype : $element);
4890 $this->db->begin();
4891 $error = 0;
4892
4893 if (!$notrigger) {
4894 // Call trigger
4895 $this->context['link_id'] = $rowid;
4896 $this->context['link_source_id'] = $sourceid;
4897 $this->context['link_source_type'] = $sourcetype;
4898 $this->context['link_target_id'] = $targetid;
4899 $this->context['link_target_type'] = $targettype;
4900
4901 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4902 if ($result < 0) {
4903 $error++;
4904 }
4905 // End call triggers
4906 }
4907
4908 if (!$error) {
4909 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4910 $sql .= " WHERE";
4911 if ($rowid > 0) {
4912 $sql .= " rowid = " . ((int) $rowid);
4913 } else {
4914 if ($deletesource) {
4915 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4916 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4917 } elseif ($deletetarget) {
4918 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4919 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4920 } else {
4921 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4922 $sql .= " OR";
4923 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4924 }
4925 }
4926
4927 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4928 if (!$this->db->query($sql)) {
4929 $this->error = $this->db->lasterror();
4930 $this->errors[] = $this->error;
4931 $error++;
4932 }
4933 }
4934
4935 if (!$error) {
4936 $this->db->commit();
4937 return 1;
4938 } else {
4939 $this->db->rollback();
4940 return 0;
4941 }
4942 }
4943
4953 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4954 {
4955 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4956 return -1;
4957 }
4958 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4959 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4960 }
4961
4962 global $db;
4963
4964 $sql = "SELECT ".$db->sanitize($field_select)." FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4965 $resql = $db->query($sql);
4966
4967 $TRes = array();
4968 if (!empty($resql)) {
4969 while ($res = $db->fetch_object($resql)) {
4970 $TRes[] = $res->{$field_select};
4971 }
4972 }
4973
4974 return $TRes;
4975 }
4976
4985 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4986 {
4987 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4988 return -1;
4989 }
4990
4991 global $db;
4992
4993 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4994 $resql = $db->query($sql);
4995 $n = 0;
4996 if ($resql) {
4997 $res = $db->fetch_object($resql);
4998 if ($res) {
4999 $n = $res->nb;
5000 }
5001 }
5002
5003 return $n;
5004 }
5005
5014 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
5015 {
5016 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
5017 return -1;
5018 }
5019
5020 global $db;
5021
5022 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
5023 $resql = $db->query($sql);
5024
5025 if (empty($resql)) {
5026 return 0;
5027 }
5028
5029 return 1;
5030 }
5031
5043 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
5044 {
5045 global $user;
5046
5047 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
5048
5049 $elementId = (!empty($elementId) ? $elementId : $this->id);
5050 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
5051
5052 $this->db->begin();
5053
5054 if (empty($fieldstatus)) {
5055 if ($elementTable == 'facture_rec') {
5056 $fieldstatus = "suspended";
5057 }
5058 if ($elementTable == 'mailing') {
5059 $fieldstatus = "statut";
5060 }
5061 if ($elementTable == 'cronjob') {
5062 $fieldstatus = "status";
5063 }
5064 if ($elementTable == 'user') {
5065 $fieldstatus = "statut";
5066 }
5067 if ($elementTable == 'expensereport') {
5068 $fieldstatus = "fk_statut";
5069 }
5070 if ($elementTable == 'receptiondet_batch') {
5071 $fieldstatus = "status";
5072 }
5073 if ($elementTable == 'prelevement_bons') {
5074 $fieldstatus = "statut";
5075 }
5076 if ($elementTable == 'bank_account') {
5077 $fieldstatus = "clos";
5078 }
5079 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
5080 $fieldstatus = 'status';
5081 }
5082 // If still empty
5083 if (empty($fieldstatus)) {
5084 $fieldstatus = 'fk_statut';
5085 }
5086 }
5087
5088 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
5089 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
5090 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
5091 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
5092 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
5093 $sql .= ", fk_user_valid = ".((int) $user->id);
5094 }
5095 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
5096 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
5097 }
5098 if ($status == 1 && in_array($elementTable, array('inventory'))) {
5099 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
5100 }
5101 $sql .= " WHERE rowid = ".((int) $elementId);
5102 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
5103
5104 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
5105 $resql = $this->db->query($sql);
5106 if ($resql) {
5107 $error = 0;
5108
5109 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
5110
5111 if ($nb_rows_affected > 0) {
5112 if (empty($trigkey)) {
5113 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5114 if ($this->element == 'supplier_proposal' && $status == 2) {
5115 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5116 }
5117 if ($this->element == 'supplier_proposal' && $status == 3) {
5118 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5119 }
5120 if ($this->element == 'supplier_proposal' && $status == 4) {
5121 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5122 }
5123 if ($this->element == 'fichinter' && $status == 3) {
5124 $trigkey = 'FICHINTER_CLASSIFY_DONE';
5125 }
5126 if ($this->element == 'fichinter' && $status == 2) {
5127 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5128 }
5129 if ($this->element == 'fichinter' && $status == 1) {
5130 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5131 }
5132 }
5133
5134 $this->context = array_merge($this->context, array('newstatus' => $status));
5135
5136 if ($trigkey && $trigkey != 'none') {
5137 $this->oldcopy = dol_clone($this);
5138
5139 // Call trigger
5140 $result = $this->call_trigger($trigkey, $user);
5141 if ($result < 0) {
5142 $error++;
5143 }
5144 // End call triggers
5145 }
5146 } else {
5147 // The status was probably already good. We do nothing more, no triggers.
5148 }
5149
5150 if (!$error) {
5151 $this->db->commit();
5152
5153 if (empty($savElementId)) {
5154 // If the element we update is $this (so $elementId was provided as null)
5155 if ($fieldstatus == 'dispute_status') {
5156 $this->dispute_status = $status;
5157 } elseif ($fieldstatus == 'tosell') {
5158 $this->status = $status;
5159 } elseif ($fieldstatus == 'tobuy') {
5160 $this->status_buy = $status; // @phpstan-ignore-line
5161 } elseif ($fieldstatus == 'tobatch') {
5162 $this->status_batch = $status; // @phpstan-ignore-line
5163 } else {
5164 $this->status = $status;
5165 }
5166 }
5167
5168 return 1;
5169 } else {
5170 $this->db->rollback();
5171 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5172 return -1;
5173 }
5174 } else {
5175 $this->error = $this->db->lasterror();
5176 $this->db->rollback();
5177 return -1;
5178 }
5179 }
5180
5181
5189 public function getCanvas($id = 0, $ref = '')
5190 {
5191 if (empty($id) && empty($ref)) {
5192 return 0;
5193 }
5194 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5195 return 0; // To increase speed. Not enabled by default.
5196 }
5197
5198 // Clean parameters
5199 $ref = trim($ref);
5200
5201 $sql = "SELECT rowid, canvas";
5202 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5203 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5204 if (!empty($id)) {
5205 $sql .= " AND rowid = ".((int) $id);
5206 }
5207 if (!empty($ref)) {
5208 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5209 }
5210
5211 $resql = $this->db->query($sql);
5212 if ($resql) {
5213 $obj = $this->db->fetch_object($resql);
5214 if ($obj) {
5215 $this->canvas = $obj->canvas;
5216 return 1;
5217 } else {
5218 return 0;
5219 }
5220 } else {
5221 dol_print_error($this->db);
5222 return -1;
5223 }
5224 }
5225
5226
5233 public function getSpecialCode($lineid)
5234 {
5235 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
5236 $sql .= " WHERE rowid = ".((int) $lineid);
5237 $resql = $this->db->query($sql);
5238 if ($resql) {
5239 $row = $this->db->fetch_row($resql);
5240 return (!empty($row[0]) ? $row[0] : 0);
5241 }
5242
5243 return 0;
5244 }
5245
5254 public function isObjectUsed($id = 0, $entity = 0)
5255 {
5256 global $langs;
5257
5258 if (empty($id)) {
5259 $id = $this->id;
5260 }
5261
5262 // Check parameters
5263 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5264 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5265 return -1;
5266 }
5267
5268 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5269 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5270 $tmparray = array_keys($this->childtables);
5271 if (is_numeric($tmparray[0])) {
5272 $arraytoscan = array_flip($this->childtables);
5273 }
5274
5275 // Test on all child tables to scan if child exists
5276 $haschild = 0;
5277 foreach ($arraytoscan as $table => $element) {
5278 //print $id.'-'.$table.'-'.$elementname.'<br>';
5279
5280 // Check if module is enabled (to avoid error if tables of module not created)
5281 if (isset($element['enabled']) && !empty($element['enabled'])) {
5282 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5283 if (empty($enabled)) {
5284 continue;
5285 }
5286 }
5287
5288 // Check if element can be deleted
5289 $sql = "SELECT COUNT(*) as nb";
5290 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($table)." as c";
5291 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5292 $sql .= ", ".$this->db->prefix().$this->db->sanitize($element['parent'])." as p";
5293 }
5294 if (!empty($element['fk_element'])) {
5295 $sql .= " WHERE c.".$this->db->sanitize($element['fk_element'])." = ".((int) $id);
5296 } else {
5297 $sql .= " WHERE c.".$this->db->sanitize($this->fk_element)." = ".((int) $id);
5298 }
5299 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5300 $sql .= " AND c.".$this->db->sanitize($element['parentkey'])." = p.rowid";
5301 }
5302 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5303 $sql .= " AND c.".$this->db->sanitize($element['parenttypefield'])." = '".$this->db->escape($element['parenttypevalue'])."'";
5304 }
5305 if (!empty($entity)) {
5306 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5307 $sql .= " AND p.entity = ".((int) $entity);
5308 } else {
5309 $sql .= " AND c.entity = ".((int) $entity);
5310 }
5311 }
5312 //var_dump($table, $element, $sql);
5313
5314 $resql = $this->db->query($sql);
5315 if ($resql) {
5316 $obj = $this->db->fetch_object($resql);
5317 if ($obj->nb > 0) {
5318 $langs->load("errors");
5319 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5320 $haschild += (int) $obj->nb;
5321 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5322 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5323 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5324 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5325 } else { // new usage: $element['name']=Translation key
5326 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5327 }
5328 break; // We found at least one, we stop here
5329 }
5330 } else {
5331 $this->errors[] = $this->db->lasterror();
5332 return -1;
5333 }
5334 }
5335
5336 if ($haschild > 0) {
5337 $this->errors[] = "ErrorRecordHasChildren";
5338 return $haschild;
5339 } else {
5340 return 0;
5341 }
5342 }
5343
5350 public function hasProductsOrServices($predefined = -1)
5351 {
5352 $nb = 0;
5353
5354 foreach ($this->lines as $key => $val) {
5355 $qualified = 0;
5356 if ($predefined == -1) {
5357 $qualified = 1;
5358 }
5359 if ($predefined == 1 && $val->fk_product > 0) {
5360 $qualified = 1;
5361 }
5362 if ($predefined == 0 && $val->fk_product <= 0) {
5363 $qualified = 1;
5364 }
5365 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5366 $qualified = 1;
5367 }
5368 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5369 $qualified = 1;
5370 }
5371 if ($qualified) {
5372 $nb++;
5373 }
5374 }
5375 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5376 return $nb;
5377 }
5378
5384 public function getTotalDiscount()
5385 {
5386 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5387 $total_discount = 0.00;
5388
5389 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5390 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5391 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
5392
5393 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5394 $resql = $this->db->query($sql);
5395 if ($resql) {
5396 $num = $this->db->num_rows($resql);
5397 $i = 0;
5398 while ($i < $num) {
5399 $obj = $this->db->fetch_object($resql);
5400
5401 $pu_ht = $obj->pu_ht;
5402 $qty = $obj->qty;
5403 $total_ht = $obj->total_ht;
5404
5405 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5406 $total_discount += $total_discount_line;
5407
5408 $i++;
5409 }
5410 }
5411
5412 //print $total_discount; exit;
5413 return (float) price2num($total_discount);
5414 }
5415
5416 return null;
5417 }
5418
5419
5426 public function getTotalWeightVolume()
5427 {
5428 $totalWeight = 0;
5429 $totalVolume = 0;
5430 // defined for shipment only
5431 $totalOrdered = 0;
5432 // defined for shipment only
5433 $totalToShip = 0;
5434
5435 if (empty($this->lines)) {
5436 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5437 }
5438
5439 foreach ($this->lines as $line) {
5440 if (isset($line->qty_asked)) {
5441 $totalOrdered += $line->qty_asked; // defined for shipment only
5442 }
5443 if (isset($line->qty_shipped)) {
5444 $totalToShip += $line->qty_shipped; // defined for shipment only
5445 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5446 if (empty($totalToShip)) {
5447 $totalToShip = 0;
5448 }
5449 $totalToShip += $line->qty; // defined for reception only
5450 }
5451
5452 // Define qty, weight, volume, weight_units, volume_units
5453 if ($this->element == 'shipping') {
5454 // for shipments
5455 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5456 } else {
5457 $qty = $line->qty ? $line->qty : 0;
5458 }
5459
5460 $weight = !empty($line->weight) ? $line->weight : 0;
5461 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5462 $volume = !empty($line->volume) ? $line->volume : 0;
5463 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5464
5465 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5466 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5467 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5468 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5469
5470 $weightUnit = 0;
5471 $volumeUnit = 0;
5472 if (!empty($weight_units)) {
5473 $weightUnit = $weight_units;
5474 }
5475 if (!empty($volume_units)) {
5476 $volumeUnit = $volume_units;
5477 }
5478
5479 if (empty($totalWeight)) {
5480 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5481 }
5482 if (empty($totalVolume)) {
5483 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5484 }
5485
5486 //var_dump($line->volume_units);
5487 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5488 $trueWeightUnit = pow(10, $weightUnit);
5489 $totalWeight += $weight * $qty * $trueWeightUnit;
5490 } else {
5491 if ($weight_units == 99) {
5492 // conversion 1 Pound = 0.45359237 KG
5493 $trueWeightUnit = 0.45359237;
5494 $totalWeight += $weight * $qty * $trueWeightUnit;
5495 } elseif ($weight_units == 98) {
5496 // conversion 1 Ounce = 0.0283495 KG
5497 $trueWeightUnit = 0.0283495;
5498 $totalWeight += $weight * $qty * $trueWeightUnit;
5499 } else {
5500 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5501 }
5502 }
5503 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5504 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5505 $trueVolumeUnit = pow(10, $volumeUnit);
5506 //print $line->volume;
5507 $totalVolume += $volume * $qty * $trueVolumeUnit;
5508 } else {
5509 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5510 }
5511 }
5512
5513 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5514 }
5515
5516
5522 public function setExtraParameters()
5523 {
5524 $this->db->begin();
5525
5526 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5527 $extraparams = dol_trunc($extraparams, 250);
5528
5529 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5530 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5531 $sql .= " WHERE rowid = ".((int) $this->id);
5532
5533 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5534 $resql = $this->db->query($sql);
5535 if (!$resql) {
5536 $this->error = $this->db->lasterror();
5537 $this->db->rollback();
5538 return -1;
5539 } else {
5540 $this->db->commit();
5541 return 1;
5542 }
5543 }
5544
5545
5546 // --------------------
5547 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5548 // --------------------
5549
5550 /* This is to show add lines */
5551
5561 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5562 {
5563 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5564
5565 // Line extrafield
5566 if (!is_object($extrafields)) {
5567 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5568 $extrafields = new ExtraFields($this->db);
5569 }
5570 $extrafields->fetch_name_optionals_label($this->table_element_line);
5571
5572 // Output template part (modules that overwrite templates must declare this into descriptor)
5573 // Use global variables + $dateSelector + $seller and $buyer
5574 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5575 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5576 foreach ($dirtpls as $module => $reldir) {
5577 if (!empty($module)) {
5578 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5579 } else {
5580 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5581 }
5582
5583 if (empty($conf->file->strict_mode)) {
5584 $res = @include $tpl;
5585 } else {
5586 $res = include $tpl; // for debug
5587 }
5588 if ($res) {
5589 break;
5590 }
5591 }
5592 }
5593
5594
5595
5596 /* This is to show array of line of details */
5597
5598
5613 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5614 {
5615 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5616 // TODO We should not use global var for this
5617 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5618
5619 // Define $usemargins (used by objectline_xxx.tpl.php files)
5620 $usemargins = 0;
5621 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5622 $usemargins = 1;
5623 }
5624
5625 $num = count($this->lines);
5626
5627 // Line extrafield
5628 if (!is_object($extrafields)) {
5629 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5630 $extrafields = new ExtraFields($this->db);
5631 }
5632 $extrafields->fetch_name_optionals_label($this->table_element_line);
5633
5634 if (method_exists($this, 'loadExpeditions')) {
5635 // 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.
5636 $this->loadExpeditions();
5637 }
5638
5639 $parameters = array();
5640 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5641 if (empty($reshook)) {
5642 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5643 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5644 if (empty($reshook)) {
5645 // Output template part (modules that overwrite templates must declare this into descriptor)
5646 // Use global variables + $dateSelector + $seller and $buyer
5647 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5648 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5649 foreach ($dirtpls as $module => $reldir) {
5650 $res = 0;
5651 if (!empty($module)) {
5652 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5653 } else {
5654 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5655 }
5656 if (file_exists($tpl)) {
5657 if (empty($conf->file->strict_mode)) {
5658 $res = @include $tpl;
5659 } else {
5660 $res = include $tpl; // for debug
5661 }
5662 }
5663 if ($res) {
5664 break;
5665 }
5666 }
5667 }
5668
5669 $i = 0;
5670
5671 print "<!-- begin printObjectLines() -->\n";
5672 foreach ($this->lines as $line) {
5673 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5674 //$line->fetch_optionals();
5675
5676 if (is_object($hookmanager)) {
5677 if (empty($line->fk_parent_line)) {
5678 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5679 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5680 } else {
5681 $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);
5682 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5683 }
5684 }
5685 if (empty($reshook)) {
5686 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5687 }
5688
5689 $i++;
5690 }
5691 print "<!-- end printObjectLines() -->\n";
5692 }
5693 }
5694
5712 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5713 {
5714 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5715 global $form;
5716 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5717
5718 // var used into tpl
5719 $text = '';
5720 $description = '';
5721
5722 // Line in view mode
5723 if ($action != 'editline' || $selected != $line->id) {
5724 // Product
5725 if (!empty($line->fk_product) && $line->fk_product > 0) {
5726 $product_static = new Product($this->db);
5727 $product_static->fetch($line->fk_product);
5728
5729 $product_static->ref = (string) $line->ref; //can change ref in hook
5730 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5731
5732 $text = $product_static->getNomUrl(1);
5733
5734 // Define output language and label
5735 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5736 // @phan-suppress-next-line PhanUndeclaredProperty
5737 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5738 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5739 return;
5740 }
5741
5742 $prod = new Product($this->db);
5743 $prod->fetch($line->fk_product);
5744
5745 $outputlangs = $langs;
5746 $newlang = '';
5747 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5748 $newlang = GETPOST('lang_id', 'aZ09');
5749 }
5750 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5751 $newlang = $this->thirdparty->default_lang; // To use language of customer
5752 }
5753 if (!empty($newlang)) {
5754 $outputlangs = new Translate("", $conf);
5755 $outputlangs->setDefaultLang($newlang);
5756 }
5757
5758 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5759 } else {
5760 $label = $line->product_label;
5761 }
5762
5763 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5764 $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.
5765 }
5766
5767 // Recalculate unit price with tax if not defined
5768 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)
5769 // So we calculate an estimated value just to show something on screen.
5770 // Not: the unit price is always for 100% of line, it is not a prorata of situation invoice (when total is)
5771 if ($line->remise_percent != 100) {
5772 $line->subprice_ttc = (float) price2num($line->total_ttc * ($line->situation_percent ? 100 / $line->situation_percent : 1) / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5773 } else {
5774 // Other method is less accurate
5775 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5776 }
5777 }
5778 $line->pu_ttc = $line->subprice_ttc; // deprecated
5779
5780 // Output template part (modules that overwrite templates must declare this into descriptor)
5781 // Use global variables + $dateSelector + $seller and $buyer
5782 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5783
5784 $qty_shipped = 0;
5785 if (isset($this->expeditions[$line->id])) {
5786 $qty_shipped = $this->expeditions[$line->id];
5787 }
5788 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5789
5790 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5791 foreach ($dirtpls as $module => $reldir) {
5792 $res = 0;
5793 if (!empty($module)) {
5794 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5795 } else {
5796 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5797 }
5798 //var_dump($tpl);
5799 if (file_exists($tpl)) {
5800 if (empty($conf->file->strict_mode)) {
5801 $res = @include $tpl;
5802 } else {
5803 $res = include $tpl; // for debug
5804 }
5805 }
5806 if ($res) {
5807 break;
5808 }
5809 }
5810 }
5811
5812 // Line in update mode
5813 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5814 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5815
5816 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5817 $line->pu_ttc = $line->subprice_ttc; // deprecated
5818
5819 // Output template part (modules that overwrite templates must declare this into descriptor)
5820 // Use global variables + $dateSelector + $seller and $buyer
5821 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5822 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5823 foreach ($dirtpls as $module => $reldir) {
5824 if (!empty($module)) {
5825 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5826 } else {
5827 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5828 }
5829
5830 if (empty($conf->file->strict_mode)) {
5831 $res = @include $tpl;
5832 } else {
5833 $res = include $tpl; // for debug
5834 }
5835 if ($res) {
5836 break;
5837 }
5838 }
5839 }
5840 }
5841
5842
5843 /* This is to show array of line of details of source object */
5844
5845
5856 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5857 {
5858 global $langs, $hookmanager, $form, $action;
5859
5860 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5861 print '<tr class="liste_titre">';
5862 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5863 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5864 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5865 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5866 if (isModEnabled("multicurrency")) {
5867 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5868 }
5869 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5870 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5871 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5872 }
5873 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5874 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5875 print '<td class="center">';
5876 print $form->showCheckAddButtons('checkforselect', 1);
5877 print '</td>';
5878 print '</tr>';
5879 $i = 0;
5880
5881 if (!empty($this->lines)) {
5882 foreach ($this->lines as $line) {
5883 $reshook = 0;
5884 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5885 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5886 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5887 if (!empty($line->fk_parent_line)) {
5888 $parameters['fk_parent_line'] = $line->fk_parent_line;
5889 }
5890 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5891 }
5892 if (empty($reshook)) {
5893 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5894 }
5895
5896 $i++;
5897 }
5898 }
5899 }
5900
5914 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5915 {
5916 global $langs, $conf;
5917
5918 //var_dump($line);
5919 if (!empty($line->date_start)) {
5920 // @phan-suppress-next-line PhanUndeclaredProperty
5921 $date_start = $line->date_start;
5922 } else {
5923 $date_start = $line->date_debut_prevue;
5924 if ($line->date_debut_reel) {
5925 $date_start = $line->date_debut_reel;
5926 }
5927 }
5928 if (!empty($line->date_end)) {
5929 // @phan-suppress-next-line PhanUndeclaredProperty
5930 $date_end = $line->date_end;
5931 } else {
5932 $date_end = $line->date_fin_prevue;
5933 if ($line->date_fin_reel) {
5934 $date_end = $line->date_fin_reel;
5935 }
5936 }
5937
5938 // Set thevalue into ->tpl[] array.
5939 $this->tpl['id'] = $line->id;
5940
5941 $this->tpl['label'] = '';
5942 if (!empty($line->fk_parent_line)) {
5943 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5944 }
5945
5946 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5947 $discount = new DiscountAbsolute($this->db);
5948 if (property_exists($this, 'socid')) {
5949 $discount->fk_soc = $this->socid;
5950 $discount->socid = $this->socid;
5951 }
5952 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5953 } elseif (!empty($line->fk_product)) {
5954 $productstatic = new Product($this->db);
5955 $productstatic->id = $line->fk_product;
5956 $productstatic->ref = (string) $line->ref;
5957 $productstatic->type = $line->fk_product_type;
5958 if (empty($productstatic->ref)) {
5959 $line->fetch_product();
5960 $productstatic = $line->product;
5961 }
5962
5963 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5964 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5965 // Dates
5966 if ($line->product_type == 1 && ($date_start || $date_end)) {
5967 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5968 }
5969 } else {
5970 $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"')));
5971 if (!empty($line->desc)) {
5972 $this->tpl['label'] .= $line->desc;
5973 } else {
5974 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5975 }
5976
5977 // Dates
5978 if ($line->product_type == 1 && ($date_start || $date_end)) {
5979 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5980 }
5981 }
5982
5983 if (!empty($line->desc)) {
5984 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5985 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5986 $discount = new DiscountAbsolute($this->db);
5987 $discount->fetch($line->fk_remise_except);
5988 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5989 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5990 $discount = new DiscountAbsolute($this->db);
5991 $discount->fetch($line->fk_remise_except);
5992 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5993 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5994 $discount = new DiscountAbsolute($this->db);
5995 $discount->fetch($line->fk_remise_except);
5996 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5997 } elseif ($line->desc == '(EXCESS PAID)') {
5998 $discount = new DiscountAbsolute($this->db);
5999 $discount->fetch($line->fk_remise_except);
6000 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
6001 } else {
6002 $this->tpl['description'] = dol_trunc($line->desc, 60);
6003 }
6004 } else {
6005 $this->tpl['description'] = '&nbsp;';
6006 }
6007
6008 // VAT Rate
6009 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
6010 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
6011 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
6012 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
6013 }
6014
6015 $this->tpl['price'] = price($line->subprice);
6016 $this->tpl['total_ht'] = price($line->total_ht);
6017 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
6018 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
6019 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
6020 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
6021 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
6022 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
6023 }
6024 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
6025
6026 // Is the line strike or not
6027 $this->tpl['strike'] = 0;
6028 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
6029 $this->tpl['strike'] = 1;
6030 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
6031 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
6032 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
6033 // Without the guard, printing origin lines from a PO into a vendor
6034 // invoice fatals (#37663). Other tpl files already follow this pattern.
6035 $this->tpl['strike'] = 1;
6036 }
6037
6038 // Output template part (modules that overwrite templates must declare this into descriptor)
6039 // Use global variables + $dateSelector + $seller and $buyer
6040 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
6041 foreach ($dirtpls as $module => $reldir) {
6042 if (!empty($module)) {
6043 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
6044 } else {
6045 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
6046 }
6047
6048 if (empty($conf->file->strict_mode)) {
6049 $res = @include $tpl;
6050 } else {
6051 $res = include $tpl; // for debug
6052 }
6053 if ($res) {
6054 break;
6055 }
6056 }
6057 }
6058
6059
6060 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6072 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
6073 {
6074 // phpcs:enable
6075 global $user;
6076 $this->db->begin();
6077
6078 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
6079 $sql .= "resource_id";
6080 $sql .= ", resource_type";
6081 $sql .= ", element_id";
6082 $sql .= ", element_type";
6083 $sql .= ", busy";
6084 $sql .= ", mandatory";
6085 $sql .= ") VALUES (";
6086 $sql .= ((int) $resource_id);
6087 $sql .= ", '".$this->db->escape($resource_type)."'";
6088 $sql .= ", '".$this->db->escape((string) $this->id)."'";
6089 $sql .= ", '".$this->db->escape($this->element)."'";
6090 $sql .= ", '".$this->db->escape((string) $busy)."'";
6091 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
6092 $sql .= ")";
6093
6094 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
6095 if ($this->db->query($sql)) {
6096 if (!$notrigger) {
6097 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
6098 if ($result < 0) {
6099 $this->db->rollback();
6100 return -1;
6101 }
6102 }
6103 $this->db->commit();
6104 return 1;
6105 } else {
6106 $this->error = $this->db->lasterror();
6107 $this->db->rollback();
6108 return 0;
6109 }
6110 }
6111
6112 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6121 public function delete_resource($rowid, $element = '', $notrigger = 0)
6122 {
6123 // phpcs:enable
6124 global $user;
6125
6126 $this->db->begin();
6127
6128 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6129 $sql .= " WHERE rowid = ".((int) $rowid);
6130
6131 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6132
6133 $resql = $this->db->query($sql);
6134 if (!$resql) {
6135 $this->error = $this->db->lasterror();
6136 $this->db->rollback();
6137 return -1;
6138 } else {
6139 if (!$notrigger) {
6140 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6141 if ($result < 0) {
6142 $this->db->rollback();
6143 return -1;
6144 }
6145 }
6146 $this->db->commit();
6147 return 1;
6148 }
6149 }
6150
6151
6157 public function __clone()
6158 {
6159 // Force a copy of this->lines, otherwise it will point to same object.
6160 if (isset($this->lines) && is_array($this->lines)) {
6161 $nboflines = count($this->lines);
6162 for ($i = 0; $i < $nboflines; $i++) {
6163 if (is_object($this->lines[$i])) {
6164 $this->lines[$i] = clone $this->lines[$i];
6165 }
6166 }
6167 }
6168 }
6169
6183 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6184 {
6185 global $conf, $langs, $hookmanager, $action;
6186
6187 $srctemplatepath = '';
6188
6189 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6190 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6191
6192 if (!empty($reshook)) {
6193 return $reshook;
6194 }
6195
6196 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6197
6198 if (empty($modele)) {
6199 $this->error = 'BadValueForParameterModele';
6200 return -1;
6201 }
6202
6203 // Increase limit for PDF build
6204 $err = error_reporting();
6205 error_reporting(0);
6206 @set_time_limit(120);
6207 error_reporting($err);
6208
6209 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6210 $tmp = explode(':', $modele, 2);
6211 $saved_model = $modele;
6212 if (!empty($tmp[1])) {
6213 $modele = $tmp[0];
6214 $srctemplatepath = $tmp[1];
6215
6216 if (!preg_match('/^'.preg_quote(DOL_DATA_ROOT, '/').'\/(ecm|doctemplates)/', $srctemplatepath)) {
6217 $this->error = 'BadDirForTemplateFile';
6218 return -1;
6219 }
6220 }
6221
6222 // Search template files
6223 $file = '';
6224 $classname = '';
6225 $filefound = '';
6226 $dirmodels = array('/');
6227 if (is_array($conf->modules_parts['models'])) {
6228 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6229 }
6230 foreach ($dirmodels as $reldir) {
6231 foreach (array('doc', 'pdf') as $prefix) {
6232 if (in_array(get_class($this), array('Adherent'))) {
6233 // Member module use prefix_modele.class.php
6234 $file = $prefix."_".$modele.".class.php";
6235 } else {
6236 // Other module use prefix_modele.modules.php
6237 $file = $prefix."_".$modele.".modules.php";
6238 }
6239
6240 $file = dol_sanitizeFileName($file);
6241
6242 // We check if the file exists
6243 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6244 if (file_exists($file)) {
6245 $filefound = $file;
6246 $classname = $prefix.'_'.$modele;
6247 break;
6248 }
6249 }
6250 if ($filefound) {
6251 break;
6252 }
6253 }
6254
6255 if ($filefound === '' || $classname === '') {
6256 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6257 $this->errors[] = $this->error;
6258 dol_syslog($this->error, LOG_ERR);
6259 return -1;
6260 }
6261
6262 // Sanitize $filefound
6263 $filefound = dol_sanitizePathName($filefound);
6264
6265 // If generator was found
6266 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6267
6268 require_once $filefound;
6269
6270 $obj = new $classname($this->db);
6271
6272 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6273 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6274 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6275 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6276 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6277 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6278 // ModelePDFStock|ModelePDFStockTransfer|
6279 // ModeleDon|ModelePDFTask|
6280 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6281 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6282 '@phan-var-force ModelePDFMember $obj';
6283 // '@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';
6284
6285 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6286 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6287 $varfortemplatedir = $obj->scandir;
6288 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6289 $dirtoscan = getDolGlobalString($varfortemplatedir);
6290
6291 $listoffiles = array();
6292
6293 // Now we add first model found in directories scanned
6294 $listofdir = explode(',', $dirtoscan);
6295 foreach ($listofdir as $key => $tmpdir) {
6296 $tmpdir = trim($tmpdir);
6297 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6298 if (!$tmpdir) {
6299 unset($listofdir[$key]);
6300 continue;
6301 }
6302 if (is_dir($tmpdir)) {
6303 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6304 if (count($tmpfiles)) {
6305 $listoffiles = array_merge($listoffiles, $tmpfiles);
6306 }
6307 }
6308 }
6309
6310 if (count($listoffiles)) {
6311 foreach ($listoffiles as $record) {
6312 $srctemplatepath = $record['fullname'];
6313 break;
6314 }
6315 }
6316 }
6317
6318 if (empty($srctemplatepath)) {
6319 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6320 return -1;
6321 }
6322 }
6323
6324 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6325 if (!dol_is_file($srctemplatepath)) {
6326 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6327 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6328 return -1;
6329 }
6330 }
6331
6332 // We save charset_output to restore it because write_file can change it if needed for
6333 // output format that does not support UTF8.
6334 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6335
6336 // update model_pdf in object
6337 $this->model_pdf = $saved_model;
6338
6339 try {
6340 if ($obj instanceof ModelePDFMember) {
6341 if ($this instanceof Adherent) {
6342 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6343 } else {
6344 $resultwritefile = -1;
6345 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6346 }
6347 } elseif ($obj instanceof ModeleDon) {
6348 // Only 3 arguments
6349 if ($this instanceof Don) {
6350 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6351 } else {
6352 $resultwritefile = -1;
6353 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6354 }
6355 } else {
6356 // TODO: Try to set type above again
6357 '@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';
6358 $this->context['moreparams'] = $moreparams;
6359 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref); // @phan-suppress-current-line PhanTypeMismatchArgument
6360 }
6361 } catch (\Throwable $e) {
6362 $resultwritefile = -1;
6363 $this->error = $e->getMessage();
6364 dol_syslog("Exception in write_file() for ".get_class($this).": ".$e->getMessage(), LOG_ERR);
6365 }
6366 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6367
6368
6369 if ($resultwritefile > 0) {
6370 $outputlangs->charset_output = $sav_charset_output;
6371
6372 // We delete old preview
6373 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6374 dol_delete_preview($this);
6375
6376 // Index file in database
6377 if (!empty($obj->result['fullpath'])) {
6378 $destfull = $obj->result['fullpath'];
6379
6380 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6381 $update_main_doc_field = 0;
6382 if (!empty($obj->update_main_doc_field)) {
6383 $update_main_doc_field = 1;
6384 }
6385
6386 // Check that the file exists, before indexing it.
6387 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6388 if (dol_is_file($destfull)) {
6389 $this->indexFile($destfull, $update_main_doc_field);
6390 }
6391 } else {
6392 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);
6393 }
6394
6395 // Success in building document. We build meta file.
6396 dol_meta_create($this);
6397
6398 $this->warnings = $obj->warnings;
6399
6400 return 1;
6401 } else {
6402 $outputlangs->charset_output = $sav_charset_output;
6403 if (empty($this->error)) {
6404 $this->error = $obj->error;
6405 }
6406 if (empty($this->errors)) {
6407 $this->errors = $obj->errors;
6408 }
6409 $this->warnings = $obj->warnings;
6410 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$this->error, LOG_ERR);
6411 return -1;
6412 }
6413 }
6414
6424 public function indexFile($destfull, $update_main_doc_field)
6425 {
6426 global $conf, $user;
6427
6428 $upload_dir = dirname($destfull);
6429 $destfile = basename($destfull);
6430 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6431
6432 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6433 $filename = basename($destfile);
6434 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6435 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6436
6437 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6438 $ecmfile = new EcmFiles($this->db);
6439 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6440
6441 // Set the public "share" key
6442 $setsharekey = false;
6443 if (
6444 !empty($this->TRIGGER_PREFIX)
6445 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6446 ) {
6447 $setsharekey = true;
6448 }
6449 // TODO Remove case covered by trigger prefix
6450 if ($this->element == 'propal' || $this->element == 'proposal') {
6451 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6452 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6453 }
6454 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6455 $setsharekey = true;
6456 }
6457 }
6458 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6459 $setsharekey = true;
6460 }
6461 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6462 $setsharekey = true;
6463 }
6464 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6465 $setsharekey = true;
6466 }
6467 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6468 $setsharekey = true;
6469 }
6470 if ($this->element == 'order_supplier' && getDolGlobalInt("SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6471 $setsharekey = true;
6472 }
6473 if ($this->element == 'invoice_supplier' && getDolGlobalInt("SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6474 $setsharekey = true;
6475 }
6476 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6477 $setsharekey = true;
6478 }
6479
6480 if ($setsharekey) {
6481 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6482 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6483 $ecmfile->share = getRandomPassword(true);
6484 }
6485 }
6486
6487 if ($result > 0) {
6488 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6489 $ecmfile->fullpath_orig = '';
6490 $ecmfile->gen_or_uploaded = 'generated';
6491 $ecmfile->description = ''; // indexed content
6492 $ecmfile->keywords = ''; // keyword content
6493 $ecmfile->src_object_type = $this->table_element;
6494 $ecmfile->src_object_id = $this->id;
6495 $result = $ecmfile->update($user);
6496 if ($result < 0) {
6497 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6498 return -1;
6499 }
6500 } else {
6501 $ecmfile->entity = $conf->entity;
6502 $ecmfile->filepath = $rel_dir;
6503 $ecmfile->filename = $filename;
6504 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6505 $ecmfile->fullpath_orig = '';
6506 $ecmfile->gen_or_uploaded = 'generated';
6507 $ecmfile->description = ''; // indexed content
6508 $ecmfile->keywords = ''; // keyword content
6509 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6510 $ecmfile->src_object_id = $this->id;
6511
6512 $result = $ecmfile->create($user);
6513 if ($result < 0) {
6514 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6515 return -1;
6516 }
6517 }
6518
6519 /*$this->result['fullname']=$destfull;
6520 $this->result['filepath']=$ecmfile->filepath;
6521 $this->result['filename']=$ecmfile->filename;*/
6522 //var_dump($obj->update_main_doc_field);exit;
6523
6524 if ($update_main_doc_field && !empty($this->table_element)) {
6525 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6526 $sql .= " WHERE rowid = ".((int) $this->id);
6527
6528 $resql = $this->db->query($sql);
6529 if (!$resql) {
6530 dol_print_error($this->db);
6531 return -1;
6532 } else {
6533 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6534 }
6535 }
6536 }
6537
6538 return 1;
6539 }
6540
6549 public function addThumbs($file, $quality = 50)
6550 {
6551 $file_osencoded = dol_osencode($file);
6552
6553 if (file_exists($file_osencoded)) {
6554 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6555
6556 $tmparraysize = getDefaultImageSizes();
6557 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6558 $maxheightsmall = $tmparraysize['maxheightsmall'];
6559 $maxwidthmini = $tmparraysize['maxwidthmini'];
6560 $maxheightmini = $tmparraysize['maxheightmini'];
6561 //$quality = $tmparraysize['quality'];
6562
6563 // Create small thumbs for company (Ratio is near 16/9)
6564 // Used on logon for example
6565 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6566
6567 // Create mini thumbs for company (Ratio is near 16/9)
6568 // Used on menu or for setup page for example
6569 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6570 }
6571 }
6572
6580 public function delThumbs($file)
6581 {
6582 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6583 dol_delete_file($imgThumbName);
6584 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6585 dol_delete_file($imgThumbName);
6586 }
6587
6588
6589 /* Functions common to commonobject and commonobjectline */
6590
6591 /* For default values */
6592
6606 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6607 {
6608 /* TODO Remove this. Must use now something like:
6609 $note_private = GETPOST('note_private', 'restricthtml');
6610 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6611 $note_private = $objectsrc->note_private;
6612 }
6613 */
6614
6615 // If param here has been posted, we use this value first.
6616 if (GETPOSTISSET($fieldname)) {
6617 return GETPOST($fieldname, $type, 3);
6618 }
6619
6620 if (isset($alternatevalue)) {
6621 return $alternatevalue;
6622 }
6623
6624 $newelement = $this->element;
6625 if ($newelement == 'facture') {
6626 $newelement = 'invoice';
6627 }
6628 if ($newelement == 'commande') {
6629 $newelement = 'order';
6630 }
6631 if (empty($newelement)) {
6632 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6633 return '';
6634 }
6635
6636 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6637 //var_dump($keyforfieldname);
6638 if (getDolGlobalString($keyforfieldname)) {
6639 return getDolGlobalString($keyforfieldname);
6640 }
6641
6642 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6643 // store content into $conf->cache['overwrite_default']
6644
6645 return '';
6646 }
6647
6648
6649 /* Functions for data in other language */
6650
6651
6661 {
6662 // To avoid SQL errors. Probably not the better solution though
6663 if (!$this->element) {
6664 return 0;
6665 }
6666 if (!($this->id > 0)) {
6667 return 0;
6668 }
6669 if (is_array($this->array_languages)) {
6670 return 1;
6671 }
6672
6673 $this->array_languages = array();
6674
6675 $element = $this->element;
6676 if ($element == 'categorie') {
6677 $element = 'categories'; // For compatibility
6678 }
6679
6680 // Request to get translation values for object
6681 $sql = "SELECT rowid, property, lang , value";
6682 $sql .= " FROM ".$this->db->prefix()."object_lang";
6683 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6684 $sql .= " AND fk_object = ".((int) $this->id);
6685
6686 $resql = $this->db->query($sql);
6687 if ($resql) {
6688 $numrows = $this->db->num_rows($resql);
6689 if ($numrows) {
6690 $i = 0;
6691 while ($i < $numrows) {
6692 $obj = $this->db->fetch_object($resql);
6693 $key = $obj->property;
6694 $value = $obj->value;
6695 $codelang = $obj->lang;
6696 $type = $this->fields[$key]['type'];
6697
6698 // we can add this attribute to object
6699 if (preg_match('/date/', $type)) {
6700 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6701 } else {
6702 $this->array_languages[$key][$codelang] = $value;
6703 }
6704
6705 $i++;
6706 }
6707 }
6708
6709 $this->db->free($resql);
6710
6711 if ($numrows) {
6712 return $numrows;
6713 } else {
6714 return 0;
6715 }
6716 } else {
6717 dol_print_error($this->db);
6718 return -1;
6719 }
6720 }
6721
6729 public function setValuesForExtraLanguages($onlykey = '')
6730 {
6731 // Get extra fields
6732 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6733 $tmparray = explode('-', $postfieldkey);
6734 if ($tmparray[0] != 'field') {
6735 continue;
6736 }
6737
6738 $element = $tmparray[1];
6739 $key = $tmparray[2];
6740 $codelang = $tmparray[3];
6741 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6742
6743 if (!empty($onlykey) && $key != $onlykey) {
6744 continue;
6745 }
6746 if ($element != $this->element) {
6747 continue;
6748 }
6749
6750 $key_type = $this->fields[$key]['type'];
6751
6752 $enabled = 1;
6753 if (isset($this->fields[$key]['enabled'])) {
6754 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6755 }
6756 /*$perms = 1;
6757 if (isset($this->fields[$key]['perms']))
6758 {
6759 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6760 }*/
6761 if (empty($enabled)) {
6762 continue;
6763 }
6764 //if (empty($perms)) continue;
6765
6766 if (in_array($key_type, array('date'))) {
6767 // Clean parameters
6768 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6769 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6770 } elseif (in_array($key_type, array('datetime'))) {
6771 // Clean parameters
6772 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6773 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6774 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6775 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6776 if (!empty($value_arr)) {
6777 $value_key = implode(',', $value_arr);
6778 } else {
6779 $value_key = '';
6780 }
6781 } elseif (in_array($key_type, array('price', 'double'))) {
6782 $value_arr = GETPOST($postfieldkey, 'alpha');
6783 $value_key = price2num($value_arr);
6784 } else {
6785 $value_key = GETPOST($postfieldkey);
6786 if (in_array($key_type, array('link')) && $value_key == '-1') {
6787 $value_key = '';
6788 }
6789 }
6790
6791 $this->array_languages[$key][$codelang] = $value_key;
6792
6793 /*if ($nofillrequired) {
6794 $langs->load('errors');
6795 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6796 return -1;
6797 }*/
6798 }
6799
6800 return 1;
6801 }
6802
6803
6804 /* Functions for extrafields */
6805
6812 public function fetchNoCompute($id)
6813 {
6814 global $conf;
6815
6816 $savDisableCompute = $conf->disable_compute;
6817 $conf->disable_compute = 1;
6818
6819 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6820
6821 $conf->disable_compute = $savDisableCompute;
6822
6823 return $ret;
6824 }
6825
6826 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6836 public function fetch_optionals($rowid = null, $optionsArray = null)
6837 {
6838 // phpcs:enable
6839 global $conf, $extrafields;
6840
6841 if (empty($rowid)) {
6842 $rowid = $this->id;
6843 }
6844 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6845 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6846 }
6847
6848 // To avoid SQL errors. Probably not the better solution though
6849 if (!$this->table_element) {
6850 return 0;
6851 }
6852 $this->array_options = array();
6853
6854 if (!is_array($optionsArray)) {
6855 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6856 if (!isset($extrafields) || !is_object($extrafields)) {
6857 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6858 $extrafields = new ExtraFields($this->db);
6859 }
6860
6861 // Load array of extrafields for elementype = $this->table_element
6862 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6863 $extrafields->fetch_name_optionals_label($this->table_element);
6864 }
6865 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6866 } else {
6867 global $extrafields;
6868 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6869 }
6870
6871 $table_element = $this->table_element;
6872 if ($table_element == 'categorie') {
6873 $table_element = 'categories'; // For compatibility
6874 }
6875
6876 // Request to get complementary values
6877 if (is_array($optionsArray) && count($optionsArray) > 0) {
6878 $sql = "SELECT rowid";
6879 foreach ($optionsArray as $name => $label) {
6880 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6881 $sql .= ", ".$this->db->sanitize($name);
6882 }
6883 // use geo sql function to read as text
6884 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6885 // TODO Add an abstraction method in the database driver
6886 $sql .= ", ST_AsWKT(".$this->db->sanitize($name).") as ".$this->db->sanitize($name);
6887 }
6888 }
6889 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6890 $sql .= " WHERE fk_object = ".((int) $rowid);
6891
6892 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6893 $resql = $this->db->query($sql);
6894 if ($resql) {
6895 $numrows = $this->db->num_rows($resql);
6896 if ($numrows) {
6897 $tab = $this->db->fetch_array($resql);
6898 foreach ($tab as $key => $value) {
6899 // 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)
6900 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6901 // we can add this attribute to object
6902 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6903 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6904 $this->array_options["options_".$key] = $this->db->jdate($value);
6905 } else {
6906 $this->array_options["options_".$key] = $value;
6907 }
6908
6909 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6910 }
6911 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6912 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6913 $this->array_options["options_".$key] = dolDecrypt($value);
6914 }
6915 }
6916 }
6917 } else {
6922 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6923 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6924 $this->array_options['options_' . $key] = null;
6925 }
6926 }
6927 }
6928
6929 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6930 // in the element's extrafields table)
6931 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6932 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6933 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6934 //var_dump($conf->disable_compute);
6935 if (empty($conf->disable_compute)) {
6936 // We set a global variable to $objectoffield so we can use it inside computed formula (must be before the assignment)
6937 global $objectoffield;
6938 $objectoffield = dol_clone($this, 2);
6939 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6940 }
6941 }
6942 }
6943 }
6944
6945 $this->db->free($resql);
6946
6947 if ($numrows) {
6948 return $numrows;
6949 } else {
6950 return 0;
6951 }
6952 } else {
6953 $this->errors[] = $this->db->lasterror();
6954 return -1;
6955 }
6956 }
6957 return 0;
6958 }
6959
6966 public function deleteExtraFields()
6967 {
6968 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6969 return 0;
6970 }
6971
6972 $this->db->begin();
6973
6974 $table_element = $this->table_element;
6975 if ($table_element == 'categorie') {
6976 $table_element = 'categories'; // For compatibility
6977 }
6978
6979 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6980
6981 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6982
6983 $resql = $this->db->query($sql_del);
6984 if (!$resql) {
6985 $this->error = $this->db->lasterror();
6986 $this->db->rollback();
6987 return -1;
6988 } else {
6989 $this->db->commit();
6990 return 1;
6991 }
6992 }
6993
7004 public function insertExtraFields($trigger = '', $userused = null)
7005 {
7006 global $langs, $extrafields, $user;
7007
7008 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
7009 return 0;
7010 }
7011
7012 if (empty($userused)) {
7013 $userused = $user;
7014 }
7015
7016 $error = 0;
7017
7018 // Check parameters
7019 $langs->load('admin');
7020 if (!isset($extrafields) || !is_object($extrafields)) {
7021 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7022 $extrafields = new ExtraFields($this->db);
7023 }
7024 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
7025
7026 // Eliminate copied source object extra fields that do not exist in target object
7027 $new_array_options = array();
7028 foreach ($this->array_options as $key => $value) {
7029 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
7030 $new_array_options[$key] = $value;
7031 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
7032 $new_array_options['options_'.$key] = $value;
7033 }
7034 }
7035
7036 foreach ($new_array_options as $key => $value) {
7037 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7038 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7039 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
7040 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7041 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7042 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7043 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7044 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
7045 /*
7046 $attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7047 $enabled = 0;
7048 if (!empty($extrafields->attributes[$this->table_element]['enabled'][$key])) {
7049 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
7050 }
7051 */
7052
7053 // If we clone, we have to clean unique extrafields to prevent duplicates.
7054 // If we clone, we have to clean extrafields having "empty on clone" option on.
7055 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
7056 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
7057 $new_array_options[$key] = null;
7058 continue;
7059 }
7060
7061 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
7062 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
7063 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
7064 $new_array_options[$key] = null;
7065 continue;
7066 }
7067
7068 // Similar code than into insertExtraFields
7069 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'.
7070 $v = $this->array_options[$key];
7071 if (ExtraFields::isEmptyValue($v, $attributeType)) {
7072 $langs->load("errors");
7073 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
7074 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7075 return -1;
7076 }
7077 }
7078
7079 if (!empty($attrfieldcomputed)) {
7080 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7081 global $objectoffield; // We set a global variable to $objectoffield so
7082 $objectoffield = $this; // we can use it inside the computed formula
7083 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7084 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7085 $new_array_options[$key] = $value;
7086 } else {
7087 $new_array_options[$key] = null;
7088 }
7089 continue;
7090 }
7091
7092 switch ($attributeType) {
7093 case 'int':
7094 case 'duration':
7095 case 'stars':
7096 if (!is_numeric($value) && $value != '') {
7097 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7098 return -1;
7099 } elseif ($value === '') {
7100 $new_array_options[$key] = null;
7101 }
7102 break;
7103 case 'boolean':
7104 if ($value === '' || $value === false || $value === null) {
7105 $new_array_options[$key] = null;
7106 }
7107 break;
7108 case 'price':
7109 case 'double':
7110 $value = price2num($value);
7111 if (!is_numeric($value) && $value != '') {
7112 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7113 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7114 return -1;
7115 } elseif ($value == '') {
7116 $value = null;
7117 }
7118 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7119 $new_array_options[$key] = $value;
7120 break;
7121 /*case 'select': // Not required, we chose value='0' for undefined values
7122 if ($value=='-1')
7123 {
7124 $this->array_options[$key] = null;
7125 }
7126 break;*/
7127 case 'password':
7128 $algo = '';
7129 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7130 // If there is an encryption choice, we use it to encrypt data before insert
7131 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7132 $algo = reset($tmparrays);
7133 if ($algo != '') {
7134 //global $action; // $action may be 'create', 'update', 'update_extras'...
7135 //var_dump($action);
7136 //var_dump($this->oldcopy);exit;
7137 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
7138 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7139 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7140 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7141 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7142 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7143 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7144 } else {
7145 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7146 }
7147 } else {
7148 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7149 }
7150 } else {
7151 // If value has changed
7152 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7153 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7154 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7155 } else {
7156 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7157 }
7158 } else {
7159 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7160 }
7161 }
7162 } else {
7163 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7164 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7165 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7166 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7167 } else {
7168 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7169 }
7170 }
7171 } else {
7172 // No encryption
7173 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7174 }
7175 } else { // Common usage
7176 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7177 }
7178 break;
7179 case 'date':
7180 case 'datetime':
7181 // If data is a string instead of a timestamp, we convert it
7182 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7183 $this->array_options[$key] = strtotime($this->array_options[$key]);
7184 }
7185 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7186 break;
7187 case 'datetimegmt':
7188 // If data is a string instead of a timestamp, we convert it
7189 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7190 $this->array_options[$key] = strtotime($this->array_options[$key]);
7191 }
7192 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7193 break;
7194 case 'link':
7195 $param_list = array_keys($attributeParam['options']);
7196 // 0 : ObjectName
7197 // 1 : classPath
7198 $InfoFieldList = explode(":", $param_list[0]);
7199 dol_include_once($InfoFieldList[1]);
7200 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7201 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7202 $new_array_options[$key] = '';
7203 } elseif ($value) {
7204 $object = new $InfoFieldList[0]($this->db);
7205 '@phan-var-force CommonObject $object';
7206
7207 $objectId = 0;
7208
7209 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7210 if (is_numeric($value)) {
7211 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7212 } else {
7213 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7214 }
7215
7216 $obj = $this->db->getRow($sqlFetchObject);
7217
7218 // getRow() returns an object on success, int 0 when the query succeeded but
7219 // returned no row, and false on SQL failure. Testing "!== false" let the 0
7220 // through as a success: $obj->rowid on an int is null, $res was set to 1 and
7221 // null was stored in the column while a success was reported.
7222 if (is_object($obj)) {
7223 $objectId = $obj->rowid;
7224 $res = 1;
7225 } else {
7226 $res = -1;
7227 }
7228
7229 if ($res > 0) {
7230 $new_array_options[$key] = $objectId;
7231 } else {
7232 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7233 return -1;
7234 }
7235 }
7236 } else {
7237 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7238 }
7239 break;
7240 case 'checkbox':
7241 case 'chkbxlst':
7242 if (is_array($this->array_options[$key])) {
7243 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7244 } else {
7245 $new_array_options[$key] = $this->array_options[$key];
7246 }
7247 break;
7248 }
7249 }
7250
7251 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7252 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7253
7254 foreach ($new_array_options as $key => $newValue) {
7255 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7256 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7257 if ($attributeType === 'separate') {
7258 // separator extrafields don't have data per object so they don't have a comlumn in the database
7259 continue;
7260 }
7261 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7262 // Add field of attribute
7263 if (! $geoDataType) {
7264 // not a geodata type
7265 if ($newValue != '') {
7266 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7267 } else {
7268 $sqlColumnValues[$attributeKey] = 'null';
7269 }
7270 continue;
7271 }
7272 if (empty($newValue)) {
7273 $sqlColumnValues[$attributeKey] = 'null';
7274 continue;
7275 }
7276 if (preg_match('/error/i', $newValue)) {
7277 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7278 $sqlColumnValues[$attributeKey] = 'null';
7279 continue;
7280 }
7281
7282 // Geodata type: Text must be a WKT string. Examples:
7283 // - point => "POINT(15 20)"
7284 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7285 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7286 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7287 $sqlColumnValues[$attributeKey] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')"; // @phan-suppress-current-line SqlInjection
7288 }
7289
7290 $this->db->begin();
7291
7292 $table_element = $this->table_element;
7293 if ($table_element == 'categorie') {
7294 $table_element = 'categories'; // For compatibility
7295 }
7296 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7297
7298 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7299
7300 $linealreadyfound = 0;
7301 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7302 $resql = $this->db->query($sql);
7303 if ($resql) {
7304 $tmpobj = $this->db->fetch_object($resql);
7305 if ($tmpobj) {
7306 $linealreadyfound = $tmpobj->nb;
7307 }
7308 }
7309
7310 // if the extrafields row already exists for the object, we update it
7311 if ($linealreadyfound) {
7312 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7313 if (empty($sqlColumnValues)) {
7314 // No column of the target element to update. This happens when the object was created from
7315 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7316 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7317 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7318 // that would produce an invalid SQL statement.
7319 $this->db->commit();
7320 return 1;
7321 }
7322 $sqlColumnValueString = implode(
7323 ',',
7328 array_map(function ($key) use ($sqlColumnValues) {
7329 return "{$key} = {$sqlColumnValues[$key]}";
7330 }, array_keys($sqlColumnValues))
7331 );
7332 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7333 } else {
7334 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7335 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7336 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7337 if (isset($sqlColumnValues[$key])) {
7338 // extrafield value already provided: no need to add it
7339 continue;
7340 }
7341 // default value: empty string, except for 'int', 'double' and 'price'
7342 $sqlColumnValues[$key] = "''";
7343 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7344 $sqlColumnValues[$key] = 0;
7345 }
7346 }
7347 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7348 $sqlValues = implode(',', array_values($sqlColumnValues));
7349 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7350 }
7351
7352 $resql = $this->db->query($sql);
7353 if (!$resql) {
7354 $this->error = $this->db->lasterror();
7355 $error++;
7356 }
7357
7358 // Update also the user of last modification in parent table
7359 if (!$error && !empty($this->fields['fk_user_modif'])) { // @phan-suppress-current-line PhanTypeMismatchProperty
7360 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7361 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7362 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7363 $this->db->query($sql);
7364 }
7365
7366 if (!$error && $trigger) {
7367 // Call trigger
7368 $this->context = array('extrafieldaddupdate' => 1);
7369 $result = $this->call_trigger($trigger, $userused);
7370 if ($result < 0) {
7371 $error++;
7372 }
7373 // End call trigger
7374 }
7375
7376 if ($error) {
7377 $this->db->rollback();
7378 return -1;
7379 } else {
7380 $this->db->commit();
7381 return 1;
7382 }
7383 }
7384
7396 public function insertExtraLanguages($trigger = '', $userused = null)
7397 {
7398 global $langs, $user;
7399
7400 if (empty($userused)) {
7401 $userused = $user;
7402 }
7403
7404 $error = 0;
7405
7406 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7407 return 0; // For avoid conflicts if trigger used
7408 }
7409
7410 if (is_array($this->array_languages)) {
7411 $new_array_languages = $this->array_languages;
7412
7413 foreach ($new_array_languages as $key => $value) {
7414 $attributeKey = $key;
7415 $attributeType = $this->fields[$attributeKey]['type'];
7416 $attributeLabel = $this->fields[$attributeKey]['label'];
7417
7418 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7419 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7420
7421 switch ($attributeType) {
7422 case 'int':
7423 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7424 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7425 return -1;
7426 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7427 $new_array_languages[$key] = null;
7428 }
7429 break;
7430 case 'double':
7431 $value = price2num((string) $value);
7432 if (!is_numeric($value) && $value != '') {
7433 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7434 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7435 return -1;
7436 } elseif ($value == '') {
7437 $new_array_languages[$key] = null;
7438 } else {
7439 $new_array_languages[$key] = $value;
7440 }
7441 break;
7442 /*case 'select': // Not required, we chose value='0' for undefined values
7443 if ($value=='-1')
7444 {
7445 $this->array_options[$key] = null;
7446 }
7447 break;*/
7448 }
7449 }
7450
7451 $this->db->begin();
7452
7453 $table_element = $this->table_element;
7454 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7455 $table_element = 'categories'; // For compatibility
7456 }
7457
7458 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7459
7460 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7461 foreach ($langcodearray as $langcode => $value) {
7462 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7463 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7464 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7465 $this->db->query($sql_del);
7466
7467 if ($value !== '') {
7468 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7469 $sql .= ") VALUES (".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7470 $sql .= ")";
7471
7472 $resql = $this->db->query($sql);
7473 if (!$resql) {
7474 $this->error = $this->db->lasterror();
7475 $error++;
7476 break;
7477 }
7478 }
7479 }
7480 }
7481
7482 if (!$error && $trigger) {
7483 // Call trigger
7484 $this->context = array('extralanguagesaddupdate' => 1);
7485 $result = $this->call_trigger($trigger, $userused);
7486 if ($result < 0) {
7487 $error++;
7488 }
7489 // End call trigger
7490 }
7491
7492 if ($error) {
7493 $this->db->rollback();
7494 return -1;
7495 } else {
7496 $this->db->commit();
7497 return 1;
7498 }
7499 } else {
7500 return 0;
7501 }
7502 }
7503
7514 public function updateExtraField($key, $trigger = null, $userused = null)
7515 {
7516 global $langs, $user, $extrafields, $hookmanager;
7517
7518 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7519 return 0;
7520 }
7521
7522 if (empty($userused)) {
7523 $userused = $user;
7524 }
7525
7526 $error = 0;
7527
7528 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7529 // Check parameters
7530 $langs->load('admin');
7531 if (!isset($extrafields) || !is_object($extrafields)) {
7532 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7533 $extrafields = new ExtraFields($this->db);
7534 }
7535 $extrafields->fetch_name_optionals_label($this->table_element);
7536 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7537 // Extrafield not defined for this object type: nothing to update
7538 return 0;
7539 }
7540 $value = $this->array_options["options_".$key];
7541
7542 $attributeKey = $key;
7543 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7544 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7545 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7546 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7547 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7548 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7549 //$attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7550
7551 // Similar code than into insertExtraFields
7552 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'.
7553 $mandatorypb = false;
7554 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7555 $mandatorypb = true;
7556 }
7557 if ($this->array_options["options_".$key] === '') {
7558 $mandatorypb = true;
7559 }
7560 if ($mandatorypb) {
7561 $langs->load("errors");
7562 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7563 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7564 return -1;
7565 }
7566 }
7567
7568 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7569 $new_array_options = $this->array_options;
7570
7571 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7572 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7573 if (!empty($attrfieldcomputed)) {
7574 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7575 global $objectoffield; // We set a global variable to $objectoffield so
7576 $objectoffield = $this; // we can use it inside the computed formula
7577 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7578 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7579
7580 $new_array_options["options_".$key] = $value;
7581
7582 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7583 } else {
7584 $new_array_options["options_".$key] = null;
7585
7586 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7587 }
7588 }
7589
7590 switch ($attributeType) {
7591 case 'int':
7592 if (!is_numeric($value) && $value != '') {
7593 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7594 return -1;
7595 } elseif ($value === '') {
7596 $new_array_options["options_".$key] = null;
7597
7598 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7599 }
7600 break;
7601 case 'price':
7602 case 'double':
7603 $value = price2num($value);
7604 if (!is_numeric($value) && $value != '') {
7605 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7606 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7607 return -1;
7608 } elseif ($value === '') {
7609 $value = null;
7610 }
7611 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7612 $new_array_options["options_".$key] = $value;
7613
7614 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7615 break;
7616 /*case 'select': // Not required, we chose value='0' for undefined values
7617 if ($value=='-1')
7618 {
7619 $new_array_options["options_".$key] = $value;
7620
7621 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7622 }
7623 break;*/
7624 case 'password':
7625 $algo = '';
7626 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7627 // If there is an encryption choice, we use it to encrypt data before insert
7628 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7629 $algo = reset($tmparrays);
7630 if ($algo != '') {
7631 //global $action; // $action may be 'create', 'update', 'update_extras'...
7632 //var_dump($action);
7633 //var_dump($this->oldcopy);exit;
7634 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7635 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
7636 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7637 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.
7638 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7639 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7640 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7641 } else {
7642 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7643 }
7644 } else {
7645 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7646 }
7647 } else {
7648 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7649 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7650 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7651 } else {
7652 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7653 }
7654 } else {
7655 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7656 }
7657 }
7658 } else {
7659 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7660 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7661 } else {
7662 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7663 }
7664 }
7665 } else {
7666 // No encryption
7667 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7668 }
7669 } else { // Common usage
7670 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7671 }
7672
7673 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7674 break;
7675 case 'date':
7676 case 'datetime':
7677 if (empty($this->array_options["options_".$key])) {
7678 $new_array_options["options_".$key] = null;
7679
7680 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7681 } else {
7682 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7683 }
7684 break;
7685 case 'datetimegmt':
7686 if (empty($this->array_options["options_".$key])) {
7687 $new_array_options["options_".$key] = null;
7688
7689 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7690 } else {
7691 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7692 }
7693 break;
7694 case 'boolean':
7695 if (empty($this->array_options["options_".$key])) {
7696 $new_array_options["options_".$key] = null;
7697
7698 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7699 }
7700 break;
7701 case 'link':
7702 if ($this->array_options["options_".$key] === '') {
7703 $new_array_options["options_".$key] = null;
7704
7705 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7706 }
7707 break;
7708 /*
7709 case 'link':
7710 $param_list = array_keys($attributeParam['options']);
7711 // 0 : ObjectName
7712 // 1 : classPath
7713 $InfoFieldList = explode(":", $param_list[0]);
7714 dol_include_once($InfoFieldList[1]);
7715 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7716 {
7717 if ($value == '-1') // -1 is key for no defined in combo list of objects
7718 {
7719 $new_array_options[$key] = '';
7720 } elseif ($value) {
7721 $object = new $InfoFieldList[0]($this->db);
7722 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7723 else $res = $object->fetch(0, $value); // For compatibility
7724
7725 if ($res > 0) $new_array_options[$key] = $object->id;
7726 else {
7727 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7728 $this->db->rollback();
7729 return -1;
7730 }
7731 }
7732 } else {
7733 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7734 }
7735 break;
7736 */
7737 case 'checkbox':
7738 case 'chkbxlst':
7739 $new_array_options = array();
7740 if (is_array($this->array_options["options_".$key])) {
7741 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7742 } else {
7743 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7744 }
7745
7746 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7747 break;
7748 }
7749
7750 $this->db->begin();
7751
7752 $linealreadyfound = 0;
7753
7754 // 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)
7755 $table_element = $this->table_element;
7756 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7757 $table_element = 'categories'; // For compatibility
7758 }
7759
7760 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7761 $resql = $this->db->query($sql);
7762 if ($resql) {
7763 $tmpobj = $this->db->fetch_object($resql);
7764 if ($tmpobj) {
7765 $linealreadyfound = $tmpobj->nb;
7766 }
7767 }
7768
7769 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7770 if ($linealreadyfound) {
7771 $sanitizedGeoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null; // @phan-suppress-current-line SqlInjection
7772 if ($this->array_options["options_".$key] === null) {
7773 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7774 $sql .= " SET ".$this->db->sanitize($key)." = null";
7775 } elseif (!empty($sanitizedGeoDataType['ST_Function'])) {
7776 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7777 $sql .= " SET ".$this->db->sanitize($key)." = ".$this->db->sanitize($sanitizedGeoDataType['ST_Function'])."('".$this->db->escape($this->array_options["options_".$key])."')";
7778 } else {
7779 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7780 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7781 $sql .= " SET ".$this->db->sanitize($key)." = '".$this->db->escape($new_array_options["options_".$key])."'";
7782 }
7783 $sql .= " WHERE fk_object = ".((int) $this->id);
7784
7785 $resql = $this->db->query($sql);
7786 if (!$resql) {
7787 $error++;
7788 $this->error = $this->db->lasterror();
7789 }
7790 } else {
7791 $result = $this->insertExtraFields('', $user);
7792 if ($result < 0) {
7793 $error++;
7794 }
7795 }
7796
7797 // Update also the user of last modification in parent table
7798 if (!$error && !empty($this->fields['fk_user_modif'])) {
7799 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7800 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7801 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7802 $this->db->query($sql);
7803 }
7804
7805 if (!$error) {
7806 $parameters = array('key' => $key);
7807 global $action;
7808 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7809 if ($reshook < 0) {
7810 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7811 }
7812 }
7813
7814 if (!$error && $trigger) {
7815 // Call trigger
7816 $this->context = array('extrafieldupdate' => 1);
7817 $result = $this->call_trigger($trigger, $userused);
7818 if ($result < 0) {
7819 $error++;
7820 }
7821 // End call trigger
7822 }
7823
7824 if ($error) {
7825 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7826 $this->db->rollback();
7827 return -1;
7828 } else {
7829 $this->db->commit();
7830 return 1;
7831 }
7832 } else {
7833 return 0;
7834 }
7835 }
7836
7843 public function getExtraField($key)
7844 {
7845 return $this->array_options['options_'.$key] ?? null;
7846 }
7847
7855 public function setExtraField($key, $value)
7856 {
7857 $this->array_options['options_'.$key] = $value;
7858 }
7859
7870 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7871 {
7872 global $user;
7873
7874 if (empty($userused)) {
7875 $userused = $user;
7876 }
7877
7878 //$error = 0;
7879
7880 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7881 return 0; // For avoid conflicts if trigger used
7882 }
7883
7884 return 0;
7885 }
7886
7887
7902 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7903 {
7904 global $conf, $langs, $form;
7905
7906
7907 // 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...)
7908
7909 if (!is_object($form)) {
7910 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7911 $form = new Form($this->db);
7912 }
7913
7914 if (!empty($this->fields)) {
7915 $val = $this->fields[$key];
7916 }
7917
7918 // Validation tests and output
7919 $fieldValidationErrorMsg = '';
7920 $validationClass = '';
7921 $fieldValidationErrorMsg = $this->getFieldError($key);
7922 if (!empty($fieldValidationErrorMsg)) {
7923 $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
7924 } else {
7925 $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
7926 }
7927
7928 //$valuemultiselectinput = array();
7929 $out = '';
7930 $type = '';
7931 $isDependList = 0;
7932 $param = array();
7933 $param['options'] = array();
7934 $reg = array();
7935 // @phan-suppress-next-line PhanTypeMismatchProperty
7936 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7937 // Because we work on extrafields
7938 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7939 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7940 $type = 'link';
7941 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7942 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7943 $type = 'link';
7944 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7945 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7946 $type = 'link';
7947 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7948 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7949 $type = 'sellist';
7950 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7951 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7952 $type = 'sellist';
7953 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7954 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7955 $type = 'sellist';
7956 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7957 $param['options'] = array($reg[1] => 'N');
7958 $type = 'chkbxlst';
7959 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7960 $param['options'] = array();
7961 $type = 'varchar';
7962 $size = $reg[1];
7963 } elseif (preg_match('/varchar/', $val['type'])) {
7964 $param['options'] = array();
7965 $type = 'varchar';
7966 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7967 $param['options'] = array();
7968 $type = 'stars';
7969 $size = $reg[1];
7970 } else {
7971 $param['options'] = array();
7972 $type = $this->fields[$key]['type'];
7973 }
7974 //var_dump($type); var_dump($param['options']);
7975
7976 // Special case that force options and type ($type can be integer, varchar, ...)
7977 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7978 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7979 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7980 if (empty($this->fields[$key]['multiinput'])) {
7981 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7982 }
7983 }
7984
7985 $label = $this->fields[$key]['label'];
7986 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7987 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7988 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7989 // @phan-suppress-next-line PhanTypeMismatchProperty
7990 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7991 // @phan-suppress-next-line PhanTypeMismatchProperty
7992 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7993 // @phan-suppress-next-line PhanTypeMismatchProperty
7994 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7995 // @phan-suppress-next-line PhanTypeMismatchProperty
7996 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7997 // @phan-suppress-next-line PhanTypeMismatchProperty
7998 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7999
8000 // @phan-suppress-next-line PhanTypeMismatchProperty
8001 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
8002 // @phan-suppress-next-line PhanTypeMismatchProperty
8003 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
8004 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
8005
8006 $objectid = $this->id;
8007
8008 if ($computed) {
8009 if (!preg_match('/^search_/', $keyprefix)) {
8010 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
8011 } else {
8012 return '';
8013 }
8014 }
8015
8016 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
8017 if (empty($morecss) && !empty($val['css'])) {
8018 $morecss = $val['css'];
8019 } elseif (empty($morecss)) {
8020 if ($type == 'date') {
8021 $morecss = 'minwidth100imp';
8022 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
8023 $morecss = 'minwidth200imp';
8024 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8025 $morecss = 'maxwidth75';
8026 } elseif ($type == 'url') {
8027 $morecss = 'minwidth400';
8028 } elseif ($type == 'boolean') {
8029 $morecss = '';
8030 } else {
8031 if (is_numeric($size) && round((float) $size) < 12) {
8032 $morecss = 'minwidth100';
8033 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8034 $morecss = 'minwidth200';
8035 } else {
8036 $morecss = 'minwidth400';
8037 }
8038 }
8039 }
8040
8041 // Add validation state class
8042 if (!empty($validationClass)) {
8043 $morecss .= $validationClass;
8044 }
8045
8046 if (in_array($type, array('date'))) {
8047 $tmp = explode(',', $size);
8048 $newsize = $tmp[0];
8049 $showtime = 0;
8050
8051 // Do not show current date when field not required (see selectDate() method)
8052 if (!$required && $value == '') {
8053 $value = '-1';
8054 }
8055
8056 // TODO Must also support $moreparam
8057 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
8058 } elseif (in_array($type, array('datetime'))) {
8059 $tmp = explode(',', $size);
8060 $newsize = $tmp[0];
8061 $showtime = 1;
8062
8063 // Do not show current date when field not required (see selectDate() method)
8064 if (!$required && $value == '') {
8065 $value = '-1';
8066 }
8067
8068 // TODO Must also support $moreparam
8069 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
8070 } elseif (in_array($type, array('duration'))) {
8071 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
8072 } elseif (in_array($type, array('int', 'integer'))) {
8073 $tmp = explode(',', $size);
8074 $newsize = $tmp[0];
8075 $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' : '').'>';
8076 } elseif (in_array($type, array('real'))) {
8077 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8078 } elseif (preg_match('/varchar/', (string) $type)) {
8079 $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' : '').'>';
8080 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
8081 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8082 } elseif (preg_match('/^text/', (string) $type)) {
8083 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8084 if (!empty($param['options'])) {
8085 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
8086 //var_dump($param['options']);
8087 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
8088 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
8089 $out .= "<script>";
8090 $out .= '
8091 function handlemultiinputdisabling(htmlname){
8092 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
8093 multiinput = $("#"+htmlname+"_multiinput");
8094 multiinput.find("option").each(function(){
8095 tmpval = $("#"+htmlname).val();
8096 tmpvalarray = tmpval.split("\n");
8097 valtotest = $(this).val();
8098 if(tmpvalarray.includes(valtotest)){
8099 $(this).prop("disabled",true);
8100 } else {
8101 if($(this).prop("disabled") == true){
8102 console.log(valtotest)
8103 $(this).prop("disabled", false);
8104 }
8105 }
8106 });
8107 }
8108
8109 $(document).ready(function () {
8110 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
8111 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
8112 tmpvalarray = tmpval.split(",");
8113 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
8114 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8115 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
8116 if(tmpval == ""){
8117 tmpval = valtotest;
8118 } else {
8119 tmpval = tmpval + "\n" + valtotest;
8120 }
8121 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
8122 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8123 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
8124 } else {
8125 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
8126 }
8127 });
8128 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
8129 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8130 });
8131 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8132 })';
8133 $out .= "</script>";
8134 $value = str_replace(',', "\n", $value);
8135 }
8136 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8137 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8138 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
8139 } else {
8140 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8141 }
8142 } elseif (preg_match('/^html/', (string) $type)) {
8143 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8144 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8145 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8146 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8147 } else {
8148 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8149 }
8150 } elseif ($type == 'boolean') {
8151 $checked = '';
8152 if (!empty($value)) {
8153 $checked = ' checked value="1" ';
8154 } else {
8155 $checked = ' value="1" ';
8156 }
8157 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
8158 } elseif ($type == 'price') {
8159 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8160 $value = price($value);
8161 }
8162 $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>';
8163 } elseif ($type == 'stars') {
8164 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8165 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8166 $i = 1;
8167 while ($i <= $size) {
8168 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8169 $i++;
8170 }
8171 $out .= '</div>';
8172 $out .= '<script>
8173 jQuery(function($) { /* commonobject.class.php 1 */
8174 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8175 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8176 container.find(".star").each(function() {
8177 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8178 });
8179 container.find(".star").on("mouseover", function() {
8180 let selectedStar = $(this).data("value");
8181 container.find(".star").each(function() {
8182 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8183 });
8184 });
8185 container.on("mouseout", function() {
8186 container.find(".star").each(function() {
8187 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8188 });
8189 });
8190 container.find(".star").off("click").on("click", function() {
8191 selectedStars = $(this).data("value");
8192 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8193 selectedStars = 0;
8194 }
8195 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8196 container.find(".star").each(function() {
8197 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8198 });
8199 });
8200 });
8201 </script>';
8202 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8203 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8204 $value = price($value);
8205 }
8206 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8207 } elseif ($type == 'select') { // combo list
8208 $out = '';
8209 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8210 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8211 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8212 }
8213
8214 $tmpselect = '';
8215 $nbchoice = 0;
8216
8217 foreach ($param['options'] as $keyb => $valb) {
8218 if ((string) $keyb == '') {
8219 continue;
8220 }
8221 if (strpos($valb, "|") !== false) {
8222 list($valb, $parent) = explode('|', $valb);
8223 }
8224 $nbchoice++;
8225 $tmpselect .= '<option value="'.$keyb.'"';
8226 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8227 if (!empty($parent)) {
8228 $isDependList = 1;
8229 }
8230 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8231 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8232 }
8233
8234 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8235 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8236 $out .= '<option value="0">&nbsp;</option>';
8237 }
8238 $out .= $tmpselect;
8239 $out .= '</select>';
8240 } elseif ($type == 'sellist') {
8241 $out = '';
8242 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8243 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8244 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8245 }
8246
8247 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8248 if (is_array($param['options'])) {
8249 $tmpparamoptions = array_keys($param['options']);
8250 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8251
8252 $InfoFieldList = explode(":", $paramoptions[0], 5);
8253 // 0 : tableName
8254 // 1 : label field name
8255 // 2 : key fields name (if different of rowid)
8256 // optional parameters...
8257 // 3 : key field parent (for dependent lists). How this is used ?
8258 // 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".
8259 // 5 : string category type. This replace the filter.
8260 // 6 : ids categories list separated by comma for category root. This replace the filter.
8261 // 7 : sort field
8262
8263 // If there is filter
8264 if (! empty($InfoFieldList[4])) {
8265 $pos = 0;
8266 $parenthesisopen = 0;
8267 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8268 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8269 $parenthesisopen++;
8270 }
8271 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8272 $parenthesisopen--;
8273 }
8274 $pos++;
8275 }
8276 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8277 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8278 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8279 $InfoFieldList[4] = $tmpbefore;
8280 if ($tmpafter !== '') {
8281 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8282 }
8283 //var_dump($InfoFieldList);
8284
8285 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8286 $reg = array();
8287 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8288 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8289 }
8290
8291 //var_dump($InfoFieldList);
8292 }
8293
8294 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8295
8296 $parentName = '';
8297 $parentField = '';
8298
8299 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8300
8301 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8302 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8303 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8304 } else {
8305 $keyList = $InfoFieldList[2].' as rowid';
8306 }
8307 }
8308 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8309 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8310 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8311 $keyList .= ', main.'.$parentField;
8312 } else {
8313 $keyList .= ', '.$parentField;
8314 }
8315 }
8316
8317 $filter_categorie = false;
8318 if (count($InfoFieldList) > 5) {
8319 if ($InfoFieldList[0] == 'categorie') {
8320 $filter_categorie = true;
8321 }
8322 }
8323
8324 if (!$filter_categorie) {
8325 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8326 if (!empty($fields_label)) {
8327 $keyList .= ', ';
8328 $keyList .= implode(', ', $fields_label);
8329 }
8330
8331 $sqlwhere = '';
8332 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8333 $sql .= " FROM " . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8334
8335 if (!empty($InfoFieldList[4])) {
8336 // can use SELECT request
8337 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8338 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8339 }
8340
8341 // current object id can be use into filter
8342 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8343 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8344 } else {
8345 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8346 }
8347
8348 // We have to join on extrafield table
8349 $errstr = '';
8350 if (strpos($InfoFieldList[4], 'extra') !== false) {
8351 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8352 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8353 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8354 } else {
8355 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8356 }
8357 } else {
8358 $sqlwhere .= ' WHERE 1=1';
8359 }
8360
8361 // Add Usf filter on second line
8362 /*
8363 if ($Usf) {
8364 $errorstr = '';
8365 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8366 if (!$errorstr) {
8367 $sqlwhere .= $sqlusf;
8368 } else {
8369 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8370 }
8371 }
8372 */
8373
8374 // Some tables may have field, some other not. For the moment we disable it.
8375 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8376 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8377 }
8378 $sql .= $sqlwhere;
8379
8380 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8381 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8382 $sql .= " ORDER BY ".$this->db->sanitize($InfoFieldList[7]);
8383 } else {
8384 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8385 }
8386 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8387 // print $sql;
8388
8389 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8390 $resql = $this->db->query($sql);
8391 if ($resql) {
8392 $out .= '<option value="0">&nbsp;</option>';
8393 $num = $this->db->num_rows($resql);
8394 $i = 0;
8395 while ($i < $num) {
8396 $labeltoshow = '';
8397 $obj = $this->db->fetch_object($resql);
8398
8399 // Several field into label (eq table:code|libelle:rowid)
8400 $notrans = false;
8401 $fields_label = explode('|', $InfoFieldList[1]);
8402 if (count($fields_label) > 1) {
8403 $notrans = true;
8404 foreach ($fields_label as $field_toshow) {
8405 $labeltoshow .= $obj->$field_toshow . ' ';
8406 }
8407 } else {
8408 $labeltoshow = $obj->{$InfoFieldList[1]};
8409 }
8410 $labeltoshow = dol_trunc($labeltoshow, 45);
8411
8412 if ($value == $obj->rowid) {
8413 foreach ($fields_label as $field_toshow) {
8414 $translabel = $langs->trans($obj->$field_toshow);
8415 if ($translabel != $obj->$field_toshow) {
8416 $labeltoshow = dol_trunc($translabel) . ' ';
8417 } else {
8418 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8419 }
8420 }
8421 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8422 } else {
8423 if (!$notrans) {
8424 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8425 if ($translabel != $obj->{$InfoFieldList[1]}) {
8426 $labeltoshow = dol_trunc($translabel, 18);
8427 } else {
8428 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8429 }
8430 }
8431 if (empty($labeltoshow)) {
8432 $labeltoshow = '(not defined)';
8433 }
8434 if ($value == $obj->rowid) {
8435 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8436 }
8437
8438 if (!empty($InfoFieldList[3]) && $parentField) {
8439 $parent = $parentName . ':' . $obj->{$parentField};
8440 $isDependList = 1;
8441 }
8442
8443 $out .= '<option value="' . $obj->rowid . '"';
8444 $out .= ($value == $obj->rowid ? ' selected' : '');
8445 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8446 $out .= '>' . $labeltoshow . '</option>';
8447 }
8448
8449 $i++;
8450 }
8451 $this->db->free($resql);
8452 } else {
8453 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8454 }
8455 } else {
8456 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8457 $categcode = $InfoFieldList[5];
8458 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8459 $tmpcategory = new Categorie($this->db);
8460 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8461 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8462 }
8463
8464 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8465 $out .= '<option value="0">&nbsp;</option>';
8466 foreach ($data as $data_key => $data_value) {
8467 $out .= '<option value="' . $data_key . '"';
8468 $out .= ($value == $data_key ? ' selected' : '');
8469 $out .= '>' . $data_value . '</option>';
8470 }
8471 }
8472 }
8473 $out .= '</select>';
8474 } elseif ($type == 'checkbox') {
8475 $value_arr = explode(',', $value);
8476 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8477 } elseif ($type == 'radio') {
8478 $out = '';
8479 foreach ($param['options'] as $keyopt => $valopt) {
8480 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8481 $out .= ' value="'.$keyopt.'"';
8482 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8483 $out .= ($value == $keyopt ? 'checked' : '');
8484 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8485 }
8486 } elseif ($type == 'chkbxlst') {
8487 if (is_array($value)) {
8488 $value_arr = $value;
8489 } else {
8490 $value_arr = explode(',', $value);
8491 }
8492
8493 if (is_array($param['options'])) {
8494 $tmpparamoptions = array_keys($param['options']);
8495 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8496
8497 $InfoFieldList = explode(":", $paramoptions[0], 5);
8498 // 0 : tableName
8499 // 1 : label field name
8500 // 2 : key fields name (if different of rowid)
8501 // optional parameters...
8502 // 3 : key field parent (for dependent lists). How this is used ?
8503 // 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".
8504 // 5 : string category type. This replace the filter.
8505 // 6 : ids categories list separated by comma for category root. This replace the filter.
8506 // 7 : sort field
8507
8508 // If there is a filter
8509 if (! empty($InfoFieldList[4])) {
8510 $pos = 0;
8511 $parenthesisopen = 0;
8512 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8513 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8514 $parenthesisopen++;
8515 }
8516 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8517 $parenthesisopen--;
8518 }
8519 $pos++;
8520 }
8521 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8522 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8523 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8524 $InfoFieldList[4] = $tmpbefore;
8525 if ($tmpafter !== '') {
8526 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8527 }
8528
8529 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8530 $reg = array();
8531 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8532 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8533 }
8534
8535 //var_dump($InfoFieldList);
8536 }
8537
8538 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8539
8540 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8541
8542 $parentName = '';
8543 $parentField = '';
8544
8545 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8546
8547 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8548 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8549 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8550 } else {
8551 $keyList = $InfoFieldList[2].' as rowid';
8552 }
8553 }
8554 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8555 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8556 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8557 $keyList .= ', main.'.$parentField;
8558 } else {
8559 $keyList .= ', '.$parentField;
8560 }
8561 }
8562
8563 $filter_categorie = false;
8564 if (count($InfoFieldList) > 5) {
8565 if ($InfoFieldList[0] == 'categorie') {
8566 $filter_categorie = true;
8567 }
8568 }
8569
8570 // Common filter
8571 if (!$filter_categorie) {
8572 $fields_label = explode('|', $InfoFieldList[1]);
8573 if (is_array($fields_label)) {
8574 $keyList .= ', ';
8575 $keyList .= implode(', ', $fields_label);
8576 }
8577
8578 $sqlwhere = '';
8579 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8580 $sql .= ' FROM ' . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8581
8582 if (!empty($InfoFieldList[4])) {
8583 // can use SELECT request
8584 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8585 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8586 }
8587
8588 // current object id can be use into filter
8589 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8590 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8591 } else {
8592 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8593 }
8594
8595 // We have to join on extrafield table
8596 $errstr = '';
8597 if (strpos($InfoFieldList[4], 'extra') !== false) {
8598 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8599 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8600 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8601 } else {
8602 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8603 }
8604 } else {
8605 $sqlwhere .= ' WHERE 1=1';
8606 }
8607
8608 // Add Usf filter on second line
8609 /*
8610 if ($Usf) {
8611 $errorstr = '';
8612 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8613 if (!$errorstr) {
8614 $sqlwhere .= $sqlusf;
8615 } else {
8616 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8617 }
8618 }
8619 */
8620
8621 // Some tables may have field, some other not. For the moment we disable it.
8622 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8623 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8624 }
8625 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8626 // print $sql;
8627
8628 $sql .= $sqlwhere;
8629
8630 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8631
8632 $resql = $this->db->query($sql);
8633 if ($resql) {
8634 $num = $this->db->num_rows($resql);
8635 $i = 0;
8636
8637 $data = array();
8638
8639 while ($i < $num) {
8640 $labeltoshow = '';
8641 $obj = $this->db->fetch_object($resql);
8642
8643 $notrans = false;
8644 // Several field into label (eq table:code|libelle:rowid)
8645 $fields_label = explode('|', $InfoFieldList[1]);
8646 if (count($fields_label) > 1) {
8647 $notrans = true;
8648 foreach ($fields_label as $field_toshow) {
8649 $labeltoshow .= $obj->$field_toshow . ' ';
8650 }
8651 } else {
8652 $labeltoshow = $obj->{$InfoFieldList[1]};
8653 }
8654 $labeltoshow = dol_trunc($labeltoshow, 45);
8655
8656 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8657 foreach ($fields_label as $field_toshow) {
8658 $translabel = $langs->trans($obj->$field_toshow);
8659 if ($translabel != $obj->$field_toshow) {
8660 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8661 } else {
8662 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8663 }
8664 }
8665
8666 $data[$obj->rowid] = $labeltoshow;
8667 } else {
8668 if (!$notrans) {
8669 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8670 if ($translabel != $obj->{$InfoFieldList[1]}) {
8671 $labeltoshow = dol_trunc($translabel, 18);
8672 } else {
8673 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8674 }
8675 }
8676 if (empty($labeltoshow)) {
8677 $labeltoshow = '(not defined)';
8678 }
8679
8680 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8681 $data[$obj->rowid] = $labeltoshow;
8682 }
8683
8684 if (!empty($InfoFieldList[3]) && $parentField) {
8685 $parent = $parentName . ':' . $obj->{$parentField};
8686 $isDependList = 1;
8687 }
8688
8689 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8690 }
8691
8692 $i++;
8693 }
8694 $this->db->free($resql);
8695
8696 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8697 } else {
8698 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8699 }
8700 } else {
8701 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8702 $categcode = $InfoFieldList[5];
8703 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8704 $tmpcategory = new Categorie($this->db);
8705 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8706 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8707 }
8708
8709 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8710 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8711 }
8712 }
8713 } elseif ($type == 'link') {
8714 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8715 // Filter can contains some ':' inside.
8716 $param_list = array_keys($param['options']);
8717 $param_list_array = explode(':', $param_list[0], 4);
8718
8719 $showempty = (($required && $default != '') ? 0 : 1);
8720
8721 if (!preg_match('/search_/', $keyprefix)) {
8722 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8723 // @phan-suppress-next-line PhanTypeMismatchProperty
8724 if (!empty($this->fields[$key]['picto'])) {
8725 $morecss .= ' widthcentpercentminusxx';
8726 } else {
8727 $morecss .= ' widthcentpercentminusx';
8728 }
8729 } else {
8730 // @phan-suppress-next-line PhanTypeMismatchProperty
8731 if (!empty($this->fields[$key]['picto'])) {
8732 $morecss .= ' widthcentpercentminusx';
8733 }
8734 }
8735 }
8736
8737 // $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.
8738
8739 // $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.
8740
8741 $objectfield = $val;
8742
8743 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8744 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8745
8746 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8747 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8748 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8749 list($class, $classfile) = explode(':', $param_list[0]);
8750 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8751 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8752 } else {
8753 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8754 }
8755 $paramforthenewlink = '';
8756 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8757 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8758 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8759 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8760 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8761 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8762 $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>';
8763 }
8764 }
8765 } elseif ($type == 'password') {
8766 // If prefix is 'search_', field is used as a filter, we use a common text field.
8767 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8768 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8769 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8770 } else {
8771 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8772 }
8773 } elseif ($type == 'array') {
8774 $newval = $val;
8775 $newval['type'] = 'varchar(256)';
8776
8777 $out = '';
8778 if (!empty($value)) {
8779 foreach ($value as $option) {
8780 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8781 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8782 }
8783 }
8784 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8785
8786 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8787 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8788
8789 if (!empty($conf->use_javascript_ajax)) {
8790 $out .= '
8791 <script nonce="'.getNonce().'">
8792 $(document).ready(function() {
8793 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8794 $("'.dol_escape_js($newInput).'").insertBefore(this);
8795 });
8796
8797 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8798 $(this).parent().remove();
8799 });
8800 });
8801 </script>';
8802 }
8803 }
8804 if (!empty($hidden)) {
8805 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8806 }
8807
8808 if ($isDependList == 1) {
8809 $out .= $this->getJSListDependancies('_common');
8810 }
8811 /* Add comments
8812 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8813 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8814 */
8815
8816 // Display error message for field
8817 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8818 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8819 }
8820
8821 return $out;
8822 }
8823
8837 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8838 {
8839 global $conf, $langs, $form;
8840
8841 // 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...)
8842
8843 if (!is_object($form)) {
8844 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8845 $form = new Form($this->db);
8846 }
8847
8848 //$label = empty($val['label']) ? '' : $val['label'];
8849 $type = empty($val['type']) ? '' : $val['type'];
8850 $size = empty($val['css']) ? '' : $val['css'];
8851
8852 $reg = array();
8853
8854 // Convert var to be able to share same code than showOutputField of extrafields
8855 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8856 $type = 'varchar'; // convert varchar(xx) int varchar
8857 $size = $reg[1];
8858 } elseif (preg_match('/varchar/', $type)) {
8859 $type = 'varchar'; // convert varchar(xx) int varchar
8860 }
8861 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8862 // @phan-suppress-next-line PhanTypeMismatchProperty
8863 if (empty($this->fields[$key]['multiinput'])) {
8864 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8865 }
8866 }
8867 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8868 $type = 'link';
8869 }
8870
8871 $default = empty($val['default']) ? '' : $val['default'];
8872 $computed = empty($val['computed']) ? '' : $val['computed'];
8873 $unique = empty($val['unique']) ? '' : $val['unique'];
8874 $required = empty($val['required']) ? '' : $val['required'];
8875 $param = array();
8876 $param['options'] = array();
8877
8878 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8879 $param['options'] = $val['arrayofkeyval'];
8880 }
8881 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8882 $type = 'link';
8883 $stringforoptions = $reg[1].':'.$reg[2];
8884 // Special case: Force addition of getnomurlparam1 to -1 for users
8885 if ($reg[1] == 'User') {
8886 $stringforoptions .= ':#getnomurlparam1=-1';
8887 }
8888 $param['options'] = array($stringforoptions => $stringforoptions);
8889 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8890 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8891 $type = 'sellist';
8892 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8893 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8894 $type = 'sellist';
8895 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8896 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8897 $type = 'sellist';
8898 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8899 $param['options'] = array($reg[1] => 'N');
8900 $type = 'chkbxlst';
8901 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8902 $param['options'] = array();
8903 $type = 'stars';
8904 $size = $reg[1];
8905 }
8906
8907 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8908 $list = (empty($val['list']) ? '' : $val['list']);
8909 $help = (empty($val['help']) ? '' : $val['help']);
8910 $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)
8911
8912 if ($hidden) {
8913 return '';
8914 }
8915
8916 // If field is a computed field, value must become result of compute
8917 if ($computed) {
8918 // Make the eval of compute string
8919 //var_dump($computed);
8920 $value = dol_eval((string) $computed, 1, 0, '2');
8921 }
8922
8923 if (empty($morecss)) {
8924 if ($type == 'date') {
8925 $morecss = 'minwidth100imp';
8926 } elseif ($type == 'datetime' || $type == 'timestamp') {
8927 $morecss = 'minwidth200imp';
8928 } elseif (in_array($type, array('int', 'double', 'price'))) {
8929 $morecss = 'maxwidth75';
8930 } elseif ($type == 'url') {
8931 $morecss = 'minwidth400';
8932 } elseif ($type == 'boolean') {
8933 $morecss = '';
8934 } else {
8935 if (is_numeric($size) && round((float) $size) < 12) {
8936 $morecss = 'minwidth100';
8937 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8938 $morecss = 'minwidth200';
8939 } else {
8940 $morecss = 'minwidth400';
8941 }
8942 }
8943 }
8944
8945 // Format output value differently according to properties of field
8946 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8947 // @phan-suppress-next-line PhanTypeMismatchProperty
8948 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.
8949 $value = $this->getNomUrl(1, '', 0, '', 1);
8950 }
8951 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8952 $value = $this->getLibStatut(3);
8953 } elseif ($type == 'date') {
8954 if (!empty($value)) {
8955 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8956 } else {
8957 $value = '';
8958 }
8959 } elseif ($type == 'datetime' || $type == 'timestamp') {
8960 if (!empty($value)) {
8961 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8962 } else {
8963 $value = '';
8964 }
8965 } elseif ($type == 'duration') {
8966 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8967 if (!is_null($value) && $value !== '') {
8968 $value = convertSecondToTime((int) $value, 'allhourmin');
8969 }
8970 } elseif ($type == 'double' || $type == 'real') {
8971 if (!is_null($value) && $value !== '') {
8972 $value = price($value);
8973 }
8974 } elseif ($type == 'boolean') {
8975 $checked = '';
8976 if (!empty($value)) {
8977 $checked = ' checked ';
8978 }
8979 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8980 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8981 } else {
8982 $value = yn($value ? 1 : 0);
8983 }
8984 } elseif ($type == 'mail' || $type == 'email') {
8985 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8986 } elseif ($type == 'url') {
8987 $value = dol_print_url((string) $value, '_blank', 32, 1);
8988 } elseif ($type == 'phone') {
8989 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8990 } elseif ($type == 'ip') {
8991 $value = dol_print_ip((string) $value, 0);
8992 } elseif ($type == 'stars') {
8993 $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 : '').'>';
8994 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8995 $i = 1;
8996 while ($i <= $size) {
8997 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8998 $i++;
8999 }
9000 $value .= '</div>';
9001 $value .= '<script>
9002 $(document).ready(function() { /* commonobject.class.php 2 */
9003 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
9004 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
9005 container.find(".star").each(function() {
9006 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9007 });
9008 container.find(".star").on("mouseover", function() {
9009 let selectedStar = $(this).data("value");
9010 container.find(".star").each(function() {
9011 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
9012 });
9013 });
9014 container.on("mouseout", function() {
9015 container.find(".star").each(function() {
9016 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9017 });
9018 });
9019 container.find(".star").off("click").on("click", function() {
9020 selectedStars = $(this).data("value");
9021 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
9022 selectedStars = 0;
9023 }
9024 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
9025 container.find(".star").each(function() {
9026 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9027 });
9028 $.ajax({
9029 url: "ajax/'.$this->element.'.php",
9030 method: "POST",
9031 data: {
9032 objectId: "'.$this->id.'",
9033 field: "'.$keyprefix.$key.$keysuffix.'",
9034 value: selectedStars,
9035 token: "'.newToken().'"
9036 },
9037 success: function(response) {
9038 var res = JSON.parse(response);
9039 console[res.status === "success" ? "log" : "error"](res.message);
9040 },
9041 error: function(xhr, status, error) {
9042 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
9043 }
9044 });
9045 });
9046 });
9047 </script>';
9048 } elseif ($type == 'price') {
9049 if (!is_null($value) && $value !== '') {
9050 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
9051 }
9052 } elseif ($type == 'select') {
9053 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
9054 if (strpos($value, "|") !== false) {
9055 $value = $langs->trans(explode('|', $value)[0]);
9056 } elseif (! is_numeric($value)) {
9057 $value = $langs->trans($value);
9058 }
9059 } elseif ($type == 'sellist') {
9060 $param_list = array_keys($param['options']);
9061 $InfoFieldList = explode(":", $param_list[0]);
9062
9063 $selectkey = "rowid";
9064 $keyList = 'rowid';
9065
9066 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
9067 $selectkey = $InfoFieldList[2];
9068 $keyList = $InfoFieldList[2].' as rowid';
9069 }
9070
9071 $fields_label = explode('|', $InfoFieldList[1]);
9072 if (is_array($fields_label)) {
9073 $keyList .= ', ';
9074 $keyList .= implode(', ', $fields_label);
9075 }
9076
9077 $filter_categorie = false;
9078 if (count($InfoFieldList) > 5) {
9079 if ($InfoFieldList[0] == 'categorie') {
9080 $filter_categorie = true;
9081 }
9082 }
9083
9084 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9085 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9086 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9087 $sql .= ' as main';
9088 }
9089 if ($selectkey == 'rowid' && empty($value)) {
9090 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = 0";
9091 } elseif ($selectkey == 'rowid') {
9092 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = ".((int) $value);
9093 } else {
9094 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = '".$this->db->escape((string) $value)."'";
9095 }
9096
9097 //$sql.= ' AND entity = '.((int) $conf->entity);
9098
9099 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
9100 $resql = $this->db->query($sql);
9101 if ($resql) {
9102 if (!$filter_categorie) {
9103 $value = ''; // value was used, so now we reset it to use it to build final output
9104 $numrows = $this->db->num_rows($resql);
9105 if ($numrows) {
9106 $obj = $this->db->fetch_object($resql);
9107
9108 // Several field into label (eq table:code|libelle:rowid)
9109 $fields_label = explode('|', $InfoFieldList[1]);
9110
9111 if (is_array($fields_label) && count($fields_label) > 1) {
9112 foreach ($fields_label as $field_toshow) {
9113 $translabel = '';
9114 if (!empty($obj->$field_toshow)) {
9115 $translabel = $langs->trans($obj->$field_toshow);
9116 }
9117 if ($translabel != $field_toshow) {
9118 $value .= dol_trunc($translabel, 18) . ' ';
9119 } else {
9120 $value .= $obj->$field_toshow . ' ';
9121 }
9122 }
9123 } else {
9124 $translabel = '';
9125 if (!empty($obj->{$InfoFieldList[1]})) {
9126 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9127 }
9128 if ($translabel != $obj->{$InfoFieldList[1]}) {
9129 $value = dol_trunc($translabel, 18);
9130 } else {
9131 $value = $obj->{$InfoFieldList[1]};
9132 }
9133 }
9134 }
9135 } else {
9136 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9137
9138 $toprint = array();
9139 $obj = $this->db->fetch_object($resql);
9140 $c = new Categorie($this->db);
9141 $c->fetch($obj->rowid);
9142 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9143 foreach ($ways as $way) {
9144 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9145 }
9146 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9147 }
9148 } else {
9149 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9150 }
9151 } elseif ($type == 'radio') {
9152 $value = $param['options'][(string) $value];
9153 } elseif ($type == 'checkbox') {
9154 $value_arr = explode(',', (string) $value);
9155 $value = '';
9156 if (is_array($value_arr) && count($value_arr) > 0) {
9157 $toprint = array();
9158 foreach ($value_arr as $keyval => $valueval) {
9159 if (!empty($valueval)) {
9160 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9161 }
9162 }
9163 if (!empty($toprint)) {
9164 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9165 }
9166 }
9167 } elseif ($type == 'chkbxlst') {
9168 $value_arr = (isset($value) ? explode(',', $value) : array());
9169
9170 $param_list = array_keys($param['options']);
9171 $InfoFieldList = explode(":", $param_list[0]);
9172
9173 $selectkey = "rowid";
9174 $keyList = 'rowid';
9175
9176 if (count($InfoFieldList) >= 3) {
9177 $selectkey = $InfoFieldList[2];
9178 $keyList = $InfoFieldList[2].' as rowid';
9179 }
9180
9181 $fields_label = explode('|', $InfoFieldList[1]);
9182 if (is_array($fields_label)) {
9183 $keyList .= ', ';
9184 $keyList .= implode(', ', $fields_label);
9185 }
9186
9187 $filter_categorie = false;
9188 if (count($InfoFieldList) > 5) {
9189 if ($InfoFieldList[0] == 'categorie') {
9190 $filter_categorie = true;
9191 }
9192 }
9193
9194 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9195 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9196 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9197 $sql .= ' as main';
9198 }
9199 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9200 // $sql.= ' AND entity = '.((int) $conf->entity);
9201
9202 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9203 $resql = $this->db->query($sql);
9204 if ($resql) {
9205 if (!$filter_categorie) {
9206 $value = ''; // value was used, so now we reset it to use it to build final output
9207 $toprint = array();
9208 while ($obj = $this->db->fetch_object($resql)) {
9209 // Several field into label (eq table:code|libelle:rowid)
9210 $fields_label = explode('|', $InfoFieldList[1]);
9211 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9212 if (is_array($fields_label) && count($fields_label) > 1) {
9213 foreach ($fields_label as $field_toshow) {
9214 $translabel = '';
9215 if (!empty($obj->$field_toshow)) {
9216 $translabel = $langs->trans($obj->$field_toshow);
9217 }
9218 if ($translabel != $field_toshow) {
9219 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9220 } else {
9221 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9222 }
9223 }
9224 } else {
9225 $translabel = '';
9226 if (!empty($obj->{$InfoFieldList[1]})) {
9227 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9228 }
9229 if ($translabel != $obj->{$InfoFieldList[1]}) {
9230 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9231 } else {
9232 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9233 }
9234 }
9235 }
9236 }
9237 } else {
9238 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9239
9240 $toprint = array();
9241 while ($obj = $this->db->fetch_object($resql)) {
9242 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9243 $c = new Categorie($this->db);
9244 $c->fetch($obj->rowid);
9245 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9246 foreach ($ways as $way) {
9247 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9248 }
9249 }
9250 }
9251 }
9252 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9253 } else {
9254 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9255 }
9256 } elseif ($type == 'link') {
9257 $out = '';
9258
9259 // only if something to display (perf)
9260 if ($value) {
9261 $param_list = array_keys($param['options']);
9262 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9263 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9264
9265 $InfoFieldList = explode(":", $param_list[0]);
9266
9267 $classname = $InfoFieldList[0];
9268 $classpath = $InfoFieldList[1];
9269
9270 // Set $getnomurlparam1 et getnomurlparam2
9271 $getnomurlparam = 3;
9272 $getnomurlparam2 = '';
9273 $regtmp = array();
9274 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9275 $getnomurlparam = $regtmp[1];
9276 }
9277 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9278 $getnomurlparam2 = $regtmp[1];
9279 }
9280
9281 if (!empty($classpath)) {
9282 dol_include_once($InfoFieldList[1]);
9283
9284 if ($classname && !class_exists($classname)) {
9285 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9286 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9287 $element_prop = getElementProperties($classname);
9288 if ($element_prop) {
9289 $classname = $element_prop['classname'];
9290 }
9291 }
9292
9293
9294 if ($classname && class_exists($classname)) {
9295 $object = new $classname($this->db);
9296 '@phan-var-force CommonObject $object';
9297 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9298 '@phan-var-force Product $object';
9299 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9300 } else {
9301 $result = $object->fetch($value);
9302 }
9303 if ($result > 0) {
9304 if ($object->element === 'product') {
9305 '@phan-var-force Product $object';
9306 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9307 if (isset($val['get_name_url_params'])) {
9308 $get_name_url_params = explode(':', $val['get_name_url_params']);
9309 if (!empty($get_name_url_params)) {
9310 $param_num_max = count($get_name_url_param_arr) - 1;
9311 foreach ($get_name_url_params as $param_num => $param_value) {
9312 if ($param_num > $param_num_max) {
9313 break;
9314 }
9315 $get_name_url_param_arr[$param_num] = $param_value;
9316 }
9317 }
9318 }
9319
9323 $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]);
9324 } else {
9325 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9326 }
9327 } else {
9328 $value = '';
9329 }
9330 }
9331 } else {
9332 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9333 return 'Error bad setup of extrafield';
9334 }
9335 } else {
9336 $value = '';
9337 }
9338 } elseif ($type == 'password') {
9339 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9340 //$value = preg_replace('/./i', '*', $value);
9341 } elseif ($type == 'array') {
9342 if (is_array($value)) {
9343 $value = implode('<br>', $value);
9344 } else {
9345 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9346 return 'Error unexpected result from code evaluation';
9347 }
9348 } else { // text|html|varchar
9349 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9350 $value = str_replace(',', "\n", $value);
9351 }
9352 $value = dol_htmlentitiesbr((string) $value);
9353 }
9354
9355 //print $type.'-'.$size.'-'.$value;
9356 $out = $value;
9357
9358 return is_null($out) ? '' : $out;
9359 }
9360
9367 public function clearFieldError($fieldKey)
9368 {
9369 $this->error = '';
9370 unset($this->validateFieldsErrors[$fieldKey]);
9371 }
9372
9380 public function setFieldError($fieldKey, $msg = '')
9381 {
9382 global $langs;
9383 if (empty($msg)) {
9384 $msg = $langs->trans("UnknownError");
9385 }
9386
9387 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9388 }
9389
9396 public function getFieldError($fieldKey)
9397 {
9398 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9399 return $this->validateFieldsErrors[$fieldKey];
9400 }
9401 return '';
9402 }
9403
9412 public function validateField($fields, $fieldKey, $fieldValue)
9413 {
9414 global $langs;
9415
9416 if (!class_exists('Validate')) {
9417 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9418 }
9419
9420 $this->clearFieldError($fieldKey);
9421
9422 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9423 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9424 return false;
9425 }
9426
9427 $val = $fields[$fieldKey];
9428
9429 $param = array();
9430 $param['options'] = array();
9431 $type = $val['type'];
9432
9433 $required = false;
9434 if (isset($val['notnull']) && $val['notnull'] === 1) {
9435 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9436 $required = true;
9437 }
9438
9439 $maxSize = 0;
9440 $minSize = 0;
9441
9442 //
9443 // PREPARE Elements
9444 //
9445 $reg = array();
9446
9447 // Convert var to be able to share same code than showOutputField of extrafields
9448 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9449 $type = 'varchar'; // convert varchar(xx) int varchar
9450 $maxSize = (int) $reg[1];
9451 } elseif (preg_match('/varchar/', $type)) {
9452 $type = 'varchar'; // convert varchar(xx) int varchar
9453 }
9454
9455 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9456 $type = 'select';
9457 }
9458
9459 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9460 $type = 'link';
9461 }
9462
9463 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9464 $param['options'] = $val['arrayofkeyval'];
9465 }
9466
9467 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9468 $type = 'link';
9469 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9470 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9471 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9472 $type = 'sellist';
9473 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9474 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9475 $type = 'sellist';
9476 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9477 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9478 $type = 'sellist';
9479 }
9480
9481 //
9482 // TEST Value
9483 //
9484
9485 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9486 $validate = new Validate($this->db, $langs);
9487
9488
9489 // little trick : to perform tests with good performances sort tests by quick to low
9490
9491 //
9492 // COMMON TESTS
9493 //
9494
9495 // Required test and empty value
9496 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9497 $this->setFieldError($fieldKey, $validate->error);
9498 return false;
9499 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9500 // if no value sent and the field is not mandatory, no need to perform tests
9501 return true;
9502 }
9503
9504 // MAX Size test
9505 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9506 $this->setFieldError($fieldKey, $validate->error);
9507 return false;
9508 }
9509
9510 // MIN Size test
9511 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9512 $this->setFieldError($fieldKey, $validate->error);
9513 return false;
9514 }
9515
9516 //
9517 // TESTS for TYPE
9518 //
9519
9520 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9521 if (!$validate->isTimestamp($fieldValue)) {
9522 $this->setFieldError($fieldKey, $validate->error);
9523 return false;
9524 } else {
9525 return true;
9526 }
9527 } elseif ($type == 'duration') {
9528 if (!$validate->isDuration($fieldValue)) {
9529 $this->setFieldError($fieldKey, $validate->error);
9530 return false;
9531 } else {
9532 return true;
9533 }
9534 } elseif (in_array($type, array('double', 'real', 'price'))) {
9535 // is numeric
9536 if (!$validate->isNumeric($fieldValue)) {
9537 $this->setFieldError($fieldKey, $validate->error);
9538 return false;
9539 } else {
9540 return true;
9541 }
9542 } elseif ($type == 'boolean') {
9543 if (!$validate->isBool($fieldValue)) {
9544 $this->setFieldError($fieldKey, $validate->error);
9545 return false;
9546 } else {
9547 return true;
9548 }
9549 } elseif ($type == 'mail' || $type == 'email') {
9550 if (!$validate->isEmail($fieldValue)) {
9551 $this->setFieldError($fieldKey, $validate->error);
9552 return false;
9553 }
9554 } elseif ($type == 'url') {
9555 if (!$validate->isUrl($fieldValue)) {
9556 $this->setFieldError($fieldKey, $validate->error);
9557 return false;
9558 } else {
9559 return true;
9560 }
9561 } elseif ($type == 'phone') {
9562 if (!$validate->isPhone($fieldValue)) {
9563 $this->setFieldError($fieldKey, $validate->error);
9564 return false;
9565 } else {
9566 return true;
9567 }
9568 } elseif ($type == 'select' || $type == 'radio') {
9569 if (!isset($param['options'][$fieldValue])) {
9570 $this->error = $langs->trans('RequireValidValue');
9571 return false;
9572 } else {
9573 return true;
9574 }
9575 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9576 $param_list = array_keys($param['options']);
9577 $InfoFieldList = explode(":", $param_list[0]);
9578 $value_arr = explode(',', $fieldValue);
9579
9580 $selectkey = "rowid";
9581 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9582 $selectkey = $InfoFieldList[2];
9583 }
9584
9585 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9586 $this->setFieldError($fieldKey, $validate->error);
9587 return false;
9588 } else {
9589 return true;
9590 }
9591 } elseif ($type == 'link') {
9592 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9593 $InfoFieldList = explode(":", $param_list[0]);
9594 $classname = $InfoFieldList[0];
9595 $classpath = $InfoFieldList[1];
9596 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9597 $lastIsFetchableError = $validate->error;
9598
9599 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9600 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9601 return true;
9602 }
9603
9604 $this->setFieldError($fieldKey, $lastIsFetchableError);
9605 return false;
9606 } else {
9607 return true;
9608 }
9609 }
9610
9611 // if no test failed all is ok
9612 return true;
9613 }
9614
9628 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9629 {
9630 global $db, $conf, $langs, $action, $form, $hookmanager;
9631 global $objectoffield;
9632
9633 if (!is_object($form)) {
9634 $form = new Form($db);
9635 }
9636 if (!is_object($extrafields)) {
9637 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9638 return 'Bad parameter extrafields for showOptionals';
9639 }
9640 if (!is_array($extrafields->attributes[$this->table_element])) {
9641 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9642 }
9643
9644 // Ensure $objectoffield is available for dol_eval visibility formulas.
9645 $objectoffield = $this;
9646
9647 $out = '';
9648
9649 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9650 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9651 $hookResPrint = $hookmanager->resPrint;
9652
9653 if (empty($reshook)) {
9654 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) {
9655 $out .= "\n";
9656 $out .= '<!-- commonobject:showOptionals --> ';
9657 $out .= "\n";
9658
9659 $nbofextrafieldsshown = 0;
9660 $e = 0; // var to manage the modulo (odd/even)
9661
9662 $lastseparatorkeyfound = '';
9663 $extrafields_collapse_num = '';
9664 $extrafields_collapse_num_old = '';
9665 $i = 0;
9666
9667 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9668 $i++;
9669
9670 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9671 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9672 continue;
9673 }
9674
9675 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9676 $enabled = 1;
9677 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9678 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9679 }
9680 if (empty($enabled)) {
9681 continue;
9682 }
9683
9684 $visibility = 1;
9685 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9686 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9687 }
9688
9689 $perms = 1;
9690 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9691 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9692 }
9693
9694 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9695 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9696 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9697 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9698 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9699 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9700 $ef_name = 'options_' . $key;
9701 $ef_value = $this->array_options[$ef_name] ?? '';
9702 $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).
9703 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9704 } elseif ($mode == 'view' && empty($visibility)) {
9705 continue;
9706 }
9707 if (empty($perms)) {
9708 continue;
9709 }
9710
9711 // Load language if required
9712 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9713 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9714 }
9715
9716 $colspan = 0;
9717 $value = null;
9718 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9719 if (array_key_exists('cols', $params)) {
9720 $colspan = $params['cols'];
9721 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9722 $reg = array();
9723 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9724 $colspan = $reg[1];
9725 } else {
9726 $colspan = $params['colspan'];
9727 }
9728 }
9729 }
9730 $colspan = intval($colspan);
9731
9732 switch ($mode) {
9733 case "view":
9734 $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
9735 break;
9736 case "create":
9737 case "edit":
9738 // We get the value of property found with GETPOST so it takes into account:
9739 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9740 $check = 'alphanohtml';
9741 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9742 $check = 'restricthtml';
9743 }
9744 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9745 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9746 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9747 if (is_array($getposttemp)) {
9748 // $getposttemp is an array but following code expects a comma separated string
9749 $value = implode(",", $getposttemp);
9750 } else {
9751 $value = $getposttemp;
9752 }
9753 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9754 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9755 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('varchar', 'char'))) {
9756 $value = (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] !== '') ? $this->array_options["options_".$key] : '';
9757 } else {
9758 $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.
9759 }
9760 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9761 break;
9762 }
9763
9764 $nbofextrafieldsshown++;
9765
9766 // Output value of the current field
9767 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9768 $extrafields_collapse_num = $key;
9769 /*
9770 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9771 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9772 $extrafield_param_list = array_keys($extrafield_param['options']);
9773
9774 if (count($extrafield_param_list) > 0) {
9775 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9776
9777 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9778 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9779 $extrafields_collapse_num = $key;
9780 }
9781 }
9782 }
9783 */
9784
9785 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9786 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9787
9788 $lastseparatorkeyfound = $key;
9789 } else {
9790 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9791
9792 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9793 $csstyle = '';
9794 if (is_array($params) && count($params) > 0) {
9795 if (array_key_exists('class', $params)) {
9796 $class .= $params['class'].' ';
9797 }
9798 if (array_key_exists('style', $params)) {
9799 $csstyle = $params['style'];
9800 }
9801 }
9802
9803 // add html5 elements
9804 $domData = ' data-element="extrafield"';
9805 $domData .= ' data-targetelement="'.$this->element.'"';
9806 $domData .= ' data-targetid="'.$this->id.'"';
9807
9808 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9809 if ($display_type == 'card') {
9810 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9811 $colspan = 0;
9812 }
9813
9814 if ($action == 'selectlines') {
9815 $colspan++;
9816 }
9817 }
9818
9819 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9820 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9821 $force_values_on_change_company = (
9822 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9823 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9824 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9825 );
9826 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9827 $value = $this->array_options['options_'.$key] ?? $value;
9828 }
9829
9830 // Convert date into timestamp format (value in memory must be a timestamp)
9831 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9832 $datenotinstring = null;
9833 if (array_key_exists('options_'.$key, $this->array_options)) {
9834 $datenotinstring = $this->array_options['options_'.$key];
9835 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9836 $datenotinstring = $this->db->jdate($datenotinstring);
9837 }
9838 }
9839 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9840 $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;
9841 }
9842 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9843 $datenotinstring = null;
9844 if (array_key_exists('options_'.$key, $this->array_options)) {
9845 $datenotinstring = $this->array_options['options_'.$key];
9846 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9847 $datenotinstring = $this->db->jdate($datenotinstring);
9848 }
9849 }
9850 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9851 $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;
9852 }
9853 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9854 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9855 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9856 $value = price2num($value);
9857 } elseif (isset($this->array_options['options_'.$key])) {
9858 $value = $this->array_options['options_'.$key];
9859 }
9860 }
9861
9862 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9863 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9864 if ($action == 'create' || $mode == 'create') {
9865 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9866 }
9867 }
9868
9869 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9870 if ($action == 'create') {
9871 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9872 }
9873 }
9874
9875
9876 $labeltoshow = $langs->trans($label);
9877 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9878 if ($display_type == 'card') {
9879 $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.' >';
9880 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9881 $out .= '<td></td>';
9882 }
9883 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9884 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9885 $out .= ' tdtop';
9886 }
9887 } elseif ($display_type == 'line') {
9888 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9889 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9890 }
9891 //$out .= "titlefield";
9892 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9893 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9894 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9895 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9896 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9897 $out .= ' fieldrequired';
9898 }
9899 }
9900 $out .= '">';
9901 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9902 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9903 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9904 } else {
9905 $out .= $labeltoshow;
9906 }
9907 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9908 $out .= '&nbsp;<span style="color: red">*</span>';
9909 }
9910 } else {
9911 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9912 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9913 } else {
9914 $out .= $labeltoshow;
9915 }
9916 }
9917
9918 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9919
9920 // Second column
9921 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9922 if ($display_type == 'card') {
9923 // 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
9924 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9925 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9926 $out .= '>';
9927 } elseif ($display_type == 'line') {
9928 $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].'">';
9929 }
9930
9931 switch ($mode) {
9932 case "view":
9933 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9934 break;
9935 case "create":
9936 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9937 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9938 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9939 }
9940 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9941 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9942 break;
9943 case "edit":
9944 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9945 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9946 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9947 }
9948 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9949 break;
9950 }
9951
9952 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9953 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9954 $e++;
9955 }
9956 }
9957 $out .= "\n";
9958 // Add code to manage list depending on others
9959 if (!empty($conf->use_javascript_ajax)) {
9960 $out .= $this->getJSListDependancies();
9961 }
9962
9963 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9964
9965 if (empty($nbofextrafieldsshown)) {
9966 $out = '';
9967 }
9968 }
9969 }
9970
9971 $out .= $hookResPrint;
9972
9973 return $out;
9974 }
9975
9980 public function getJSListDependancies($type = '_extra')
9981 {
9982 $out = '
9983 <script nonce="'.getNonce().'">
9984 jQuery(document).ready(function() {
9985 function showOptions'.$type.'(child_list, parent_list, orig_select)
9986 {
9987 var val = $("select[name=\""+parent_list+"\"]").val();
9988 var parentVal = parent_list + ":" + val;
9989 if(typeof val == "string"){
9990 if(val != "") {
9991 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9992 $("select[name=\""+child_list+"\"] option[parent]").remove();
9993 $("select[name=\""+child_list+"\"]").append(options);
9994 } else {
9995 var options = orig_select.find("option[parent]").clone();
9996 $("select[name=\""+child_list+"\"] option[parent]").remove();
9997 $("select[name=\""+child_list+"\"]").append(options);
9998 }
9999 } else if(val > 0) {
10000 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
10001 $("select[name=\""+child_list+"\"] option[parent]").remove();
10002 $("select[name=\""+child_list+"\"]").append(options);
10003 } else {
10004 var options = orig_select.find("option[parent]").clone();
10005 $("select[name=\""+child_list+"\"] option[parent]").remove();
10006 $("select[name=\""+child_list+"\"]").append(options);
10007 }
10008 }
10009 function setListDependencies'.$type.'() {
10010 jQuery("select option[parent]").parent().each(function() {
10011 var orig_select = {};
10012 var child_list = $(this).attr("name");
10013 orig_select[child_list] = $(this).clone();
10014 var parent = $(this).find("option[parent]:first").attr("parent");
10015 var infos = parent.split(":");
10016 var parent_list = infos[0];
10017
10018 //Hide daughters lists
10019 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
10020 $("#"+child_list).hide();
10021 //Show mother lists
10022 } else if ($("#"+parent_list).val() != 0){
10023 $("#"+parent_list).show();
10024 }
10025 //Show the child list if the parent list value is selected
10026 $("select[name=\""+parent_list+"\"]").click(function() {
10027 if ($(this).val() != 0){
10028 $("#"+child_list).show()
10029 }
10030 });
10031
10032 //When we change parent list
10033 $("select[name=\""+parent_list+"\"]").change(function() {
10034 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
10035 //Select the value 0 on child list after a change on the parent list
10036 $("#"+child_list).val(0).trigger("change");
10037 //Hide child lists if the parent value is set to 0
10038 if ($(this).val() == 0){
10039 $("#"+child_list).hide();
10040 }
10041 });
10042 });
10043 }
10044
10045 setListDependencies'.$type.'();
10046 });
10047 </script>'."\n";
10048 return $out;
10049 }
10050
10056 public function getRights()
10057 {
10058 global $user;
10059
10060 $module = empty($this->module) ? '' : $this->module;
10061 $element = $this->element;
10062
10063 if ($element == 'facturerec') {
10064 $element = 'facture';
10065 } elseif ($element == 'invoice_supplier_rec') {
10066 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
10067 } elseif ($module && $user->hasRight($module, $element)) {
10068 // for modules built with ModuleBuilder
10069 return $user->hasRight($module, $element);
10070 }
10071
10072 return isset($user->rights->$element) ? $user->rights->$element : null;
10073 }
10074
10087 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10088 {
10089 global $hookmanager;
10090
10091 $parameters = array(
10092 'origin_id' => $origin_id,
10093 'dest_id' => $dest_id,
10094 'tables' => $tables,
10095 );
10096 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
10097 if ($reshook) {
10098 return true; // replacement code
10099 } elseif ($reshook < 0) {
10100 return $ignoreerrors === 1; // failure
10101 } // reshook = 0 => execute normal code
10102
10103 foreach ($tables as $table) {
10104 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
10105
10106 if (!$dbs->query($sql)) {
10107 if ($ignoreerrors) {
10108 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.
10109 }
10110 //$this->errors = $db->lasterror();
10111 return false;
10112 }
10113 }
10114
10115 return true;
10116 }
10117
10130 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10131 {
10132 foreach ($tables as $table) {
10133 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
10134
10135 if (!$dbs->query($sql)) {
10136 if ($ignoreerrors) {
10137 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.
10138 }
10139 //$this->errors = $db->lasterror();
10140 return false;
10141 }
10142 }
10143
10144 return true;
10145 }
10146
10160 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0, $qty = 0)
10161 {
10162 global $conf;
10163
10164 $buyPrice = 0;
10165
10166 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10167 // When ForceBuyingPriceIfNull is set
10168 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10169 } else {
10170 // Get cost price for margin calculation
10171 if (!empty($fk_product) && $fk_product > 0) {
10172 $result = 0;
10173 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10174 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10175 $product = new Product($this->db);
10176 $result = $product->fetch($fk_product);
10177 if ($result <= 0) {
10178 $this->errors[] = 'ErrorProductIdDoesNotExists';
10179 return -1;
10180 }
10181 if ($product->cost_price > 0) {
10182 $buyPrice = $product->cost_price;
10183 } elseif ($product->pmp > 0) {
10184 $buyPrice = $product->pmp;
10185 }
10186 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10187 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10188 $product = new Product($this->db);
10189 $result = $product->fetch($fk_product);
10190 if ($result <= 0) {
10191 $this->errors[] = 'ErrorProductIdDoesNotExists';
10192 return -1;
10193 }
10194 if ($product->pmp > 0) {
10195 $buyPrice = $product->pmp;
10196 }
10197 }
10198
10199 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10200 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10201 $productFournisseur = new ProductFournisseur($this->db);
10202 $result = $productFournisseur->find_min_price_product_fournisseur($fk_product, $qty);
10203 if ($result == 0 && $qty > 0) {
10204 // No supplier price defined for such a low quantity, fall back on the lowest known price
10205 $result = $productFournisseur->find_min_price_product_fournisseur($fk_product);
10206 }
10207 if ($result > 0) {
10208 $buyPrice = $productFournisseur->fourn_unitprice;
10209 } elseif ($result < 0) {
10210 $this->errors[] = $productFournisseur->error;
10211 return -2;
10212 }
10213 }
10214 }
10215 }
10216
10217 return (float) $buyPrice;
10218 }
10219
10227 public function getDataToShowPhoto($modulepart, $imagesize)
10228 {
10229 // See getDataToShowPhoto() implemented by Product for example.
10230 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10231 }
10232
10233
10234 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10254 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')
10255 {
10256 // phpcs:enable
10257 global $user, $langs;
10258
10259 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10260 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10261 include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
10262
10263 $sortfield = 'position_name';
10264 $sortorder = 'asc';
10265
10266 $dir = $sdir.'/';
10267 $pdir = '/';
10268
10269 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10270 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10271
10272 // For backward compatibility
10273 if ($modulepart == 'product') {
10274 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10275 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10276 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10277 }
10278 }
10279 if ($modulepart == 'category') {
10280 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10281 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10282 }
10283
10284 // Defined relative dir to DOL_DATA_ROOT
10285 $relativedir = '';
10286 if ($dir) {
10287 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10288 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10289 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10290 }
10291
10292 $dirthumb = $dir.'thumbs/';
10293 $pdirthumb = $pdir.'thumbs/';
10294
10295 $return = '<!-- Photo -->'."\n";
10296 $nbphoto = 0;
10297
10298 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10299
10300 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10301 {
10302 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10303 $filearray=array_merge($filearray, $filearrayold);
10304 }*/
10305
10306 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10307 '@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';
10308
10309 $useLinkPathPhoto = false;
10310
10311 if (getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO')) {
10312 $link = new Link($this->db);
10313 $links = array();
10314 $link->fetchAll($links, 'product', $this->id);
10315
10316 if (count($links) > 0) {
10317 $useLinkPathPhoto = true;
10318 }
10319
10320 if ($useLinkPathPhoto) {
10321 // Start table or div based on nbbyrow
10322 if ($nbbyrow > 0) {
10323 $return .= '<table class="valigntop center centpercent" style="border:0; padding:2px; border-spacing:2px; border-collapse: separate;">';
10324 }
10325
10326 $i = 0;
10327 foreach ($links as $link) {
10328 if (!empty($link->url) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $link->url)) {
10329 $url = dol_escape_htmltag($link->url);
10330 $i++;
10331 $nbphoto++;
10332
10333 if ($nbbyrow > 0 && ($i % $nbbyrow == 1)) {
10334 $return .= '<tr class="center valignmiddle">';
10335 }
10336
10337 if ($nbbyrow > 0) {
10338 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%" class="photo">';
10339 } else {
10340 $return .= '<div class="inline-block">';
10341 }
10342
10343 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.$url.'" title="External image">';
10344
10345 if ($nbbyrow > 0) {
10346 $return .= '</td>';
10347 if ($i % $nbbyrow == 0) {
10348 $return .= '</tr>';
10349 }
10350 } else {
10351 $return .= '</div>';
10352 }
10353
10354 if ($nbmax && $nbphoto >= $nbmax) {
10355 break;
10356 }
10357 }
10358 }
10359
10360 if ($nbbyrow > 0) {
10361 while ($i % $nbbyrow) {
10362 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10363 $i++;
10364 }
10365 if ($nbphoto) {
10366 $return .= '</table>';
10367 }
10368 }
10369 }
10370 }
10371
10372 if (count($filearray)) {
10373 if ($sortfield && $sortorder) {
10374 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10375 }
10376
10377 foreach ($filearray as $key => $val) {
10378 $photo = '';
10379 $file = $val['name'];
10380
10381 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10382 if (image_format_supported($file) >= 0) {
10383 $nbphoto++;
10384 $photo = $file;
10385 $viewfilename = $file;
10386
10387 if ($size == 1 || $size == 'small') { // Format vignette
10388 // Find name of thumb file
10389 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10390 if (!dol_is_file($dirthumb.$photo_vignette)) {
10391 // The thumb does not exists, so we will use the original file
10392 $dirthumb = $dir;
10393 $pdirthumb = $pdir;
10394 $photo_vignette = basename($file);
10395 }
10396
10397 // Get filesize of original file
10398 $imgarray = dol_getImageSize($dir.$photo);
10399
10400 if ($nbbyrow > 0) {
10401 if ($nbphoto == 1) {
10402 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10403 }
10404
10405 if ($nbphoto % $nbbyrow == 1) {
10406 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10407 }
10408 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10409 } elseif ($nbbyrow < 0) {
10410 $return .= '<div class="inline-block">'."\n";
10411 }
10412
10413 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10414 if (empty($nolink)) {
10415 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10416 if ($urladvanced) {
10417 $return .= '<a href="'.$urladvanced.'">';
10418 } else {
10419 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10420 }
10421 }
10422
10423 // Show image (width height=$maxHeight)
10424 // If thumb file available and image source is too large, we use the thumb, otherwise we use the original photo
10425 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10426 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10427 if ($overwritetitle) {
10428 if (is_numeric($overwritetitle)) {
10429 $alt = '';
10430 } else {
10431 $alt = $overwritetitle;
10432 }
10433 }
10434 if (empty($cache) && !empty($val['label'])) {
10435 // label is md5 of file
10436 // use it in url to say we want to cache this version of the file
10437 $cache = $val['label'];
10438 }
10439 if ($usesharelink) {
10440 if (array_key_exists('share', $val) && $val['share']) {
10441 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10442 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10443 $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).'">';
10444 } else {
10445 $return .= '<!-- Show original file -->';
10446 $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).'">';
10447 }
10448 } else {
10449 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10450 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10451 }
10452 } else {
10453 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10454 $return .= '<!-- Show thumb -->';
10455 $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).'">';
10456 } else {
10457 $return .= '<!-- Show original file -->';
10458 $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).'">';
10459 }
10460 }
10461
10462 if (empty($nolink)) {
10463 $return .= '</a>';
10464 }
10465
10466 if ($showfilename) {
10467 $return .= '<br>'.$viewfilename;
10468 }
10469 if ($showaction) {
10470 $return .= '<br>';
10471 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10472 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10473 $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>';
10474 }
10475 // Special case for product
10476 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10477 // Link to resize
10478 $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; ';
10479
10480 // Link to delete
10481 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10482 $return .= img_delete().'</a>';
10483 }
10484 }
10485 $return .= "\n";
10486
10487 if ($nbbyrow > 0) {
10488 $return .= '</td>';
10489 if (($nbphoto % $nbbyrow) == 0) {
10490 $return .= '</tr>';
10491 }
10492 } elseif ($nbbyrow < 0) {
10493 $return .= '</div>'."\n";
10494 }
10495 }
10496
10497 if (empty($size)) { // Format origine
10498 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10499
10500 if ($showfilename) {
10501 $return .= '<br>'.$viewfilename;
10502 }
10503 if ($showaction) {
10504 // Special case for product
10505 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10506 // Link to resize
10507 $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; ';
10508
10509 // Link to delete
10510 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10511 $return .= img_delete().'</a>';
10512 }
10513 }
10514 }
10515
10516 // On continue ou on arrete de boucler ?
10517 if ($nbmax && $nbphoto >= $nbmax) {
10518 break;
10519 }
10520 }
10521 }
10522
10523 if ($size == 1 || $size == 'small') {
10524 if ($nbbyrow > 0) {
10525 // Ferme tableau
10526 while ($nbphoto % $nbbyrow) {
10527 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10528 $nbphoto++;
10529 }
10530
10531 if ($nbphoto) {
10532 $return .= '</table>';
10533 }
10534 }
10535 }
10536 }
10537
10538 $this->nbphoto = $nbphoto;
10539
10540 return $return;
10541 }
10542
10543
10550 protected function isArray($info)
10551 {
10552 if (is_array($info)) {
10553 if (isset($info['type']) && $info['type'] == 'array') {
10554 return true;
10555 } else {
10556 return false;
10557 }
10558 }
10559 return false;
10560 }
10561
10568 public function isDate($info)
10569 {
10570 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10571 return true;
10572 }
10573 return false;
10574 }
10575
10582 public function isDuration($info)
10583 {
10584 if (is_array($info)) {
10585 if (isset($info['type']) && ($info['type'] == 'duration')) {
10586 return true;
10587 } else {
10588 return false;
10589 }
10590 } else {
10591 return false;
10592 }
10593 }
10594
10601 public function isInt($info)
10602 {
10603 if (is_array($info)) {
10604 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10605 return true;
10606 } else {
10607 return false;
10608 }
10609 } else {
10610 return false;
10611 }
10612 }
10613
10620 public function isFloat($info)
10621 {
10622 if (is_array($info)) {
10623 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10624 return true;
10625 } else {
10626 return false;
10627 }
10628 }
10629 return false;
10630 }
10631
10638 public function isText($info)
10639 {
10640 if (is_array($info)) {
10641 if (isset($info['type']) && $info['type'] == 'text') {
10642 return true;
10643 } else {
10644 return false;
10645 }
10646 }
10647 return false;
10648 }
10649
10656 protected function canBeNull($info)
10657 {
10658 if (is_array($info)) {
10659 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10660 return true;
10661 } else {
10662 return false;
10663 }
10664 }
10665 return true;
10666 }
10667
10674 protected function isForcedToNullIfZero($info)
10675 {
10676 if (is_array($info)) {
10677 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10678 return true;
10679 } else {
10680 return false;
10681 }
10682 }
10683 return false;
10684 }
10685
10692 protected function isIndex($info)
10693 {
10694 if (is_array($info)) {
10695 if (array_key_exists('index', $info) && $info['index'] == true) {
10696 return true;
10697 } else {
10698 return false;
10699 }
10700 }
10701 return false;
10702 }
10703
10704
10714 protected function setSaveQuery()
10715 {
10716 global $conf;
10717
10718 $queryarray = array();
10719 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10720 // Depending on field type ('datetime', ...)
10721 if ($this->isDate($info)) {
10722 if (empty($this->{$field})) {
10723 $queryarray[$field] = null;
10724 } else {
10725 $queryarray[$field] = $this->db->idate($this->{$field});
10726 }
10727 } elseif ($this->isDuration($info)) {
10728 // $this->{$field} may be null, '', 0, '0', 123, '123'
10729 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10730 if (!isset($this->{$field})) {
10731 if (!empty($info['default'])) {
10732 $queryarray[$field] = $info['default']; // From safe source @phan-suppress-current-line SqlInjection
10733 } else {
10734 $queryarray[$field] = 0;
10735 }
10736 } else {
10737 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10738 }
10739 } else {
10740 $queryarray[$field] = null;
10741 }
10742 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10743 if ($field == 'entity' && is_null($this->{$field})) {
10744 $queryarray[$field] = ((int) $conf->entity);
10745 } else {
10746 // $this->{$field} may be null, '', 0, '0', 123, '123'
10747 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10748 if (!isset($this->{$field})) {
10749 $queryarray[$field] = 0;
10750 } elseif ($this->isInt($info)) {
10751 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10752 } elseif ($this->isFloat($info)) {
10753 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10754 }
10755 } else {
10756 $queryarray[$field] = null;
10757 }
10758 }
10759 } else {
10760 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10761 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10762 $queryarray[$field] = $this->{$field}; // @phan-suppress-current-line SqlInjection
10763 }
10764
10765 if (is_array($info) && array_key_exists('type', $info) && !empty($info['type']) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10766 unset($queryarray[$field]);
10767 }
10768 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10769 $queryarray[$field] = null; // May force 0 to null
10770 }
10771 }
10772
10773 return $queryarray;
10774 }
10775
10782 public function setVarsFromFetchObj(&$obj)
10783 {
10784 global $db;
10785
10786 foreach ($this->fields as $field => $info) {
10787 if ($this->isDate($info)) {
10788 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') {
10789 $this->$field = '';
10790 } else {
10791 $this->$field = $db->jdate($obj->$field);
10792 }
10793 } elseif ($this->isInt($info)) {
10794 if ($field == 'rowid') {
10795 $this->id = (int) $obj->$field;
10796 } else {
10797 if ($this->isForcedToNullIfZero($info)) {
10798 if (empty($obj->$field)) {
10799 $this->$field = null;
10800 } else {
10801 $this->$field = (int) $obj->$field;
10802 }
10803 } else {
10804 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10805 $this->$field = (int) $obj->$field;
10806 } else {
10807 $this->$field = null;
10808 }
10809 }
10810 }
10811 } elseif ($this->isFloat($info)) {
10812 if ($this->isForcedToNullIfZero($info)) {
10813 if (empty($obj->$field)) {
10814 $this->$field = null;
10815 } else {
10816 $this->$field = (float) $obj->$field;
10817 }
10818 } else {
10819 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10820 $this->$field = (float) $obj->$field;
10821 } else {
10822 $this->$field = null;
10823 }
10824 }
10825 } else {
10826 $this->$field = isset($obj->$field) ? $obj->$field : null;
10827 }
10828 }
10829
10830 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10831 if (!isset($this->fields['ref']) && isset($this->id)) {
10832 $this->ref = (string) $this->id;
10833 }
10834 }
10835
10840 public function emtpyObjectVars()
10841 {
10842 foreach ($this->fields as $field => $arr) {
10843 $this->$field = null;
10844 }
10845 }
10846
10854 public function getFieldList($alias = '', $excludefields = array())
10855 {
10856 $keys = array_keys($this->fields);
10857 if (!empty($alias)) {
10858 $keys_with_alias = array();
10859 foreach ($keys as $fieldname) {
10860 if (!empty($excludefields)) {
10861 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10862 continue;
10863 }
10864 }
10865 $keys_with_alias[] = $this->db->sanitize($alias . '.' . $fieldname);
10866 }
10867 return implode(', ', $keys_with_alias);
10868 } else {
10869 return implode(', ', $keys);
10870 }
10871 }
10872
10880 protected function quote($value, $fieldsentry)
10881 {
10882 if (is_null($value)) {
10883 return 'NULL';
10884 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10885 return price2num((string) $value);
10886 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10887 return (int) $value;
10888 } elseif ($fieldsentry['type'] == 'boolean') {
10889 if ($value) {
10890 return 'true';
10891 } else {
10892 return 'false';
10893 }
10894 } else {
10895 return "'".$this->db->escape($value)."'";
10896 }
10897 }
10898
10899
10907 public function createCommon(User $user, $notrigger = 0)
10908 {
10909 global $conf;
10910 //global $langs; // Should be able to work with $langs loaded
10911
10912 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10913
10914 $error = 0;
10915
10916 $now = dol_now();
10917
10918 $fieldvalues = $this->setSaveQuery();
10919
10920 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10921
10922 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10923 $fieldvalues['date_creation'] = $this->db->idate($now);
10924 $this->date_creation = $this->db->idate($now);
10925 }
10926 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10927 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10928 $fieldvalues['fk_user_creat'] = $user->id;
10929 $this->fk_user_creat = $user->id;
10930 }
10931 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10932 $fieldvalues['user_creation_id'] = $user->id;
10933 $this->user_creation_id = $user->id;
10934 }
10935 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10936 // @phan-suppress-next-line PhanUndeclaredProperty
10937 $tmparray = dol_hash($this->pass, '0', 0, 1);
10938 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10939 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10940 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10941 }
10942 }
10943 if (array_key_exists('ref', $fieldvalues)) {
10944 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10945 }
10946 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
10947 $fieldvalues['tms'] = $this->db->idate($now);
10948 }
10949
10950 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10951
10952 $sanitized_keys = array();
10953 $sanitized_values = array(); // Array to store string forged for SQL syntax
10954 foreach ($fieldvalues as $k => $v) {
10955 $sanitized_keys[$k] = $k; // Safe source @phan-suppress-current-line SqlInjection
10956 $value = $this->fields[$k];
10957 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10958 $sanitized_values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10959 }
10960
10961 // Clean and check mandatory
10962 foreach ($sanitized_keys as $key) {
10963 if (!isset($this->fields[$key])) {
10964 continue;
10965 }
10966 $key_fields = $this->fields[$key];
10967
10968 // If field is an implicit foreign key field (so type = 'integer:...')
10969 if (preg_match('/^integer:/i', $key_fields['type']) && $sanitized_values[$key] == '-1') {
10970 $sanitized_values[$key] = '';
10971 }
10972 if (!empty($key_fields['foreignkey']) && $sanitized_values[$key] == '-1') {
10973 $sanitized_values[$key] = '';
10974 }
10975
10976 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']))) {
10977 $error++;
10978 global $langs;
10979 if (empty($langs)) {
10980 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10981 $langs = new Translate('', $conf);
10982 $langs->setDefaultLang();
10983 $langs->load("errors");
10984 }
10985 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10986 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10987 }
10988
10989 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10990 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'])) {
10991 $sanitized_values[$key] = $this->quote($key_fields['default'], $key_fields);
10992 }
10993
10994 // If field is an implicit foreign key field (so type = 'integer:...')
10995 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($sanitized_values[$key])) {
10996 if (isset($key_fields['default'])) {
10997 $sanitized_values[$key] = ((int) $key_fields['default']);
10998 } else {
10999 $sanitized_values[$key] = 'null';
11000 }
11001 }
11002 if (!empty($key_fields['foreignkey']) && empty($sanitized_values[$key])) {
11003 $sanitized_values[$key] = 'null';
11004 }
11005 }
11006
11007 if ($error) {
11008 return -1;
11009 }
11010
11011 $this->db->begin();
11012
11013 if (!$error) {
11014 $sql = "INSERT INTO ".$this->db->prefix().$this->db->sanitize($this->table_element);
11015 $sql .= " (".implode(", ", $sanitized_keys).')';
11016 $sql .= " VALUES (".implode(", ", $sanitized_values).")"; // $sanitized_values can contains 'abc' or 123
11017
11018 $res = $this->db->query($sql);
11019 if (!$res) {
11020 $error++;
11021 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
11022 $this->errors[] = "ErrorRefAlreadyExists";
11023 } else {
11024 $this->errors[] = $this->db->lasterror();
11025 }
11026 }
11027 }
11028
11029 if (!$error) {
11030 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
11031 }
11032
11033 // If we have a field ref with a default value of (PROV)
11034 if (!$error) {
11035 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
11036 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)') {
11037 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11038 $sql .= " SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
11039 $resqlupdate = $this->db->query($sql);
11040
11041 if ($resqlupdate === false) {
11042 $error++;
11043 $this->errors[] = $this->db->lasterror();
11044 } else {
11045 $this->ref = '(PROV'.$this->id.')';
11046 }
11047 }
11048 }
11049
11050 // Create extrafields
11051 if (!$error) {
11052 $result = $this->insertExtraFields();
11053 if ($result < 0) {
11054 $error++;
11055 }
11056 }
11057
11058 // Create lines
11059 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
11060 foreach ($this->lines as $line) {
11061 $keyforparent = $this->fk_element;
11062 $line->$keyforparent = $this->id;
11063
11064 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
11065 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
11066 if (!is_object($line)) {
11067 $line = (object) $line;
11068 }
11069
11070 $result = 0;
11071 if (method_exists($line, 'insert')) {
11072 $result = $line->insert($user, 1);
11073 } elseif (method_exists($line, 'create')) {
11074 $result = $line->create($user, 1);
11075 }
11076 if ($result < 0) {
11077 $this->error = $line->error;
11078 $this->db->rollback();
11079 return -1;
11080 }
11081 }
11082 }
11083
11084 // Triggers
11085 if (!$error && !$notrigger) {
11086 // Call triggers
11087 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
11088 if ($result < 0) {
11089 $error++;
11090 }
11091 // End call triggers
11092 }
11093
11094 // Commit or rollback
11095 if ($error) {
11096 $this->db->rollback();
11097 return -1;
11098 } else {
11099 $this->db->commit();
11100 return $this->id;
11101 }
11102 }
11103
11104
11114 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
11115 {
11116 if (empty($id) && empty($ref) && empty($morewhere)) {
11117 return -1;
11118 }
11119
11120 $fieldlist = $this->getFieldList('t');
11121 if (empty($fieldlist)) {
11122 return 0;
11123 }
11124
11125 $sql = "SELECT ".$this->db->sanitize($fieldlist, 0, 0, 1);
11126 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
11127
11128 if (!empty($id)) {
11129 $sql .= ' WHERE t.rowid = '.((int) $id);
11130 } elseif (!empty($ref)) {
11131 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
11132 } else {
11133 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
11134 }
11135 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
11136 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
11137 }
11138 if ($morewhere) {
11139 $sql .= $morewhere;
11140 }
11141 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
11142
11143 $res = $this->db->query($sql);
11144 if ($res) {
11145 $obj = $this->db->fetch_object($res);
11146 if ($obj) {
11147 $this->setVarsFromFetchObj($obj);
11148
11149 // Retrieve all extrafield
11150 // fetch optionals attributes and labels
11151 if (empty($noextrafields)) {
11152 $result = $this->fetch_optionals();
11153 if ($result < 0) {
11154 $this->error = $this->db->lasterror();
11155 $this->errors[] = $this->error;
11156 return -4;
11157 }
11158 }
11159
11160 return $this->id;
11161 } else {
11162 return 0;
11163 }
11164 } else {
11165 $this->error = $this->db->lasterror();
11166 $this->errors[] = $this->error;
11167 return -1;
11168 }
11169 }
11170
11178 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
11179 {
11180 $objectlineclassname = get_class($this).'Line';
11181 if (!class_exists($objectlineclassname)) {
11182 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
11183 return -1;
11184 }
11185
11186 $objectline = new $objectlineclassname($this->db);
11187 '@phan-var-force CommonObjectLine $objectline';
11188
11189 $sql = "SELECT ".$objectline->getFieldList('l');
11190 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($objectline->table_element)." as l";
11191 $sql .= " WHERE l.fk_".$this->db->sanitize($this->element)." = ".((int) $this->id);
11192 if ($morewhere) {
11193 $sql .= $morewhere;
11194 }
11195 if (isset($objectline->fields['position'])) {
11196 $sql .= $this->db->order('position', 'ASC');
11197 }
11198
11199 $resql = $this->db->query($sql);
11200 if ($resql) {
11201 $num_rows = $this->db->num_rows($resql);
11202 $i = 0;
11203 $this->lines = array();
11204 while ($i < $num_rows) {
11205 $obj = $this->db->fetch_object($resql);
11206 if ($obj) {
11207 $newline = new $objectlineclassname($this->db);
11208 '@phan-var-force CommonObjectLine $newline';
11209 $newline->setVarsFromFetchObj($obj);
11210
11211 // Load also extrafields for the line
11212 if (empty($noextrafields)) {
11213 $newline->fetch_optionals();
11214 }
11215
11216 $this->lines[] = $newline;
11217 }
11218 $i++;
11219 }
11220
11221 return 1;
11222 } else {
11223 $this->error = $this->db->lasterror();
11224 $this->errors[] = $this->error;
11225 return -1;
11226 }
11227 }
11228
11236 public function updateCommon(User $user, $notrigger = 0)
11237 {
11238 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11239
11240 $error = 0;
11241
11242 $now = dol_now();
11243
11244 // $this->oldcopy should have been set by the caller of update
11245 //if (empty($this->oldcopy)) {
11246 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11247 // $this->oldcopy = dol_clone($this, 2);
11248 //}
11249
11250 $fieldvalues = $this->setSaveQuery();
11251
11252 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11253
11254 if (array_key_exists('date_modification', $fieldvalues)) {
11255 $fieldvalues['date_modification'] = $this->db->idate($now);
11256 }
11257 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
11258 $fieldvalues['tms'] = $this->db->idate($now);
11259 }
11260 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11261 $fieldvalues['fk_user_modif'] = $user->id;
11262 }
11263 if (array_key_exists('user_modification_id', $fieldvalues)) {
11264 $fieldvalues['user_modification_id'] = $user->id;
11265 }
11266 // @phan-suppress-next-line PhanUndeclaredProperty
11267 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11268 // @phan-suppress-next-line PhanUndeclaredProperty
11269 $tmparray = dol_hash($this->pass, '0', 0, 1);
11270 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11271 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11272 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11273 }
11274 }
11275 if (array_key_exists('ref', $fieldvalues)) {
11276 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11277 }
11278
11279 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11280
11281 // Add quotes and escape on fields with type string
11282 $keys = array();
11283 $values = array();
11284 $sanitized_tmp = array();
11285 foreach ($fieldvalues as $k => $v) {
11286 $keys[$k] = $k;
11287 $value = $this->fields[$k];
11288 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11289 $values[$k] = $this->quote($v, $value);
11290 if ((!empty($value["type"]) && $value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11291 // Clean values for text with selectbox
11292 $v = preg_replace('/\s/', ',', $v);
11293 $v = preg_replace('/,+/', ',', $v);
11294 }
11295 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, SqlInjection
11296 $sanitized_tmp[] = $this->db->sanitize($k).'='.$this->quote($v, $this->fields[$k]);
11297 }
11298
11299 // Clean and check mandatory fields
11300 foreach ($keys as $key) {
11301 if (!empty($this->fields[$key]['type']) && preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11302 $values[$key] = ''; // This is an implicit foreign key field
11303 }
11304 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11305 $values[$key] = ''; // This is an explicit foreign key field
11306 }
11307
11308 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11309 /*
11310 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11311 {
11312 $error++;
11313 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11314 }*/
11315 }
11316
11317 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11318 $sql.= " SET ".implode(', ', $sanitized_tmp);
11319 $sql.= " WHERE rowid = ".((int) $this->id);
11320
11321 $this->db->begin();
11322
11323 if (!$error) {
11324 $res = $this->db->query($sql);
11325 if (!$res) {
11326 $error++;
11327 $this->errors[] = $this->db->lasterror();
11328 }
11329 }
11330
11331 // Update extrafield
11332 if (!$error) {
11333 $result = $this->insertExtraFields(); // This update extrafields
11334 if ($result < 0) {
11335 $error++;
11336 }
11337 }
11338
11339 // Triggers
11340 if (!$error && !$notrigger) {
11341 // Call triggers
11342 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11343 if ($result < 0) {
11344 $error++;
11345 } //Do also here what you must do to rollback action if trigger fail
11346 // End call triggers
11347 }
11348
11349 // Commit or rollback
11350 if ($error) {
11351 $this->db->rollback();
11352 return -1;
11353 } else {
11354 $this->db->commit();
11355 return $this->id;
11356 }
11357 }
11358
11367 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11368 {
11369 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11370
11371 $error = 0;
11372
11373 $this->db->begin();
11374
11375 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11376 foreach ($this->childtables as $table) {
11377 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($table);
11378 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11379 $resql = $this->db->query($sql);
11380 if (!$resql) {
11381 $this->error = $this->db->lasterror();
11382 $this->errors[] = $this->error;
11383 $this->db->rollback();
11384 return -1;
11385 }
11386 }
11387 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11388 $objectisused = $this->isObjectUsed($this->id);
11389 if (!empty($objectisused)) {
11390 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11391 $this->error = 'ErrorRecordHasChildren';
11392 $this->errors[] = $this->error;
11393 $this->db->rollback();
11394 return 0;
11395 }
11396 }
11397
11398 // Delete cascade first
11399 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11400 foreach ($this->childtablesoncascade as $tabletodelete) {
11401 $deleteFromObject = explode(':', $tabletodelete, 4);
11402 if (count($deleteFromObject) >= 2) {
11403 $className = str_replace('@', '', $deleteFromObject[0]);
11404 $filePath = $deleteFromObject[1];
11405 $columnName = $deleteFromObject[2];
11406 $filter = '';
11407 if (!empty($deleteFromObject[3])) {
11408 $filter = $deleteFromObject[3];
11409 }
11410
11411 if (dol_include_once($filePath)) {
11412 $childObject = new $className($this->db);
11413 if (method_exists($childObject, 'deleteByParentField')) {
11414 '@phan-var-force CommonObject $childObject';
11415 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11416 if ($result < 0) {
11417 $error++;
11418 $this->errors[] = $childObject->error;
11419 break;
11420 }
11421 } else {
11422 $error++;
11423 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11424 break;
11425 }
11426 } else {
11427 $error++;
11428 $this->errors[] = 'Cannot include child class file '.$filePath;
11429 break;
11430 }
11431 } else {
11432 // Delete record in child table
11433 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($tabletodelete)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11434
11435 $resql = $this->db->query($sql);
11436 if (!$resql) {
11437 $error++;
11438 $this->error = $this->db->lasterror();
11439 $this->errors[] = $this->error;
11440 break;
11441 }
11442 }
11443 }
11444 }
11445
11446 if (!$error) {
11447 if (!$notrigger) {
11448 // Call triggers
11449 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11450 if ($result < 0) {
11451 $error++;
11452 } // Do also here what you must do to rollback action if trigger fail
11453 // End call triggers
11454 }
11455 }
11456
11457 // Delete llx_ecm_files
11458 if (!$error) {
11459 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11460 if (!$res) {
11461 $error++;
11462 }
11463 }
11464
11465 if (!$error) {
11466 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11467 // For remove dir
11468 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11469 if (dol_is_dir($dir)) {
11470 if (!dol_delete_dir_recursive($dir)) {
11471 $this->errors[] = 'ErrorFailToDeleteDir';
11472 }
11473 }
11474 }
11475
11476 // Delete linked object
11477 $res = $this->deleteObjectLinked();
11478 if ($res < 0) {
11479 $error++;
11480 }
11481
11482 if (!$error && !empty($this->isextrafieldmanaged)) {
11483 $result = $this->deleteExtraFields();
11484 if ($result < 0) {
11485 $error++;
11486 }
11487 }
11488
11489 if (!$error) {
11490 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11491 $sql .= " WHERE rowid = ".((int) $this->id);
11492
11493 $resql = $this->db->query($sql);
11494 if (!$resql) {
11495 $error++;
11496 $this->errors[] = $this->db->lasterror();
11497 }
11498 }
11499
11500 // Commit or rollback
11501 if ($error) {
11502 $this->db->rollback();
11503 return -1;
11504 } else {
11505 $this->db->commit();
11506 return 1;
11507 }
11508 }
11509
11521 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11522 {
11523 global $user;
11524
11525 $error = 0;
11526 $deleted = 0;
11527
11528 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11529
11530 if (!empty($parentId) && !empty($parentField)) {
11531 if (empty($this->table_element)) {
11532 $this->error = 'Property table_element for object is not defined';
11533 $this->errors[] = $this->error;
11534 $error++;
11535 return -1;
11536 }
11537 if (!method_exists($this, 'fetch')) {
11538 $this->error = 'Method fetch for object is not defined';
11539 $this->errors[] = $this->error;
11540 $error++;
11541 return -1;
11542 }
11543 if (!method_exists($this, 'delete')) {
11544 $this->error = 'Method delete for object is not defined';
11545 $this->errors[] = $this->error;
11546 $error++;
11547 return -1;
11548 }
11549
11550 $this->db->begin();
11551
11552 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11553 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11554
11555 // Manage filter
11556 $errormessage = '';
11557 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11558 if ($errormessage) {
11559 $this->errors[] = $errormessage;
11560 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11561 return -1;
11562 }
11563
11564 $resql = $this->db->query($sql);
11565 if (!$resql) {
11566 $this->errors[] = $this->db->lasterror();
11567 $error++;
11568 } else {
11569 while ($obj = $this->db->fetch_object($resql)) {
11570 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11571 if ($result < 0) {
11572 $error++;
11573 $this->errors[] = $this->error;
11574 } else {
11575 $result = $this->delete($user); // @phpstan-ignore-line
11576 if ($result < 0) {
11577 $error++;
11578 $this->errors[] = $this->error;
11579 } else {
11580 $deleted++;
11581 }
11582 }
11583 }
11584 }
11585
11586 if (empty($error)) {
11587 $this->db->commit();
11588 return $deleted;
11589 } else {
11590 $this->error = implode(', ', $this->errors);
11591 $this->db->rollback();
11592 return $error * -1;
11593 }
11594 }
11595
11596 return $deleted;
11597 }
11598
11606 public function isLineOfObject($lineid)
11607 {
11608 if (!($this->id > 0) || empty($this->table_element_line) || empty($this->fk_element)) {
11609 return true;
11610 }
11611
11612 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
11613 $sql .= " WHERE rowid = ".((int) $lineid)." AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11614 $resql = $this->db->query($sql);
11615
11616 return ($resql && $this->db->num_rows($resql) > 0);
11617 }
11618
11627 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11628 {
11629 $error = 0;
11630
11631 $tmpforobjectclass = get_class($this);
11632 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11633
11634 if ($this->id > 0 && !empty($this->fk_element)) {
11635 // The line must belong to this object
11636 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
11637 $sql .= " WHERE rowid = ".((int) $idline)." AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11638 $resql = $this->db->query($sql);
11639 if (!$resql || !$this->db->num_rows($resql)) {
11640 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
11641 return -1;
11642 }
11643 }
11644
11645 $this->db->begin();
11646
11647 // Call trigger
11648 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11649 if ($result < 0) {
11650 $error++;
11651 }
11652 // End call triggers
11653
11654 if (empty($error)) {
11655 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
11656 $sql .= " WHERE rowid = ".((int) $idline);
11657
11658 $resql = $this->db->query($sql);
11659 if (!$resql) {
11660 $this->error = "Error ".$this->db->lasterror();
11661 $error++;
11662 }
11663 }
11664
11665 if (empty($error)) {
11666 // Remove extrafields
11667 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11668 '@phan-var-force CommonObjectLine $tmpobjectline';
11669 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11670 $tmpobjectline->id = $idline;
11671 $result = $tmpobjectline->deleteExtraFields();
11672 if ($result < 0) {
11673 $error++;
11674 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11675 }
11676 }
11677 }
11678
11679 if (empty($error)) {
11680 $this->db->commit();
11681 return 1;
11682 } else {
11683 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11684 $this->db->rollback();
11685 return -1;
11686 }
11687 }
11688
11689
11700 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11701 {
11702 $error = 0;
11703
11704 $this->db->begin();
11705
11706 $statusfield = 'status';
11707 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11708 $statusfield = 'fk_statut';
11709 }
11710
11711 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11712 $sql .= " SET ".$this->db->sanitize($statusfield)." = ".((int) $status);
11713 $sql .= " WHERE rowid = ".((int) $this->id);
11714
11715 if ($this->db->query($sql)) {
11716 if (!$error) {
11717 $this->oldcopy = clone $this;
11718 }
11719
11720 if (!$error && !$notrigger) {
11721 // Call trigger
11722 $result = $this->call_trigger($triggercode, $user);
11723 if ($result < 0) {
11724 $error++;
11725 }
11726 }
11727
11728 if (!$error) {
11729 $this->status = $status;
11730 if (property_exists($this, 'statut')) { // For backward compatibility
11731 $this->statut = $status;
11732 }
11733 $this->db->commit();
11734 return 1;
11735 } else {
11736 $this->db->rollback();
11737 return -1;
11738 }
11739 } else {
11740 $this->error = $this->db->error();
11741 $this->db->rollback();
11742 return -1;
11743 }
11744 }
11745
11752 public function initAsSpecimenCommon()
11753 {
11754 global $user;
11755
11756 $this->id = 0;
11757 $this->specimen = 1;
11758 $fields = array(
11759 'label' => 'This is label',
11760 'ref' => 'ABCD1234',
11761 'description' => 'This is a description',
11762 'qty' => 123.12,
11763 'note_public' => 'Public note',
11764 'note_private' => 'Private note',
11765 'date_creation' => (dol_now() - 3600 * 48),
11766 'date_modification' => (dol_now() - 3600 * 24),
11767 'fk_user_creat' => $user->id,
11768 'fk_user_modif' => $user->id,
11769 'date' => dol_now(),
11770 );
11771 foreach ($fields as $key => $value) {
11772 if (array_key_exists($key, $this->fields)) {
11773 $this->{$key} = $value; // @phpstan-ignore-line
11774 }
11775 }
11776
11777 // Force values to default values when known
11778 if (property_exists($this, 'fields')) {
11779 foreach ($this->fields as $key => $value) {
11780 // If fields are already set, do nothing
11781 if (array_key_exists($key, $fields)) {
11782 continue;
11783 }
11784
11785 if (!empty($value['default'])) {
11786 $this->$key = $value['default'];
11787 }
11788 }
11789 }
11790
11791 return 1;
11792 }
11793
11794
11795 /* Part for comments */
11796
11802 public function fetchComments()
11803 {
11804 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11805
11806 $comment = new Comment($this->db);
11807 $result = $comment->fetchAllFor($this->element, $this->id);
11808 if ($result < 0) {
11809 $this->setErrorsFromObject($comment);
11810 return -1;
11811 } else {
11812 $this->comments = $comment->comments;
11813 }
11814 return count($this->comments);
11815 }
11816
11822 public function getNbComments()
11823 {
11824 return count($this->comments);
11825 }
11826
11833 public function trimParameters($parameters)
11834 {
11835 if (!is_array($parameters)) {
11836 return;
11837 }
11838 foreach ($parameters as $parameter) {
11839 if (isset($this->$parameter)) {
11840 $this->$parameter = trim($this->$parameter);
11841 }
11842 }
11843 }
11844
11845 /* Part for categories/tags */
11846
11857 public function getCategoriesCommon($type_categ)
11858 {
11859 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11860
11861 // Get current categories
11862 $c = new Categorie($this->db);
11863 $existing = $c->containing($this->id, $type_categ, 'id');
11864
11865 return $existing;
11866 }
11867
11880 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11881 {
11882 // Handle single category
11883 if (!is_array($categories)) {
11884 $categories = array($categories);
11885 }
11886
11887 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11888
11889 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11890
11891 if (empty($type_categ)) {
11892 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11893 return -1;
11894 }
11895
11896 // Get current categories
11897 $c = new Categorie($this->db);
11898 $existing = $c->containing($this->id, $type_categ, 'id');
11899 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11900 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11901 if (!is_array($existing)) {
11902 $existing = array();
11903 }
11904 if ($remove_existing) {
11905 // Diff
11906 $to_del = array_diff($existing, $categories);
11907 $to_add = array_diff($categories, $existing);
11908 } else {
11909 $to_del = array(); // Nothing to delete
11910 $to_add = array_diff($categories, $existing);
11911 }
11912
11913 $error = 0;
11914 $ok = 0;
11915
11916 // Process
11917 foreach ($to_del as $del) {
11918 if ($c->fetch($del) > 0) {
11919 $result = $c->del_type($this, $type_categ);
11920 if ($result < 0) {
11921 $error++;
11922 $this->error = $c->error;
11923 $this->errors = $c->errors;
11924 break;
11925 } else {
11926 $ok += $result;
11927 }
11928 }
11929 }
11930 foreach ($to_add as $add) {
11931 if ($c->fetch($add) > 0) {
11932 $result = $c->add_type($this, $type_categ);
11933 if ($result < 0) {
11934 $error++;
11935 $this->error = $c->error;
11936 $this->errors = $c->errors;
11937 break;
11938 } else {
11939 $ok += $result;
11940 }
11941 }
11942 }
11943
11944 return $error ? (-1 * $error) : $ok;
11945 }
11946
11955 public function cloneCategories($fromId, $toId, $type = '')
11956 {
11957 $this->db->begin();
11958
11959 if (empty($type)) {
11960 $type = $this->table_element;
11961 }
11962
11963 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11964 $categorystatic = new Categorie($this->db);
11965
11966 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11967 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11968
11969 $sql = "INSERT INTO ".$this->db->sanitize($tablename)." (fk_categorie, ".$this->db->sanitize($fkname).")";
11970 $sql .= " SELECT fk_categorie, ".((int) $toId)." FROM ".$this->db->sanitize($tablename);
11971 $sql .= " WHERE ".$this->db->sanitize($fkname)." = ".((int) $fromId);
11972
11973 if (!$this->db->query($sql)) {
11974 $this->error = $this->db->lasterror();
11975 $this->db->rollback();
11976 return -1;
11977 }
11978
11979 $this->db->commit();
11980 return 1;
11981 }
11982
11989 public function deleteEcmFiles($mode = 0)
11990 {
11991 global $conf;
11992
11993 $this->db->begin();
11994
11995 // Delete in database with mode 0
11996 if ($mode == 0) {
11997 switch ($this->element) {
11998 case 'propal':
11999 $element = 'propale';
12000 break;
12001 case 'product':
12002 $element = 'produit';
12003 break;
12004 case 'order_supplier':
12005 $element = 'fournisseur/commande';
12006 break;
12007 case 'invoice_supplier':
12008 // Special cases that need to use get_exdir to get real dir of object
12009 // In future, all object should use this to define path of documents.
12010 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
12011 break;
12012 case 'shipping':
12013 $element = 'expedition/sending';
12014 break;
12015 case 'task':
12016 case 'project_task':
12017 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
12018
12019 $project_result = $this->fetchProject();
12020 if ($project_result >= 0) {
12021 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
12022 }
12023 // no break
12024 case 'contrat':
12025 $element = 'contract';
12026 break;
12027 default:
12028 $element = $this->element;
12029 }
12030 '@phan-var-force string $element';
12031
12032 // Delete ecm_files_extrafields with mode 0 (using name)
12033 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
12034 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
12035 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
12036 $sql .= ")";
12037
12038 if (!$this->db->query($sql)) {
12039 $this->error = $this->db->lasterror();
12040 $this->db->rollback();
12041 return false;
12042 }
12043
12044 // Delete ecm_files with mode 0 (using name)
12045 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
12046 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
12047 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
12048
12049 if (!$this->db->query($sql)) {
12050 $this->error = $this->db->lasterror();
12051 $this->db->rollback();
12052 return false;
12053 }
12054 }
12055
12056 // Delete in database with mode 1
12057 if ($mode == 1) {
12058 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
12059 $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).")";
12060 $resql = $this->db->query($sql);
12061 if (!$resql) {
12062 $this->error = $this->db->lasterror();
12063 $this->db->rollback();
12064 return false;
12065 }
12066
12067 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
12068 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
12069 $resql = $this->db->query($sql);
12070 if (!$resql) {
12071 $this->error = $this->db->lasterror();
12072 $this->db->rollback();
12073 return false;
12074 }
12075 }
12076
12077 $this->db->commit();
12078 return true;
12079 }
12080
12089 public function checkActiveProductInLines($status = 'onsale')
12090 {
12091 global $langs;
12092
12093 if (isModEnabled('product') || isModEnabled('service')) {
12094 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
12095
12096 $ret = true;
12097 $tmpproduct = new Product($this->db);
12098 foreach ($this->lines as $line) {
12099 if ($line->fk_product > 0) {
12100 $tmpproduct->fetch($line->fk_product);
12101 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
12102 if (!$tmpproduct->$statustotest) {
12103 $langs->load('products');
12104 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
12105 $ret = false;
12106 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
12107 break;
12108 }
12109 }
12110 }
12111 if (!$ret) {
12112 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
12113 }
12114 return $ret;
12115 } else {
12116 return true;
12117 }
12118 }
12119
12126 public function setErrorWithoutLog($message)
12127 {
12128 $this->error = $message;
12129 $this->errors[] = $message;
12130 }
12131
12139 public function setErrorWithLog($message, $loglevel = LOG_ERR)
12140 {
12141 global $dolibarr_main_document_root;
12142
12143 $this->setErrorWithoutLog($message);
12144 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
12145 $file = str_replace($dolibarr_main_document_root, '', $trace[0]['file'] ?? 'file unknown');
12146 $line = $trace[0]['line'] ?? 'line unknown';
12147 $syslogMessage = sprintf('%s:%s %s', $file, $line, $message);
12148 dol_syslog($syslogMessage, $loglevel);
12149 }
12150}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
global $dolibarr_main_url_root
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:476
$c
Definition line.php:334
$object ref
Definition info.php:90
Class to manage members of a foundation.
Class to manage categories.
Class to manage comment.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
getCategoriesCommon($type_categ)
Sets object to given categories.
indexFile($destfull, $update_main_doc_field)
Index a file into the ECM database.
getFormatedSupplierRef($objref)
Return supplier ref for screen output.
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteLineCommon(User $user, $idline, $notrigger=0)
Delete a line of object in database.
isEmpty()
isEmpty We consider CommonObject isEmpty if this->id is empty
clearFieldError($fieldKey)
clear validation message result for a field
static getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Count items linked to an object id in association table.
deleteEcmFiles($mode=0)
Delete related files of object in database.
getLastMainDocLink($modulepart, $initsharekey=0, $relativelink=0)
Return the link of last main doc file for direct public download.
liste_type_contact($source='internal', $order='position', $option=0, $activeonly=0, $code='')
Return array with list of possible values for type of contacts.
getTooltipContent($params)
getTooltipContent
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
swapContactStatus($rowid)
Update status of a contact linked to object.
getFieldError($fieldKey)
get field error message
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='')
Set status of an object.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
updateObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $f_user=null, $notrigger=0)
Update object linked of a current object.
setErrorWithoutLog($message)
Set the error message for the object without logging it.
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.
addThumbs($file, $quality=50)
Build thumb.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
cloneCategories($fromId, $toId, $type='')
Copy related categories to another object.
fetch_barcode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
Class to manage contact/addresses.
Class to manage absolute discounts.
Class to manage a WYSIWYG editor.
Class to manage Dolibarr database access.
Class to manage donations.
Definition don.class.php:41
Class to manage ECM files.
Class to manage standard extra fields.
static isEmptyValue($v, string $type)
Return if a value is "empty" for a mandatory vision.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage generation of HTML components Only common components must be here.
Parent class of subscription templates.
Parent class to manage intervention document templates.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_SERVICE
Service.
Class to manage projects.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
Class toolbox to validate values.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
getState($id, $withcode='0', $dbtouse=null, $withregion=0, $outputlangs=null, $entconv=1)
Return state translated from an id.
convertSecondToTime($iSecond, $format='all', $lengthOfDay=86400, $lengthOfWeek=7)
Return, in clear text, value of a number of seconds in days, hours and minutes.
Definition date.lib.php:263
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_meta_create($object)
Create a meta file with document file into same directory.
completeFileArrayWithDatabaseInfo(&$filearray, $relativedir, $object=null)
Complete $filearray with data from database.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_is_dir($folder)
Test if filename is a directory.
dol_delete_preview($object)
Delete all preview files linked to object instance.
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getPictoForType($key, $morecss='')
Return the picto for a data type.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
getDictionaryValue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
Return the value of a filed into a dictionary for the record $id.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png, bmp and webp)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:141
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:137
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.