dolibarr 25.0.0-alpha
commonobject.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2015 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
5 * Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
6 * Copyright (C) 2011-2022 Philippe Grand <philippe.grand@atoo-net.com>
7 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
8 * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
10 * Copyright (C) 2015-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
11 * Copyright (C) 2016 Bahfir abbes <bafbes@gmail.com>
12 * Copyright (C) 2017 ATM Consulting <support@atm-consulting.fr>
13 * Copyright (C) 2017-2026 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2017 Rui Strecht <rui.strecht@aliartalentos.com>
15 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
16 * Copyright (C) 2018 Josep Lluís Amador <joseplluis@lliuretic.cat>
17 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
18 * Copyright (C) 2021 Grégory Blémand <gregory.blemand@atm-consulting.fr>
19 * Copyright (C) 2023 Lenin Rivas <lenin.rivas777@gmail.com>
20 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
21 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
22 * Copyright (C) 2025 Alexandre Janniaux <alexandre.janniaux@gmail.com>
23 * Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
24 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
25 * Copyright (C) 2026 Anthony Berton <anthony.berton@bb2a.fr>
26
27 *
28 * This program is free software; you can redistribute it and/or modify
29 * it under the terms of the GNU General Public License as published by
30 * the Free Software Foundation; either version 3 of the License, or
31 * (at your option) any later version.
32 *
33 * This program is distributed in the hope that it will be useful,
34 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 * GNU General Public License for more details.
37 *
38 * You should have received a copy of the GNU General Public License
39 * along with this program. If not, see <https://www.gnu.org/licenses/>.
40 */
41
48require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
49require_once DOL_DOCUMENT_ROOT.'/core/class/commontrigger.class.php';
50
58abstract class CommonObject
59{
60 use DolDeprecationHandler;
61 use CommonTrigger;
62
66 public $module;
67
71 public $db;
72
76 public $id;
77
81 public $entity;
82
87 public $error;
88
92 public $errorhidden;
93
97 public $errors = array();
98
103 public $warning;
104
108 public $warnings = array();
109
113 private $validateFieldsErrors = array();
114
118 public $element;
119
124 public $fk_element;
125
131 public $element_for_permission;
132
136 public $table_element;
137
141 public $table_rowid;
142
146 public $table_element_line = '';
147
152 public $ismultientitymanaged;
153
157 public $import_key;
158
162 public $array_options = array();
163
164
243 public $fields = array();
244
250 public $array_languages = null; // Value is array() when load already tried
251
255 public $contacts_ids;
256
260 public $contacts_ids_internal;
261
265 public $linked_objects;
266
270 public $linkedObjectsIds;
271
275 public $linkedObjects;
276
280 private $linkedObjectsFullLoaded = array();
281
285 public $oldcopy;
286
290 public $oldref;
291
295 protected $table_ref_field = '';
296
300 public $restrictiononfksoc = 0;
301
302
303 // The following vars are used by some objects only.
304 // We keep these properties in CommonObject in order to provide common methods using them.
305
309 public $context = array();
310
314 public $actionmsg;
318 public $actionmsg2;
319
323 public $canvas;
324
329 public $project;
330
335 public $fk_project;
336
342 public $fk_projet;
343
348 public $contact;
349
354 public $contact_id;
355
360 public $thirdparty;
361
366 public $user;
367
372 public $product;
373
378 public $warehouse;
379
384 public $origin_type;
385
390 public $origin_id;
391
396
402 public $origin;
403
412 private $expedition;
413
419 private $livraison;
420
426 private $commandeFournisseur;
427
428
432 public $ref;
433
437 public $ref_ext;
438
442 public $ref_previous;
443
447 public $ref_next;
448
452 public $newref;
453
460 public $statut;
461
470 public $status;
471
476 public $country;
477
482 public $country_id;
483
488 public $country_code;
489
494 public $state;
495
500 public $state_id;
501
506 public $state_code;
507
512 public $region_id;
513
518 public $region_code;
519
524 public $region;
525
526
531 public $barcode_type;
532
537 public $barcode_type_code;
538
543 public $barcode_type_label;
544
549 public $barcode_type_coder;
550
555 public $mode_reglement_id;
556
561 public $cond_reglement_id;
562
566 public $demand_reason_id;
567
572 public $transport_mode_id;
573
584 protected $depr_cond_reglement; // Internal value for deprecation
585
591 public $fk_delivery_address;
592
597 public $shipping_method_id;
598
603 public $shipping_method;
604
605 // Multicurrency
609 public $fk_multicurrency;
610
615 public $multicurrency_code;
616
621 public $multicurrency_tx;
622
626 public $multicurrency_total_ht;
627
631 public $multicurrency_total_tva;
632
636 public $multicurrency_total_localtax1; // not in database
637
641 public $multicurrency_total_localtax2; // not in database
642
646 public $multicurrency_total_ttc;
647
648
653 public $model_pdf;
654
659 public $last_main_doc;
660
666 public $fk_bank;
667
672 public $fk_account;
673
678 public $note_public;
679
684 public $note_private;
685
691 public $note;
692
697 public $total_ht;
698
703 public $total_tva;
704
709 public $total_localtax1;
710
715 public $total_localtax2;
716
721 public $total_ttc;
722
723
727 public $lines;
728
732 public $actiontypecode;
733
738 public $comments = array();
739
743 public $name;
744
748 public $lastname;
749
753 public $firstname;
754
759 public $civility_id;
760
764 public $civility_code;
765
766 // Dates
770 public $date_creation;
771
775 public $date_validation;
776
780 public $date_modification;
781
787 public $tms;
788
794 private $date_update;
795
799 public $date_cloture;
800
804 public $user_creation_id;
805
809 public $user_validation_id;
810
814 public $user_closing_id;
815
819 public $user_modification_id;
820
825 public $fk_user_creat;
826
831 public $fk_user_modif;
832
833
837 public $next_prev_filter;
838
842 public $specimen = 0;
843
847 public $sendtoid;
848
849
854 public $alreadypaid;
855
860 public $totalpaid;
861
866 public $totalpaid_multicurrency;
867
868
872 public $labelStatus = array();
873
877 public $labelStatusShort = array();
878
882 public $tpl;
883
884
888 public $showphoto_on_popup;
889
893 public $nb = array();
894
898 public $nbphoto;
899
903 public $output;
904
908 public $extraparams = array();
909
913 protected $childtables = array();
914
920 protected $childtablesoncascade = array();
921
925 public $cond_reglement_supplier_id;
926
932 public $deposit_percent;
933
934
938 public $warehouse_id;
939
943 public $isextrafieldmanaged = 0;
944
945
946 // No constructor as it is an abstract class
947
953 protected function deprecatedProperties()
954 {
955 return array(
956 'alreadypaid' => 'totalpaid',
957 'cond_reglement' => 'depr_cond_reglement',
958 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
959 'commandeFournisseur' => 'origin_object',
960 'expedition' => 'origin_object',
961 'fk_project' => 'fk_project',
962 'livraison' => 'origin_object',
963 'projet' => 'project',
964 'statut' => 'status',
965 );
966 }
967
968
979 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
980 {
981 global $db, $conf;
982
983 $sql = "SELECT rowid";
984 $sql .= " FROM ".$db->prefix().$element;
985 $sql .= " WHERE entity IN (".getEntity($element).")";
986
987 if ($id > 0) {
988 $sql .= " AND rowid = ".((int) $id);
989 } elseif ($ref) {
990 $sql .= " AND ref = '".$db->escape($ref)."'";
991 } elseif ($ref_ext) {
992 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
993 } else {
994 $error = 'ErrorWrongParameters';
995 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
996 return -1;
997 }
998 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
999 $sql .= " AND entity = ".((int) $conf->entity);
1000 }
1001
1002 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
1003 $resql = $db->query($sql);
1004 if ($resql) {
1005 $num = $db->num_rows($resql);
1006 if ($num > 0) {
1007 return 1;
1008 } else {
1009 return 0;
1010 }
1011 }
1012 return -1;
1013 }
1014
1020 public function isEmpty()
1021 {
1022 return (empty($this->id));
1023 }
1024
1032 {
1033 if (!empty($object->error)) {
1034 $this->error = $object->error;
1035 }
1036 if (!empty($object->errors)) {
1037 $this->errors = array_merge($this->errors, $object->errors);
1038 }
1039 }
1040
1048 public function getTooltipContentArray($params)
1049 {
1050 return [];
1051 }
1052
1060 public function getTooltipContent($params)
1061 {
1062 global $action, $extrafields, $langs, $hookmanager;
1063
1064 // If there is too much extrafields, we do not include them into tooltip
1065 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
1066
1067 $data = $this->getTooltipContentArray($params);
1068 $count = 0;
1069
1070 // Add extrafields
1071 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1072 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1073 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1074 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1075 continue;
1076 }
1077
1078 if (empty($extrafields->attributes[$this->table_element]['showintooltip'][$key])) {
1079 continue;
1080 }
1081
1082
1083 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1084 $data['more_extrafields'] = '<br>...';
1085 break;
1086 }
1087 $enabled = 1;
1088 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1089 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1090 }
1091 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1092 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1093 }
1094 $perms = 1;
1095 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1096 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1097 }
1098 if (empty($enabled)) {
1099 continue; // 0 = Never visible field
1100 }
1101 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1102 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1103 }
1104 if (empty($perms)) {
1105 continue; // 0 = Not visible
1106 }
1107 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1108 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1109 }
1110 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1111 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1112 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1113 } else {
1114 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1115 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1116 $count++;
1117 }
1118 }
1119 $data['closedivextra'] = '</div>';
1120 }
1121
1122 $hookmanager->initHooks(array($this->element . 'dao'));
1123 $parameters = array(
1124 'tooltipcontentarray' => &$data,
1125 'params' => $params,
1126 );
1127 // Note that $action and $object may have been modified by some hooks
1128 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1129
1130 //var_dump($data);
1131 $label = implode($data);
1132
1133 return $label;
1134 }
1135
1136
1142 public function errorsToString()
1143 {
1144 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1145 }
1146
1147
1154 public function getFormatedCustomerRef($objref)
1155 {
1156 global $hookmanager;
1157
1158 $parameters = array('objref' => $objref);
1159 $action = '';
1160 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1161 if ($reshook > 0) {
1162 return $hookmanager->resArray['objref'];
1163 }
1164 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1165 }
1166
1173 public function getFormatedSupplierRef($objref)
1174 {
1175 global $hookmanager;
1176
1177 $parameters = array('objref' => $objref);
1178 $action = '';
1179 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1180 if ($reshook > 0) {
1181 return $hookmanager->resArray['objref'];
1182 }
1183 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1184 }
1185
1195 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1196 {
1197 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1198 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1199 $tmparray = getCountry($this->country_id, 'all');
1200 $this->country_code = $tmparray['code'];
1201 $this->country = $tmparray['label'];
1202 }
1203
1204 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1205 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1206 $tmparray = getState($this->state_id, 'all', null, 1);
1207 $this->state_code = $tmparray['code'];
1208 $this->state = $tmparray['label'];
1209 $this->region_code = $tmparray['region_code'];
1210 $this->region = $tmparray['region'];
1211 }
1212
1213 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1214 }
1215
1216
1225 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1226 {
1227 global $user, $dolibarr_main_url_root;
1228
1229 if (empty($this->last_main_doc)) {
1230 return ''; // No way to known which document name to use
1231 }
1232
1233 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1234 $ecmfile = new EcmFiles($this->db);
1235 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1236 if ($result < 0) {
1237 $this->error = $ecmfile->error;
1238 $this->errors = $ecmfile->errors;
1239 return -1;
1240 }
1241
1242 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1243 // Add entry into index
1244 if ($initsharekey) {
1245 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1246
1247 // 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
1248 /*
1249 $ecmfile->filepath = $rel_dir;
1250 $ecmfile->filename = $filename;
1251 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1252 $ecmfile->fullpath_orig = '';
1253 $ecmfile->gen_or_uploaded = 'generated';
1254 $ecmfile->description = ''; // indexed content
1255 $ecmfile->keywords = ''; // keyword content
1256 $ecmfile->share = getRandomPassword(true);
1257 $result = $ecmfile->create($user);
1258 if ($result < 0)
1259 {
1260 $this->error = $ecmfile->error;
1261 $this->errors = $ecmfile->errors;
1262 }
1263 */
1264 } else {
1265 return '';
1266 }
1267 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1268 // Add entry into index
1269 if ($initsharekey) {
1270 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1271 $ecmfile->share = getRandomPassword(true);
1272 $ecmfile->update($user);
1273 } else {
1274 return '';
1275 }
1276 }
1277 // Define $urlwithroot
1278 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1279 // This is to use external domain name found into config file
1280 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1281 //else
1282 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1283 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1284
1285 $forcedownload = 0;
1286
1287 $paramlink = '';
1288 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1289 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1290 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1291 if (!empty($ecmfile->share)) {
1292 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1293 }
1294 if ($forcedownload) {
1295 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1296 }
1297
1298 if ($relativelink) {
1299 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1300 } else {
1301 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1302 }
1303
1304 // Here $ecmfile->share is defined
1305 return $linktoreturn;
1306 }
1307
1308
1309 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1319 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1320 {
1321 // phpcs:enable
1322 global $user, $langs;
1323
1324 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1325
1326 // Check parameters
1327 if ($fk_socpeople <= 0) {
1328 $langs->load("errors");
1329 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1330 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1331 return -1;
1332 }
1333
1334 // Check that the contact actually exists in database
1335 $sql_check = "SELECT rowid FROM ".$this->db->prefix().($source == 'internal' ? "user" : "socpeople")." WHERE rowid = ".((int) $fk_socpeople);
1336 $resql_check = $this->db->query($sql_check);
1337 if ($resql_check) {
1338 if (!$this->db->num_rows($resql_check)) {
1339 $langs->load("errors");
1340 $this->error = $langs->trans("ErrorRecordNotFound");
1341 dol_syslog(get_class($this)."::add_contact Contact/user id ".$fk_socpeople." does not exist", LOG_ERR);
1342 return -1;
1343 }
1344 } else {
1345 $this->error = $this->db->lasterror();
1346 return -1;
1347 }
1348
1349 if (!$type_contact) {
1350 $langs->load("errors");
1351 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1352 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1353 return -2;
1354 }
1355
1356 // socid is checked: @phan-suppress-next-line PhanUndeclaredProperty
1357 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && $user->id > 0 && !$user->hasRight('societe', 'client', 'voir')) {
1358 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1359 // socid is checked: @phan-suppress-next-line PhanUndeclaredProperty
1360 $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid;
1361 $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id;
1362
1363 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1364
1365 if (!$resql_allowed_contacts) {
1366 $this->errors[] = $this->db->lasterror();
1367 return -3;
1368 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1369 if ($obj->cnt == 0) {
1370 $langs->load("companies");
1371 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1372 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1373 return -4;
1374 }
1375 }
1376 }
1377
1378 $id_type_contact = 0;
1379 if (is_numeric($type_contact)) {
1380 $id_type_contact = $type_contact;
1381 } else {
1382 // We look for id type_contact
1383 $sql = "SELECT tc.rowid";
1384 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1385 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1386 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1387 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1388 //print $sql;
1389 $resql = $this->db->query($sql);
1390 if ($resql) {
1391 $obj = $this->db->fetch_object($resql);
1392 if ($obj) {
1393 $id_type_contact = $obj->rowid;
1394 }
1395 }
1396 }
1397 if ($id_type_contact == 0) {
1398 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");
1399 return 0;
1400 }
1401
1402 $datecreate = dol_now();
1403
1404 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1405 $TListeContacts = $this->liste_contact(-1, $source);
1406 $already_added = false;
1407 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1408 foreach ($TListeContacts as $array_contact) {
1409 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1410 $already_added = true;
1411 break;
1412 }
1413 }
1414 }
1415
1416 if (!$already_added) {
1417 $this->db->begin();
1418
1419 // Insert into database
1420 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1421 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1422 $sql .= " VALUES (".((int) $this->id).", ".((int) $fk_socpeople)." , ";
1423 $sql .= "'".$this->db->idate($datecreate)."'";
1424 $sql .= ", 4, ".((int) $id_type_contact);
1425 $sql .= ")";
1426
1427 $resql = $this->db->query($sql);
1428 if ($resql) {
1429 if (!$notrigger) {
1430 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1431 $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user);
1432 if ($result < 0) {
1433 $this->db->rollback();
1434 return -5;
1435 }
1436 }
1437
1438 $this->db->commit();
1439 return 1;
1440 } else {
1441 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1442 $this->error = $this->db->errno();
1443 $this->db->rollback();
1444 return -6;
1445 } else {
1446 $this->error = $this->db->lasterror();
1447 $this->db->rollback();
1448 return -7;
1449 }
1450 }
1451 } else {
1452 return 0;
1453 }
1454 }
1455
1456 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1464 public function copy_linked_contact($objFrom, $source = 'internal')
1465 {
1466 // phpcs:enable
1467 $contacts = $objFrom->liste_contact(-1, $source);
1468 foreach ($contacts as $contact) {
1469 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1470 return -1;
1471 }
1472 }
1473 return 1;
1474 }
1475
1476 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1486 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1487 {
1488 // phpcs:enable
1489 // Insert into database
1490 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1491 $sql .= " statut = ".((int) $statut);
1492 if ($type_contact_id) {
1493 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1494 }
1495 if ($fk_socpeople) {
1496 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1497 }
1498 $sql .= " where rowid = ".((int) $rowid);
1499
1500 $resql = $this->db->query($sql);
1501 if ($resql) {
1502 return 0;
1503 } else {
1504 $this->error = $this->db->lasterror();
1505 return -1;
1506 }
1507 }
1508
1509 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1517 public function delete_contact($rowid, $notrigger = 0)
1518 {
1519 // phpcs:enable
1520 global $user;
1521
1522 $error = 0;
1523
1524 $this->db->begin();
1525
1526 if (!$error && empty($notrigger)) {
1527 // Call trigger
1528 $this->context['contact_id'] = ((int) $rowid);
1529 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1530 $result = $this->call_trigger($triggerPrefix.'_DELETE_CONTACT', $user);
1531 if ($result < 0) {
1532 $error++;
1533 }
1534 // End call triggers
1535 }
1536
1537 if (!$error) {
1538 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1539
1540 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1541 $sql .= " WHERE rowid = ".((int) $rowid);
1542
1543 $result = $this->db->query($sql);
1544 if (!$result) {
1545 $error++;
1546 $this->errors[] = $this->db->lasterror();
1547 }
1548 }
1549
1550 if (!$error) {
1551 $this->db->commit();
1552 return 1;
1553 } else {
1554 $this->error = $this->db->lasterror();
1555 $this->db->rollback();
1556 return -1;
1557 }
1558 }
1559
1560 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1568 public function delete_linked_contact($source = '', $code = '')
1569 {
1570 // phpcs:enable
1571 $listId = '';
1572 $temp = array();
1573 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1574
1575 if (!empty($typeContact)) {
1576 foreach ($typeContact as $key => $value) {
1577 array_push($temp, $key);
1578 }
1579 $listId = implode(",", $temp);
1580 }
1581
1582 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1583 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1584 if (empty($listId)) {
1585 return 0;
1586 }
1587
1588 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1589 $sql .= " WHERE element_id = ".((int) $this->id);
1590 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1591
1592 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1593 if ($this->db->query($sql)) {
1594 return 1;
1595 } else {
1596 $this->error = $this->db->lasterror();
1597 return -1;
1598 }
1599 }
1600
1601 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1613 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1614 {
1615 // phpcs:enable
1616 global $langs;
1617
1618 $tab = array();
1619
1620 $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
1621 if ($source == 'internal') {
1622 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1623 }
1624 if ($source == 'external' || $source == 'thirdparty') {
1625 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1626 }
1627 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1628 if (empty($arrayoftcids)) {
1629 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1630 }
1631 $sql .= " FROM";
1632 if (empty($arrayoftcids)) {
1633 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1634 }
1635 $sql .= " ".$this->db->prefix()."element_contact as ec";
1636 if ($source == 'internal') { // internal contact (user)
1637 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1638 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1639 }
1640 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1641 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1642 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1643 }
1644 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1645 if (empty($arrayoftcids)) {
1646 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1647 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1648 if ($code) {
1649 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1650 }
1651 if ($source == 'internal') {
1652 $sql .= " AND tc.source = 'internal'";
1653 }
1654 if ($source == 'external' || $source == 'thirdparty') {
1655 $sql .= " AND tc.source = 'external'";
1656 }
1657 $sql .= " AND tc.active = 1";
1658 } else {
1659 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1660 }
1661 if ($status >= 0) {
1662 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1663 }
1664 if ($statusoflink >= 0) {
1665 $sql .= " AND ec.statut = ".((int) $statusoflink);
1666 }
1667 $sql .= " ORDER BY t.lastname ASC";
1668
1669 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1670 $resql = $this->db->query($sql);
1671 if ($resql) {
1672 $num = $this->db->num_rows($resql);
1673 $i = 0;
1674 while ($i < $num) {
1675 $obj = $this->db->fetch_object($resql);
1676
1677 if (!$list) {
1678 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1679 $label_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1680 $tab[$i] = array(
1681 'parentId' => $this->id,
1682 'source' => $obj->source,
1683 'socid' => $obj->socid,
1684 'id' => $obj->id,
1685 'nom' => $obj->lastname, // For backward compatibility
1686 'civility' => $obj->civility,
1687 'lastname' => $obj->lastname,
1688 'firstname' => $obj->firstname,
1689 'email' => $obj->email,
1690 'address' => $obj->address,
1691 'zip' => $obj->zip,
1692 'town' => $obj->town,
1693 'country_id' => $obj->country_id,
1694 'country' => $obj->country,
1695 'login' => (empty($obj->login) ? '' : $obj->login),
1696 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1697 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1698 'statuscontact' => $obj->statuscontact,
1699 'rowid' => $obj->rowid,
1700 'code' => $obj->code,
1701 'libelle' => $label_type,
1702 'status' => (int) $obj->statuslink,
1703 'fk_c_type_contact' => $obj->fk_c_type_contact
1704 );
1705 } else {
1706 $tab[$i] = $obj->id;
1707 }
1708
1709 $i++;
1710 }
1711
1712 return $tab;
1713 } else {
1714 $this->error = $this->db->lasterror();
1715 dol_print_error($this->db);
1716 return -1;
1717 }
1718 }
1719
1720
1727 public function swapContactStatus($rowid)
1728 {
1729 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1730 $sql .= " tc.code, tc.libelle as type_label";
1731 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1732 $sql .= " WHERE ec.rowid =".((int) $rowid);
1733 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1734 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1735
1736 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1737 $resql = $this->db->query($sql);
1738 if ($resql) {
1739 $obj = $this->db->fetch_object($resql);
1740 $newstatut = ($obj->statut == 4) ? 5 : 4;
1741 $result = $this->update_contact($rowid, $newstatut);
1742 $this->db->free($resql);
1743 return $result;
1744 } else {
1745 $this->error = $this->db->error();
1746 dol_print_error($this->db);
1747 return -1;
1748 }
1749 }
1750
1751 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1762 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1763 {
1764 // phpcs:enable
1765 global $langs;
1766
1767 $sanitized_order = $order; // @phan-suppress-current-line SqlInjection
1768
1769 if (empty($sanitized_order)) {
1770 $sanitized_order = 'position';
1771 }
1772 if ($sanitized_order == 'position') {
1773 $sanitized_order .= ',code';
1774 }
1775
1776 $tab = array();
1777
1778 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1779 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1780 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1781 if ($activeonly == 1) {
1782 $sql .= " AND tc.active = 1"; // only the active types
1783 }
1784 if (!empty($source) && $source != 'all') {
1785 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1786 }
1787 if (!empty($code)) {
1788 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1789 }
1790 $sql .= $this->db->order($sanitized_order, 'ASC');
1791
1792 //print "sql=".$sql;
1793 $resql = $this->db->query($sql);
1794 if (!$resql) {
1795 $this->error = $this->db->lasterror();
1796 //dol_print_error($this->db);
1797 return null;
1798 }
1799
1800 $num = $this->db->num_rows($resql);
1801 $i = 0;
1802 while ($i < $num) {
1803 $obj = $this->db->fetch_object($resql);
1804
1805 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1806 $label_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1807 if (empty($option)) {
1808 $tab[$obj->rowid] = $label_type;
1809 } elseif ($option == 1) {
1810 $tab[$obj->code] = $label_type;
1811 } else {
1812 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $label_type);
1813 }
1814 $i++;
1815 }
1816
1817 return $tab;
1818 }
1819
1831 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1832 {
1833 global $langs;
1834
1835 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1836
1837 $tab = array();
1838
1839 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1840 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1841
1842 $sqlWhere = array();
1843 if (!empty($element)) {
1844 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1845 }
1846 if (!empty($excludeelement)) {
1847 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1848 }
1849
1850 if ($activeonly == 1) {
1851 $sqlWhere[] = " tc.active=1"; // only the active types
1852 }
1853
1854 if (!empty($source) && $source != 'all') {
1855 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1856 }
1857
1858 if (!empty($code)) {
1859 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1860 }
1861
1862 if (count($sqlWhere) > 0) {
1863 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1864 }
1865
1866 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1867
1868 dol_syslog(__METHOD__, LOG_DEBUG);
1869 $resql = $this->db->query($sql);
1870 if ($resql) {
1871 $num = $this->db->num_rows($resql);
1872 if ($num > 0) {
1873 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1874
1875 while ($obj = $this->db->fetch_object($resql)) {
1876 $modulename = $obj->module ?? $obj->element;
1877 if (strpos($obj->element, 'project') !== false) {
1878 $modulename = 'projet';
1879 } elseif ($obj->element == 'contrat') {
1880 $element = 'contract';
1881 } elseif ($obj->element == 'action') {
1882 $modulename = 'agenda';
1883 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1884 $modulename = 'fournisseur';
1885 }
1886 if (isModEnabled($modulename)) {
1887 $label_element = $langs->trans('ContactDefault_'.$obj->element);
1888 $tmpelement = $obj->element;
1889 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1890 $label_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1891 $tab[$obj->rowid] = $label_element.' - '.$label_type;
1892 }
1893 }
1894 }
1895 return $tab;
1896 } else {
1897 $this->error = $this->db->lasterror();
1898 return null;
1899 }
1900 }
1901
1913 public function getIdContact($source, $code, $status = 0)
1914 {
1915 $result = array();
1916 $i = 0;
1917 // Particular case for shipping
1918 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1919 $id = $this->origin_id;
1920 $element = 'commande';
1921 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1922 $id = $this->origin_id;
1923 $element = 'order_supplier';
1924 } else {
1925 $id = $this->id;
1926 $element = $this->element;
1927 }
1928
1929 $sql = "SELECT ec.fk_socpeople";
1930 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1931 if ($source == 'internal') {
1932 $sql .= " ".$this->db->prefix()."user as c,";
1933 }
1934 if ($source == 'external') {
1935 $sql .= " ".$this->db->prefix()."socpeople as c,";
1936 }
1937 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1938 $sql .= " WHERE ec.element_id = ".((int) $id);
1939 $sql .= " AND ec.fk_socpeople = c.rowid";
1940 if ($source == 'internal') {
1941 $sql .= " AND c.entity IN (".getEntity('user').")";
1942 }
1943 if ($source == 'external') {
1944 $sql .= " AND c.entity IN (".getEntity('societe').")";
1945 }
1946 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1947 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1948 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1949 if ($code) {
1950 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1951 }
1952 $sql .= " AND tc.active = 1";
1953 if ($status) {
1954 $sql .= " AND ec.statut = ".((int) $status);
1955 }
1956
1957 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1958 $resql = $this->db->query($sql);
1959 if ($resql) {
1960 while ($obj = $this->db->fetch_object($resql)) {
1961 $result[$i] = $obj->fk_socpeople;
1962 $i++;
1963 }
1964 } else {
1965 $this->error = $this->db->error();
1966 return null;
1967 }
1968
1969 return $result;
1970 }
1971
1972 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1979 public function fetch_contact($contactid = null)
1980 {
1981 // phpcs:enable
1982 if (empty($contactid)) {
1983 $contactid = $this->contact_id;
1984 }
1985
1986 if (empty($contactid)) {
1987 return 0;
1988 }
1989
1990 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1991 $contact = new Contact($this->db);
1992 $result = $contact->fetch($contactid);
1993 $this->contact = $contact;
1994 return $result;
1995 }
1996
1997 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2005 public function fetch_thirdparty($force_thirdparty_id = 0)
2006 {
2007 // phpcs:enable
2008 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
2009 return 0;
2010 }
2011
2012 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
2013
2014 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2015 if ($force_thirdparty_id) {
2016 $idtofetch = $force_thirdparty_id;
2017 }
2018
2019 if ($idtofetch) {
2020 $thirdparty = new Societe($this->db);
2021 $result = $thirdparty->fetch($idtofetch);
2022 if ($result < 0) {
2023 $this->errors = array_merge($this->errors, $thirdparty->errors);
2024 }
2025 $this->thirdparty = $thirdparty;
2026
2027 // Use first price level if level not defined for third party
2028 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
2029 $this->thirdparty->price_level = 1;
2030 }
2031
2032 return $result;
2033 } else {
2034 return -1;
2035 }
2036 }
2037
2038
2046 public function fetchOneLike($ref)
2047 {
2048 if (!$this->table_ref_field) {
2049 return 0;
2050 }
2051
2052 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
2053 $sql .= " WHERE ".$this->db->sanitize($this->table_ref_field)." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
2054 $sql .= " LIMIT 1";
2055
2056 $query = $this->db->query($sql);
2057
2058 if (!$this->db->num_rows($query)) {
2059 return 0;
2060 }
2061
2062 $result = $this->db->fetch_object($query);
2063
2064 if (method_exists($this, 'fetch')) {
2065 return $this->fetch($result->rowid);
2066 } else {
2067 $this->error = 'Fetch method not implemented on '.get_class($this);
2068 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2069 array_push($this->errors, $this->error);
2070 return -1;
2071 }
2072 }
2073
2074 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2083 public function fetch_barcode()
2084 {
2085 // phpcs:enable
2086 return $this->fetchBarCode();
2087 }
2088
2096 public function fetchBarCode()
2097 {
2098 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2099
2100 $idtype = $this->barcode_type;
2101 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
2102 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2103 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2104 } elseif ($this->element == 'societe') {
2105 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2106 } else {
2107 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2108 }
2109 }
2110
2111 if ($idtype > 0) {
2112 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
2113 $sql = "SELECT rowid, code, libelle as label, coder";
2114 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2115 $sql .= " WHERE rowid = ".((int) $idtype);
2116
2117 $resql = $this->db->query($sql);
2118 if ($resql) {
2119 $obj = $this->db->fetch_object($resql);
2120
2121 $this->barcode_type = $obj->rowid;
2122 $this->barcode_type_code = $obj->code;
2123 $this->barcode_type_label = $obj->label;
2124 $this->barcode_type_coder = $obj->coder;
2125 return 1;
2126 } else {
2127 dol_print_error($this->db);
2128 return -1;
2129 }
2130 }
2131 }
2132 return 0;
2133 }
2134
2140 public function fetchProject()
2141 {
2142 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2143
2144 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2145 $this->fk_project = $this->fk_projet; // For backward compatibility
2146 }
2147 if (empty($this->fk_project)) {
2148 return 0;
2149 }
2150
2151 $project = new Project($this->db);
2152 $result = $project->fetch($this->fk_project);
2153
2154 $this->project = $project;
2155
2156 return $result;
2157 }
2158
2159 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2167 public function fetch_project()
2168 {
2169 // phpcs:enable
2170 return $this->fetchProject();
2171 }
2172
2173 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2181 public function fetch_projet()
2182 {
2183 // phpcs:enable
2184 return $this->fetchProject();
2185 }
2186
2187 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2193 public function fetch_product()
2194 {
2195 // phpcs:enable
2196 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2197
2198 // @phan-suppress-next-line PhanUndeclaredProperty
2199 if (empty($this->fk_product)) {
2200 return 0;
2201 }
2202
2203 $product = new Product($this->db);
2204 // @phan-suppress-next-line PhanUndeclaredProperty
2205 $result = $product->fetch($this->fk_product);
2206
2207 $this->product = $product;
2208 return $result;
2209 }
2210
2217 public function fetchWarehouse($force_warehouse_id = 0)
2218 {
2219 // testing through empty: @phan-suppress-next-line PhanUndeclaredProperty
2220 if (empty($this->warehouse_id) && empty($this->fk_warehouse) && empty($force_warehouse_id)) {
2221 return 0;
2222 }
2223
2224 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
2225
2226 // testing through isset: @phan-suppress-next-line PhanUndeclaredProperty
2227 $idtofetch = isset($this->warehouse_id) ? $this->warehouse_id : (isset($this->fk_warehouse) ? $this->fk_warehouse : 0);
2228 if (!empty($force_warehouse_id)) {
2229 $idtofetch = $force_warehouse_id;
2230 }
2231
2232 if ($idtofetch) {
2233 $warehouse = new Entrepot($this->db);
2234 $result = $warehouse->fetch($idtofetch);
2235 if ($result < 0) {
2236 $this->errors = array_merge($this->errors, $warehouse->errors);
2237 }
2238 $this->warehouse = $warehouse;
2239
2240 return $result;
2241 } else {
2242 return -1;
2243 }
2244 }
2245
2246 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2253 public function fetch_user($userid)
2254 {
2255 // phpcs:enable
2256 $user = new User($this->db);
2257 $result = $user->fetch($userid);
2258 $this->user = $user;
2259 return $result;
2260 }
2261
2262 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2269 public function fetch_origin()
2270 {
2271 // phpcs:enable
2272 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2273
2274 // Manage classes with non standard name
2275 if ($tmpclassname == 'shipping') {
2276 $tmpclassname = 'Expedition';
2277 }
2278 if ($tmpclassname == 'delivery') {
2279 $tmpclassname = 'Livraison';
2280 }
2281 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2282 $tmpclassname = 'CommandeFournisseur';
2283 }
2284
2285 $classname = ucfirst($tmpclassname);
2286
2287 $this->origin_object = new $classname($this->db);
2288 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2289 $this->origin_object->fetch($this->origin_id);
2290 }
2291
2301 public function fetchObjectFrom($table, $field, $key, $element = null)
2302 {
2303 global $conf;
2304
2305 $result = false;
2306
2307 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2308 $sql .= " WHERE ".$this->db->sanitize($field)." = '".$this->db->escape($key)."'";
2309 if (!empty($element)) {
2310 $sql .= " AND entity IN (".getEntity($element).")";
2311 } else {
2312 $sql .= " AND entity = ".((int) $conf->entity);
2313 }
2314
2315 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2316 $resql = $this->db->query($sql);
2317 if ($resql) {
2318 $obj = $this->db->fetch_object($resql);
2319 // Test for avoid error -1
2320 if ($obj) {
2321 if (method_exists($this, 'fetch')) {
2322 $result = $this->fetch($obj->rowid);
2323 } else {
2324 $this->error = 'fetch() method not implemented on '.get_class($this);
2325 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2326 array_push($this->errors, $this->error);
2327 $result = -1;
2328 }
2329 }
2330 }
2331
2332 return $result;
2333 }
2334
2343 public function getValueFrom($table, $id, $field)
2344 {
2345 $result = false;
2346 if (!empty($id) && !empty($field) && !empty($table)) {
2347 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2348 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2349 return -1;
2350 }
2351 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2352 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2353 return -1;
2354 }
2355
2356 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2357 $sql .= " WHERE rowid = ".((int) $id);
2358
2359 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2360 $resql = $this->db->query($sql);
2361 if ($resql) {
2362 $row = $this->db->fetch_row($resql);
2363 $result = $row[0];
2364 }
2365 }
2366 return $result;
2367 }
2368
2385 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2386 {
2387 global $user;
2388
2389 if (empty($table)) {
2390 $table = $this->table_element;
2391 }
2392 if (empty($id)) {
2393 $id = $this->id;
2394 }
2395 if (empty($format)) {
2396 $format = 'text';
2397 }
2398 if (empty($id_field)) {
2399 $id_field = 'rowid';
2400 }
2401
2402 $propfield = $field;
2403
2404 // Special case
2405 if ($table == 'product') {
2406 if ($field == 'note_private') {
2407 $field = 'note';
2408 $propfield = 'note_private';
2409 } elseif ($field == 'status') {
2410 $field = 'tosell';
2411 $propfield = 'status';
2412 } elseif ($field == 'status_buy') {
2413 $field = 'tobuy';
2414 $propfield = 'status_buy';
2415 } elseif ($field == 'status_batch') {
2416 $field = 'tobatch';
2417 $propfield = 'status_batch';
2418 }
2419 }
2420
2421 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2422 $fk_user_field = 'fk_user_mod';
2423 }
2424 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2425 $fk_user_field = '';
2426 }
2427
2428 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2429 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2430 return -1;
2431 }
2432 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2433 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2434 return -1;
2435 }
2436
2437 $oldvalue = null;
2438 if ($trigkey) {
2439 $sql = "SELECT " . $this->db->sanitize($field);
2440 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2441 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2442
2443 $resql = $this->db->query($sql);
2444 if ($resql) {
2445 if ($obj = $this->db->fetch_object($resql)) {
2446 if ($format == 'date') {
2447 $oldvalue = $this->db->jdate($obj->$field);
2448 } else {
2449 $oldvalue = $obj->$field;
2450 }
2451 }
2452 } else {
2453 $this->error = $this->db->lasterror();
2454 return -1;
2455 }
2456 }
2457
2458 $error = 0;
2459
2460 dol_syslog(__METHOD__, LOG_DEBUG);
2461
2462 $this->db->begin();
2463
2464 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2465
2466 if ($format == 'text') {
2467 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2468 } elseif ($format == 'int') {
2469 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2470 } elseif ($format == 'date') {
2471 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2472 } elseif ($format == 'dategmt') {
2473 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2474 }
2475
2476 if ($fk_user_field) {
2477 if (!empty($fuser) && is_object($fuser)) {
2478 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2479 } elseif (empty($fuser) || $fuser != 'none') {
2480 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2481 }
2482 }
2483
2484 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2485
2486 $resql = $this->db->query($sql);
2487 if ($resql) {
2488 if ($trigkey) {
2489 // call trigger with updated object values
2490 if (method_exists($this, 'fetch')) {
2491 $result = $this->fetch($id);
2492 } else {
2493 $result = $this->fetchCommon($id);
2494 }
2495
2496 $this->oldcopy = clone $this;
2497 if (property_exists($this->oldcopy, $field)) {
2498 $this->oldcopy->$field = $oldvalue;
2499 }
2500 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2501 $this->oldcopy->$propfield = $oldvalue;
2502 }
2503
2504 if (empty($this->context['actionmsgmore'])) {
2505 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2506 }
2507
2508 if ($result >= 0) {
2509 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2510 }
2511 if ($result < 0) {
2512 $error++;
2513 }
2514 } else {
2515 if (property_exists($this, $field)) {
2516 $this->$field = $value;
2517 }
2518 if ($propfield != $field && property_exists($this, $propfield)) {
2519 $this->$propfield = $value;
2520 }
2521 }
2522
2523 if (!$error) {
2524 $this->db->commit();
2525 return 1;
2526 } else {
2527 if (property_exists($this, $field)) {
2528 $this->$field = $oldvalue;
2529 }
2530 if ($propfield != $field && property_exists($this, $propfield)) {
2531 $this->$propfield = $oldvalue;
2532 }
2533 $this->db->rollback();
2534 return -2;
2535 }
2536 } else {
2537 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2538 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2539 } else {
2540 $this->error = $this->db->lasterror();
2541 }
2542 $this->db->rollback();
2543 return -1;
2544 }
2545 }
2546
2547 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2558 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2559 {
2560 // phpcs:enable
2561 global $conf, $user;
2562
2563 if (!$this->table_element) {
2564 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2565 return -1;
2566 }
2567 if ($fieldid == 'none') {
2568 return 1;
2569 }
2570
2571 // For backward compatibility
2572 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2573 $fieldid = 'titre';
2574 }
2575
2576 // Security on socid
2577 $socid = 0;
2578 if ($user->socid > 0) {
2579 $socid = $user->socid;
2580 }
2581
2582 // this->ismultientitymanaged contains
2583 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2584 $aliastablesociete = 's';
2585 if ($this->element == 'societe') {
2586 $aliastablesociete = 'te'; // te as table_element
2587 }
2588 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2589 $sql = "SELECT MAX(te.".$this->db->sanitize($fieldid).")";
2590 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2591 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2592 $tmparray = explode('@', $this->ismultientitymanaged);
2593 $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
2594 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2595 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2596 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2597 $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
2598 }
2599 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2600 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2601 }
2602 if ($fieldid == 'rowid') {
2603 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < ".((int) $this->id);
2604 } elseif ($fieldid == 'label') {
2605 // Suppose caller is not in error @phan-suppress-next-line PhanUndeclaredProperty
2606 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < '".$this->db->escape((string) $this->label)."'";
2607 } else { // Should be 'ref' or any other string field
2608 $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)
2609 }
2610 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2611 $sql .= " AND sc.fk_user = ".((int) $user->id);
2612 }
2613 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2614 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2615 }
2616
2617 $filtermax = $filter;
2618
2619 // Manage filter
2620 $errormessage = '';
2621 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2622 if ($errormessage) {
2623 if (!preg_match('/^\s*AND/i', $filtermax)) {
2624 $sql .= " AND ";
2625 }
2626 $sql .= $filtermax; // @phan-suppress-current-line SqlInjection
2627 } else {
2628 $sql .= $tmpsql;
2629 }
2630
2631 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2632 $tmparray = explode('@', $this->ismultientitymanaged);
2633 $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
2634 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2635 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2636 }
2637 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2638 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2639 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2640 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2641 } else {
2642 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2643 }
2644 } else {
2645 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2646 }
2647 }
2648 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2649 $tmparray = explode('@', $this->ismultientitymanaged);
2650 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2651 }
2652 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2653 $sql .= ' AND te.fk_soc = '.((int) $socid);
2654 }
2655 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2656 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2657 }
2658 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2659 $sql .= ' AND te.rowid = '.((int) $socid);
2660 }
2661 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2662
2663 $result = $this->db->query($sql);
2664 if (!$result) {
2665 $this->error = $this->db->lasterror();
2666 return -1;
2667 }
2668 $row = $this->db->fetch_row($result);
2669 $this->ref_previous = $row[0];
2670
2671 $sql = "SELECT MIN(te.".$this->db->sanitize($fieldid).")";
2672 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2673 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2674 $tmparray = explode('@', $this->ismultientitymanaged);
2675 $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
2676 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2677 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2678 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2679 $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
2680 }
2681 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2682 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2683 }
2684 if ($fieldid == 'rowid') {
2685 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > ".((int) $this->id);
2686 } elseif ($fieldid == 'label') {
2687 // Suppose caller is not in error @phan-suppress-next-line PhanUndeclaredProperty
2688 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > '".$this->db->escape((string) $this->label)."'";
2689 } else { // Should be 'ref' or any other string field
2690 $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)
2691 }
2692 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2693 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2694 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2695 $userschilds = $user->getAllChildIds();
2696 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2697 }
2698 $sql .= ')';
2699 }
2700 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2701 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2702 }
2703
2704 $filtermin = $filter;
2705
2706 // Manage filter
2707 $errormessage = '';
2708 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2709 if ($errormessage) {
2710 if (!preg_match('/^\s*AND/i', $filtermin)) {
2711 $sql .= " AND ";
2712 }
2713 $sql .= $filtermin; // @phan-suppress-current-line SqlInjection
2714
2715 $filtermin = '';
2716 } else {
2717 $sql .= $tmpsql;
2718 }
2719
2720 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2721 $tmparray = explode('@', $this->ismultientitymanaged);
2722 $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
2723 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2724 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2725 }
2726 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2727 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2728 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2729 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2730 } else {
2731 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2732 }
2733 } else {
2734 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2735 }
2736 }
2737 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2738 $tmparray = explode('@', $this->ismultientitymanaged);
2739 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2740 }
2741 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2742 $sql .= ' AND te.fk_soc = '.((int) $socid);
2743 }
2744 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2745 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2746 }
2747 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2748 $sql .= ' AND te.rowid = '.((int) $socid);
2749 }
2750 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2751 // 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
2752
2753 $result = $this->db->query($sql);
2754 if (!$result) {
2755 $this->error = $this->db->lasterror();
2756 return -2;
2757 }
2758 $row = $this->db->fetch_row($result);
2759 $this->ref_next = $row[0];
2760
2761 return 1;
2762 }
2763
2764
2772 public function getListContactId($source = 'external')
2773 {
2774 $contactAlreadySelected = array();
2775 $tab = $this->liste_contact(-1, $source);
2776 $num = count($tab);
2777 $i = 0;
2778 while ($i < $num) {
2779 if ($source == 'thirdparty') {
2780 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2781 } else {
2782 $contactAlreadySelected[$i] = $tab[$i]['id'];
2783 }
2784 $i++;
2785 }
2786 return $contactAlreadySelected;
2787 }
2788
2789
2797 public function setProject($projectid, $notrigger = 0)
2798 {
2799 global $user;
2800 $error = 0;
2801
2802 if (!$this->table_element) {
2803 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2804 return -1;
2805 }
2806
2807 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2808 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2809 if ($projectid) {
2810 $sql .= " SET fk_project = ".((int) $projectid);
2811 } else {
2812 $sql .= " SET fk_project = NULL";
2813 }
2814 $sql .= ' WHERE id = '.((int) $this->id);
2815 // @phan-suppress-next-line PhanTypeMismatchProperty
2816 } elseif (!empty($this->fields['fk_project'])) { // Common case
2817 if ($projectid) {
2818 $sql .= " SET fk_project = ".((int) $projectid);
2819 } else {
2820 $sql .= " SET fk_project = NULL";
2821 }
2822 $sql .= ' WHERE rowid = '.((int) $this->id);
2823 } else { // Special case for old architecture objects
2824 if ($projectid) {
2825 $sql .= ' SET fk_projet = '.((int) $projectid);
2826 } else {
2827 $sql .= ' SET fk_projet = NULL';
2828 }
2829 $sql .= " WHERE rowid = ".((int) $this->id);
2830 }
2831
2832 $this->db->begin();
2833
2834 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2835 if ($this->db->query($sql)) {
2836 $this->fk_project = ((int) $projectid);
2837 } else {
2838 dol_print_error($this->db);
2839 $error++;
2840 }
2841
2842 // Triggers
2843 if (!$error && !$notrigger) {
2844 // Call triggers
2845 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2846 if ($result < 0) {
2847 $error++;
2848 } //Do also here what you must do to rollback action if trigger fail
2849 // End call triggers
2850 }
2851
2852 // Commit or rollback
2853 if ($error) {
2854 $this->db->rollback();
2855 return -1;
2856 } else {
2857 $this->db->commit();
2858 return 1;
2859 }
2860 }
2861
2868 public function setPaymentMethods($id)
2869 {
2870 global $user;
2871
2872 $error = 0;
2873 $notrigger = 0;
2874
2875 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2876
2877 if ($this->status >= 0 || $this->element == 'societe') {
2878 $this->oldcopy = dol_clone($this, 2); // @phan-suppress-current-line PhanTypeMismatchProperty
2879
2880 // TODO uniformize field name
2881 $fieldname = 'fk_mode_reglement';
2882 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2883 if ($this->element == 'societe') {
2884 $fieldname = 'mode_reglement';
2885 $triggerName = 'COMPANY';
2886 }
2887 if (get_class($this) == 'Facture') {
2888 $triggerName = 'BILL';
2889 }
2890 if (get_class($this) == 'Fournisseur') {
2891 $fieldname = 'mode_reglement_supplier';
2892 $triggerName = 'BILL_SUPPLIER';
2893 }
2894 if (get_class($this) == 'Tva') {
2895 $fieldname = 'fk_typepayment';
2896 $triggerName = 'VAT';
2897 }
2898 if (get_class($this) == 'Salary') {
2899 $fieldname = 'fk_typepayment';
2900 }
2901
2902 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2903 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2904 $sql .= ' WHERE rowid = '.((int) $this->id);
2905
2906 if ($this->db->query($sql)) {
2907 $this->mode_reglement_id = $id;
2908 // for supplier
2909 if (get_class($this) == 'Fournisseur') {
2910 $this->mode_reglement_supplier_id = $id;
2911 }
2912
2913 // Triggers
2914 if (!$error && !$notrigger) {
2915 // Call triggers
2916 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2917 if ($result < 0) {
2918 $error++;
2919 }
2920 // End call triggers
2921 }
2922 return 1;
2923 } else {
2924 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2925 $this->error = $this->db->error();
2926 return -1;
2927 }
2928 } else {
2929 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2930 $this->error = 'Status of the object is incompatible '.$this->status;
2931 return -2;
2932 }
2933 }
2934
2941 public function setMulticurrencyCode($code)
2942 {
2943 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2944 if ($this->status >= 0 || $this->element == 'societe') {
2945 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2946 $sql .= " SET multicurrency_code = '".$this->db->escape($code)."'";
2947 $sql .= ' WHERE rowid='.((int) $this->id);
2948
2949 if ($this->db->query($sql)) {
2950 $this->multicurrency_code = $code;
2951
2952 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2953 if ($rate) {
2954 $this->setMulticurrencyRate($rate, 2);
2955 }
2956
2957 return 1;
2958 } else {
2959 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2960 $this->error = $this->db->error();
2961 return -1;
2962 }
2963 } else {
2964 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2965 $this->error = 'Status of the object is incompatible '.$this->status;
2966 return -2;
2967 }
2968 }
2969
2977 public function setMulticurrencyRate($rate, $mode = 1)
2978 {
2979 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2980 if ($this->status >= 0 || $this->element == 'societe') {
2981 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2982 $sql .= " SET multicurrency_tx = ".((float) $rate);
2983 $sql .= ' WHERE rowid='.((int) $this->id);
2984
2985 if ($this->db->query($sql)) {
2986 $this->multicurrency_tx = $rate;
2987
2988 // Update line price
2989 if (!empty($this->lines)) {
2990 foreach ($this->lines as &$line) {
2991 // Amounts in company currency will be recalculated
2992 if ($mode == 1) {
2993 $line->subprice = 0;
2994 }
2995
2996 // Amounts in foreign currency will be recalculated
2997 if ($mode == 2) {
2998 $line->multicurrency_subprice = 0;
2999 }
3000
3001 switch ($this->element) {
3002 case 'propal':
3005 '@phan-var-force Propal $this';
3006 '@phan-var-force PropaleLigne $line';
3007 $this->updateline(
3008 $line->id,
3009 $line->subprice,
3010 $line->qty,
3011 $line->remise_percent,
3012 $line->tva_tx,
3013 $line->localtax1_tx,
3014 $line->localtax2_tx,
3015 ($line->description ? $line->description : $line->desc),
3016 'HT',
3017 $line->info_bits,
3018 $line->special_code,
3019 $line->fk_parent_line,
3020 $line->skip_update_total,
3021 $line->fk_fournprice,
3022 $line->pa_ht,
3023 $line->label,
3024 $line->product_type,
3025 $line->date_start,
3026 $line->date_end,
3027 $line->array_options,
3028 $line->fk_unit,
3029 $line->multicurrency_subprice
3030 );
3031 break;
3032 case 'commande':
3035 '@phan-var-force Commande $this';
3036 '@phan-var-force OrderLine $line';
3037 $this->updateline(
3038 $line->id,
3039 ($line->description ? $line->description : $line->desc),
3040 $line->subprice,
3041 $line->qty,
3042 $line->remise_percent,
3043 $line->tva_tx,
3044 $line->localtax1_tx,
3045 $line->localtax2_tx,
3046 'HT',
3047 $line->info_bits,
3048 $line->date_start,
3049 $line->date_end,
3050 $line->product_type,
3051 $line->fk_parent_line,
3052 $line->skip_update_total,
3053 $line->fk_fournprice,
3054 $line->pa_ht,
3055 $line->label,
3056 $line->special_code,
3057 $line->array_options,
3058 $line->fk_unit,
3059 $line->multicurrency_subprice
3060 );
3061 break;
3062 case 'facture':
3065 '@phan-var-force Facture $this';
3066 '@phan-var-force FactureLigne $line';
3067 $this->updateline(
3068 $line->id,
3069 ($line->description ? $line->description : $line->desc),
3070 $line->subprice,
3071 $line->qty,
3072 $line->remise_percent,
3073 $line->date_start,
3074 $line->date_end,
3075 $line->tva_tx,
3076 $line->localtax1_tx,
3077 $line->localtax2_tx,
3078 'HT',
3079 $line->info_bits,
3080 $line->product_type,
3081 $line->fk_parent_line,
3082 $line->skip_update_total,
3083 $line->fk_fournprice,
3084 $line->pa_ht,
3085 $line->label,
3086 $line->special_code,
3087 $line->array_options,
3088 $line->situation_percent,
3089 $line->fk_unit,
3090 $line->multicurrency_subprice
3091 );
3092 break;
3093 case 'facturerec':
3096 '@phan-var-force FactureRec $this';
3097 '@phan-var-force FactureLigneRec $line';
3098 $this->updateline(
3099 $line->id,
3100 ($line->description ? $line->description : $line->desc),
3101 $line->subprice,
3102 $line->qty,
3103 $line->tva_tx,
3104 $line->localtax1_tx,
3105 $line->localtax2_tx,
3106 $line->fk_product,
3107 $line->remise_percent,
3108 'HT',
3109 $line->info_bits,
3110 0,
3111 0,
3112 $line->product_type,
3113 $line->rang,
3114 $line->special_code,
3115 $line->label,
3116 $line->fk_unit,
3117 $line->multicurrency_subprice,
3118 0,
3119 $line->date_start, // Ignore real issue with FactureLigneRec @phan-suppress-current-line PhanUndeclaredProperty
3120 $line->date_end, // Ignore real issue with FactureLigneRec @phan-suppress-current-line PhanUndeclaredProperty
3121 $line->fk_fournprice,
3122 $line->pa_ht,
3123 $line->fk_parent_line
3124 );
3125 break;
3126 case 'supplier_proposal':
3129 '@phan-var-force SupplierProposal $this';
3130 '@phan-var-force SupplierProposalLine $line';
3131 $this->updateline(
3132 $line->id,
3133 $line->subprice,
3134 $line->qty,
3135 $line->remise_percent,
3136 $line->tva_tx,
3137 $line->localtax1_tx,
3138 $line->localtax2_tx,
3139 ($line->description ? $line->description : $line->desc),
3140 'HT',
3141 $line->info_bits,
3142 $line->special_code,
3143 $line->fk_parent_line,
3144 $line->skip_update_total,
3145 $line->fk_fournprice,
3146 $line->pa_ht,
3147 $line->label,
3148 $line->product_type,
3149 $line->array_options,
3150 $line->ref_fourn,
3151 (int) $line->fk_unit,
3152 $line->multicurrency_subprice
3153 );
3154 break;
3155 case 'order_supplier':
3158 '@phan-var-force CommandeFournisseur $this';
3159 '@phan-var-force CommandeFournisseurLigne $line';
3160 $this->updateline(
3161 $line->id,
3162 ($line->description ? $line->description : $line->desc),
3163 $line->subprice,
3164 $line->qty,
3165 $line->remise_percent,
3166 $line->tva_tx,
3167 $line->localtax1_tx,
3168 $line->localtax2_tx,
3169 'HT',
3170 $line->info_bits,
3171 $line->product_type,
3172 0,
3173 $line->date_start,
3174 $line->date_end,
3175 $line->array_options,
3176 $line->fk_unit,
3177 $line->multicurrency_subprice,
3178 $line->ref_supplier
3179 );
3180 break;
3181 case 'invoice_supplier':
3184 '@phan-var-force FactureFournisseur $this';
3185 '@phan-var-force SupplierInvoiceLIne $line';
3186 $this->updateline(
3187 $line->id,
3188 ($line->description ? $line->description : $line->desc),
3189 $line->subprice,
3190 $line->tva_tx,
3191 $line->localtax1_tx,
3192 $line->localtax2_tx,
3193 $line->qty,
3194 0,
3195 'HT',
3196 $line->info_bits,
3197 $line->product_type,
3198 $line->remise_percent,
3199 0,
3200 $line->date_start,
3201 $line->date_end,
3202 $line->array_options,
3203 $line->fk_unit,
3204 $line->multicurrency_subprice,
3205 $line->ref_supplier
3206 );
3207 break;
3208 default:
3209 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3210 break;
3211 }
3212 }
3213 }
3214
3215 return 1;
3216 } else {
3217 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3218 $this->error = $this->db->error();
3219 return -1;
3220 }
3221 } else {
3222 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3223 $this->error = 'Status of the object is incompatible '.$this->status;
3224 return -2;
3225 }
3226 }
3227
3235 public function setPaymentTerms($id, $deposit_percent = null)
3236 {
3237 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.formatLogObject($deposit_percent).')');
3238 if ($this->status >= 0 || $this->element == 'societe') {
3239 // TODO uniformize field name
3240 $fieldname = 'fk_cond_reglement';
3241 if ($this->element == 'societe') {
3242 $fieldname = 'cond_reglement';
3243 }
3244 if (get_class($this) == 'Fournisseur') {
3245 $fieldname = 'cond_reglement_supplier';
3246 }
3247
3248 if (empty($deposit_percent) || $deposit_percent < 0) {
3249 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3250 }
3251
3252 if ($deposit_percent > 100) {
3253 $deposit_percent = 100;
3254 }
3255
3256 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3257 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3258 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3259 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3260 }
3261 $sql .= ' WHERE rowid = '.((int) $this->id);
3262
3263 if ($this->db->query($sql)) {
3264 $this->cond_reglement_id = $id;
3265 // for supplier
3266 if (get_class($this) == 'Fournisseur') {
3267 $this->cond_reglement_supplier_id = $id;
3268 }
3269 $this->deposit_percent = $deposit_percent;
3270 return 1;
3271 } else {
3272 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3273 $this->error = $this->db->error();
3274 return -1;
3275 }
3276 } else {
3277 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3278 $this->error = 'Status of the object is incompatible '.$this->status;
3279 return -2;
3280 }
3281 }
3282
3289 public function setTransportMode($id)
3290 {
3291 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3292 if ($this->status >= 0 || $this->element == 'societe') {
3293 $fieldname = 'fk_transport_mode';
3294 if ($this->element == 'societe') {
3295 $fieldname = 'transport_mode';
3296 }
3297 if (get_class($this) == 'Fournisseur') {
3298 $fieldname = 'transport_mode_supplier';
3299 }
3300
3301 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3302 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3303 $sql .= ' WHERE rowid='.((int) $this->id);
3304
3305 if ($this->db->query($sql)) {
3306 $this->transport_mode_id = $id;
3307 // for supplier
3308 if (get_class($this) == 'Fournisseur') {
3309 $this->transport_mode_supplier_id = $id;
3310 }
3311 return 1;
3312 } else {
3313 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3314 $this->error = $this->db->error();
3315 return -1;
3316 }
3317 } else {
3318 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3319 $this->error = 'Status of the object is incompatible '.$this->status;
3320 return -2;
3321 }
3322 }
3323
3331 public function setDeliveryAddress($id)
3332 {
3333 $fieldname = 'fk_delivery_address';
3334 if ($this->element == 'delivery' || $this->element == 'shipping') {
3335 $fieldname = 'fk_address';
3336 }
3337
3338 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$this->db->sanitize($fieldname)." = ".((int) $id);
3339 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3340
3341 if ($this->db->query($sql)) {
3342 $this->fk_delivery_address = $id;
3343 return 1;
3344 } else {
3345 $this->error = $this->db->error();
3346 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3347 return -1;
3348 }
3349 }
3350
3351
3360 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3361 {
3362 global $user;
3363
3364 if (empty($userused)) {
3365 $userused = $user;
3366 }
3367
3368 $error = 0;
3369
3370 if (!$this->table_element) {
3371 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3372 return -1;
3373 }
3374
3375 $this->db->begin();
3376
3377 if ($shipping_method_id < 0) {
3378 $shipping_method_id = 'NULL';
3379 }
3380 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3381
3382 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3383 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3384 $sql .= " WHERE rowid=".((int) $this->id);
3385 $resql = $this->db->query($sql);
3386 if (!$resql) {
3387 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3388 $this->error = $this->db->lasterror();
3389 $error++;
3390 } else {
3391 if (!$notrigger) {
3392 // Call trigger
3393 $this->context = array('shippingmethodupdate' => 1);
3394 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3395 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3396 if ($result < 0) {
3397 $error++;
3398 }
3399 // End call trigger
3400 }
3401 }
3402 if ($error) {
3403 $this->db->rollback();
3404 return -1;
3405 } else {
3406 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3407 $this->db->commit();
3408 return 1;
3409 }
3410 }
3411
3412
3419 public function setWarehouse($warehouse_id)
3420 {
3421 if (!$this->table_element) {
3422 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3423 return -1;
3424 }
3425 if ($warehouse_id < 0) {
3426 $warehouse_id = 'NULL';
3427 }
3428 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3429
3430 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3431 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3432 $sql .= " WHERE rowid=".((int) $this->id);
3433
3434 if ($this->db->query($sql)) {
3435 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3436 return 1;
3437 } else {
3438 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3439 $this->error = $this->db->error();
3440 return 0;
3441 }
3442 }
3443
3444
3452 public function setDocModel($user, $modelpdf)
3453 {
3454 if (!$this->table_element) {
3455 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3456 return -1;
3457 }
3458
3459 $newmodelpdf = dol_trunc($modelpdf, 255);
3460
3461 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3462 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3463 $sql .= " WHERE rowid = ".((int) $this->id);
3464
3465 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3466 $resql = $this->db->query($sql);
3467 if ($resql) {
3468 $this->model_pdf = $modelpdf;
3469 return 1;
3470 } else {
3471 dol_print_error($this->db);
3472 return 0;
3473 }
3474 }
3475
3476
3485 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3486 {
3487 global $user;
3488
3489 if (empty($userused)) {
3490 $userused = $user;
3491 }
3492
3493 $error = 0;
3494
3495 if (!$this->table_element) {
3496 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3497 return -1;
3498 }
3499 $this->db->begin();
3500
3501 if ($fk_account < 0) {
3502 $fk_account = 'NULL';
3503 }
3504 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3505
3506 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3507 $sql .= " SET fk_account = ".((int) $fk_account);
3508 $sql .= " WHERE rowid=".((int) $this->id);
3509
3510 $resql = $this->db->query($sql);
3511 if (!$resql) {
3512 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3513 $this->error = $this->db->lasterror();
3514 $error++;
3515 } else {
3516 if (!$notrigger) {
3517 // Call trigger
3518 $this->context['bankaccountupdate'] = 1;
3519
3520 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3521 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3522 if ($result < 0) {
3523 $error++;
3524 }
3525 // End call trigger
3526 }
3527 }
3528 if ($error) {
3529 $this->db->rollback();
3530 return -1;
3531 } else {
3532 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3533 $this->db->commit();
3534 return 1;
3535 }
3536 }
3537
3538
3539 // TODO: Move line related operations to CommonObjectLine?
3540
3541 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3551 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3552 {
3553 // phpcs:enable
3554 if (!$this->table_element_line) {
3555 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3556 return -1;
3557 }
3558 if (!$this->fk_element) {
3559 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3560 return -1;
3561 }
3562
3563 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3564 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3565 $fieldposition = 'position';
3566 }
3567
3568 // Count number of lines to reorder (according to choice $renum)
3569 $nl = 0;
3570 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3571 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3572 if (!$renum) {
3573 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = 0";
3574 }
3575 if ($renum) {
3576 $sql .= " AND " . $this->db->sanitize($fieldposition) . " <> 0";
3577 }
3578
3579 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3580 $resql = $this->db->query($sql);
3581 if ($resql) {
3582 $row = $this->db->fetch_row($resql);
3583 $nl = $row[0];
3584 } else {
3585 dol_print_error($this->db);
3586 }
3587 if ($nl > 0) {
3588 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3589 $rows = array();
3590
3591 // We first search all lines that are parent lines (for multilevel details lines)
3592 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3593 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3594 if ($fk_parent_line) {
3595 $sql .= ' AND fk_parent_line IS NULL';
3596 }
3597 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC, rowid " . $this->db->sanitize($rowidorder);
3598
3599 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3600 $resql = $this->db->query($sql);
3601 if ($resql) {
3602 $i = 0;
3603 $num = $this->db->num_rows($resql);
3604 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3605 while ($i < $num) {
3606 $row = $this->db->fetch_row($resql);
3607 $rows[] = $row[0]; // Add parent line into array rows
3608 if ($fk_parent_line) {
3609 $children = $this->getChildrenOfLine($row[0], $grandchild);
3610 }
3611 if (!empty($children)) {
3612 foreach ($children as $child) {
3613 array_push($rows, $child);
3614 }
3615 }
3616 $i++;
3617 }
3618
3619 // Now we set a new number for each lines (parent and children with children included into parent tree)
3620 if (!empty($rows)) {
3621 foreach ($rows as $key => $row) {
3622 $this->updateRangOfLine($row, ($key + 1));
3623 }
3624 }
3625 } else {
3626 dol_print_error($this->db);
3627 }
3628 }
3629 return 1;
3630 }
3631
3639 public function getChildrenOfLine($id, $includealltree = 0)
3640 {
3641 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3642 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3643 $fieldposition = 'position';
3644 }
3645
3646 $rows = array();
3647
3648 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3649 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3650 $sql .= ' AND fk_parent_line = '.((int) $id);
3651 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC";
3652
3653 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3654
3655 $resql = $this->db->query($sql);
3656 if (!$resql || $this->db->num_rows($resql) <= 0) {
3657 return array();
3658 }
3659
3660 while ($row = $this->db->fetch_row($resql)) {
3661 $rows[] = $row[0];
3662 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3663 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3664 }
3665 }
3666 return $rows;
3667 }
3668
3669 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3677 public function line_up($rowid, $fk_parent_line = true)
3678 {
3679 // phpcs:enable
3680 $this->line_order(false, 'ASC', $fk_parent_line);
3681
3682 // Get rang of line
3683 $rang = $this->getRangOfLine($rowid);
3684
3685 // Update position of line
3686 $this->updateLineUp($rowid, $rang);
3687 }
3688
3689 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3697 public function line_down($rowid, $fk_parent_line = true)
3698 {
3699 // phpcs:enable
3700 $this->line_order(false, 'ASC', $fk_parent_line);
3701
3702 // Get rang of line
3703 $rang = $this->getRangOfLine($rowid);
3704
3705 // Get max value for rang
3706 $max = $this->line_max();
3707
3708 // Update position of line
3709 $this->updateLineDown($rowid, $rang, $max);
3710 }
3711
3719 public function updateRangOfLine($rowid, $rang)
3720 {
3721 global $hookmanager;
3722 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3723 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3724 $fieldposition = 'position';
3725 }
3726
3727 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3728 $sql .= ' WHERE rowid = '.((int) $rowid);
3729
3730 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3731 if (!$this->db->query($sql)) {
3732 dol_print_error($this->db);
3733 return -1;
3734 }
3735
3736 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3737 $action = '';
3738 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3739 return 1;
3740 }
3741
3742 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3749 public function line_ajaxorder($rows)
3750 {
3751 // phpcs:enable
3752 $num = count($rows);
3753 for ($i = 0; $i < $num; $i++) {
3754 $this->updateRangOfLine($rows[$i], ($i + 1));
3755 }
3756 }
3757
3765 public function updateLineUp($rowid, $rang)
3766 {
3767 if ($rang <= 1) {
3768 return -1;
3769 }
3770
3771 $fieldposition = 'rang';
3772 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3773 $fieldposition = 'position';
3774 }
3775
3776 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3777 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3778 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang - 1));
3779 if (!$this->db->query($sql)) {
3780 $this->error = $this->db->lasterror();
3781 return -1;
3782 }
3783
3784 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang - 1));
3785 $sql .= ' WHERE rowid = '.((int) $rowid);
3786 if (!$this->db->query($sql)) {
3787 $this->error = $this->db->lasterror();
3788 return -1;
3789 }
3790
3791 return 1;
3792 }
3793
3802 public function updateLineDown($rowid, $rang, $max)
3803 {
3804 if ($rang >= $max) {
3805 return -1;
3806 }
3807
3808 $fieldposition = 'rang';
3809 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3810 $fieldposition = 'position';
3811 }
3812
3813 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3814 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3815 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang + 1));
3816 if (!$this->db->query($sql)) {
3817 $this->error = $this->db->lasterror();
3818 return -1;
3819 }
3820
3821 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang + 1));
3822 $sql .= ' WHERE rowid = '.((int) $rowid);
3823 if (!$this->db->query($sql)) {
3824 $this->error = $this->db->lasterror();
3825 return -1;
3826 }
3827
3828 return 1;
3829 }
3830
3837 public function getRangOfLine($rowid)
3838 {
3839 $fieldposition = 'rang';
3840 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3841 $fieldposition = 'position';
3842 }
3843
3844 $sql = "SELECT " . $this->db->sanitize($fieldposition) . " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3845 $sql .= " WHERE rowid = ".((int) $rowid);
3846
3847 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3848 $resql = $this->db->query($sql);
3849 if (!$resql) {
3850 return 0;
3851 }
3852
3853 $row = $this->db->fetch_row($resql);
3854 return $row[0];
3855 }
3856
3863 public function getIdOfLine($rang)
3864 {
3865 $fieldposition = 'rang';
3866 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3867 $fieldposition = 'position';
3868 }
3869
3870 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3871 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3872 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = ".((int) $rang);
3873 $resql = $this->db->query($sql);
3874 if (!$resql) {
3875 return 0;
3876 }
3877
3878 $row = $this->db->fetch_row($resql);
3879 return $row[0];
3880 }
3881
3882 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3889 public function line_max($fk_parent_line = 0)
3890 {
3891 // phpcs:enable
3892 $positionfield = 'rang';
3893 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3894 $positionfield = 'position';
3895 }
3896
3897 $sql = "SELECT max(".$this->db->sanitize($positionfield).") FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3898 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3899
3900 // Search the last rang with fk_parent_line
3901 if ($fk_parent_line) {
3902 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3903 }
3904
3905 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3906 $resql = $this->db->query($sql);
3907 if ($resql) {
3908 $row = $this->db->fetch_row($resql);
3909
3910 if ($fk_parent_line) {
3911 if (!empty($row[0])) {
3912 return $row[0];
3913 } else {
3914 return $this->getRangOfLine($fk_parent_line);
3915 }
3916 } else {
3917 return $row[0];
3918 }
3919 }
3920
3921 return 0;
3922 }
3923
3924 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3931 public function update_ref_ext($ref_ext)
3932 {
3933 // phpcs:enable
3934 if (!$this->table_element) {
3935 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3936 return -1;
3937 }
3938
3939 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3940 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3941 // @phan-suppress-next-line PhanUndeclaredProperty
3942 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3943
3944 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3945 if ($this->db->query($sql)) {
3946 $this->ref_ext = $ref_ext;
3947 return 1;
3948 } else {
3949 $this->error = $this->db->error();
3950 return -1;
3951 }
3952 }
3953
3954 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3963 public function update_note($note, $suffix = '', $notrigger = 0)
3964 {
3965 // phpcs:enable
3966 global $user;
3967
3968 if (!$this->table_element) {
3969 $this->error = 'update_note was called on object with property table_element not defined';
3970 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3971 return -1;
3972 }
3973 if (!in_array($suffix, array('', '_public', '_private'))) {
3974 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3975 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3976 return -2;
3977 }
3978
3979 $newsuffix = $suffix;
3980
3981 // Special case
3982 if ($this->table_element == 'product' && $newsuffix == '_private') {
3983 $newsuffix = '';
3984 }
3985 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3986 $fieldusermod = "fk_user_mod";
3987 } elseif ($this->table_element == 'ecm_files') {
3988 $fieldusermod = "fk_user_m";
3989 } else {
3990 $fieldusermod = "fk_user_modif";
3991 }
3992 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3993 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3994 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3995 $sql .= " WHERE rowid = ".((int) $this->id);
3996
3997 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3998 if ($this->db->query($sql)) {
3999 if ($suffix == '_public') {
4000 $this->note_public = $note;
4001 } elseif ($suffix == '_private') {
4002 $this->note_private = $note;
4003 } else {
4004 $this->note = $note; // deprecated
4005 $this->note_private = $note;
4006 }
4007 if (empty($notrigger)) {
4008 // TODO Use the $this->TRIGGER_PREFIX when implemented
4009 switch ($this->element) {
4010 case 'societe':
4011 $triggerName = 'COMPANY_MODIFY';
4012 break;
4013 case 'facture':
4014 $triggerName = 'BILL_MODIFY';
4015 break;
4016 case 'invoice_supplier':
4017 $triggerName = 'BILL_SUPPLIER_MODIFY';
4018 break;
4019 case 'facturerec':
4020 $triggerName = 'BILLREC_MODIFIY';
4021 break;
4022 case 'expensereport':
4023 $triggerName = 'EXPENSE_REPORT_MODIFY';
4024 break;
4025 default:
4026 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
4027 }
4028 $ret = $this->call_trigger($triggerName, $user);
4029 if ($ret < 0) {
4030 return -1;
4031 }
4032 }
4033 return 1;
4034 } else {
4035 $this->error = $this->db->lasterror();
4036 return -1;
4037 }
4038 }
4039
4040 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4049 public function update_note_public($note)
4050 {
4051 // phpcs:enable
4052 return $this->update_note($note, '_public');
4053 }
4054
4055 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4066 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4067 {
4068 // phpcs:enable
4069 global $conf, $hookmanager, $action;
4070
4071 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4072 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4073 if ($reshook > 0) {
4074 return 1; // replacement code
4075 } elseif ($reshook < 0) {
4076 return -1; // failure
4077 } // reshook = 0 => execute normal code
4078
4079 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4080 $isElementForSupplier = false;
4081 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4082 $MODULE = "";
4083 if ($this->element == 'propal') {
4084 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4085 } elseif ($this->element == 'commande' || $this->element == 'order') {
4086 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4087 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4088 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4089 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4090 $isElementForSupplier = true;
4091 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4092 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4093 $isElementForSupplier = true;
4094 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4095 } elseif ($this->element == 'supplier_proposal') {
4096 $isElementForSupplier = true;
4097 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4098 }
4099 if ($isElementForSupplier) {
4100 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4101 }
4102
4103 if (!empty($MODULE)) {
4104 if (getDolGlobalString($MODULE)) {
4105 $modsactivated = explode(',', getDolGlobalString($MODULE));
4106 foreach ($modsactivated as $mod) {
4107 if (isModEnabled($mod)) {
4108 return 1; // update was disabled by specific setup
4109 }
4110 }
4111 }
4112 }
4113
4114 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4115
4116 $forcedroundingmode = $roundingadjust;
4117 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4118 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4119 } elseif ($forcedroundingmode == 'auto') {
4120 $forcedroundingmode = '0';
4121 }
4122
4123 $error = 0;
4124
4125 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4126
4127 // Define constants to find lines to sum (field name into the table_element_line not into table_element)
4128 $fieldtva = 'total_tva';
4129 $fieldlocaltax1 = 'total_localtax1';
4130 $fieldlocaltax2 = 'total_localtax2';
4131 $fieldup = 'subprice';
4132 $base_price_type = 'HT';
4133 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4134 $fieldtva = 'tva';
4135 $fieldup = 'pu_ht';
4136 }
4137 if ($this->element == 'invoice_supplier_rec') {
4138 $fieldup = 'pu_ht';
4139 }
4140 if ($this->element == 'expensereport') {
4141 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4142 // 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
4143 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4144 $forcedroundingmode = '0';
4145 $fieldup = 'value_unit';
4146 $base_price_type = 'TTC';
4147 }
4148
4149 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4150 $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,";
4151 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4152 $sql .= ' info_bits, product_type,';
4153 if ($this->element == 'expensereport') {
4154 $sql .= ' comments as description';
4155 } else {
4156 $sql .= ' description';
4157 }
4158 if ($this->table_element_line == 'facturedet') {
4159 $sql .= ', situation_percent';
4160 }
4161 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4162 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
4163 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
4164 if ($exclspec) {
4165 $product_field = 'product_type';
4166 if ($this->table_element_line == 'contratdet') {
4167 $product_field = ''; // contratdet table has no product_type field
4168 }
4169 if ($product_field) {
4170 $sql .= " AND ".$this->db->sanitize($product_field)." <> 9";
4171 }
4172 }
4173 $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
4174
4175 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4176
4177 $resql = $this->db->query($sql);
4178 if ($resql) {
4179 $this->total_ht = 0;
4180 $this->total_tva = 0;
4181 $this->total_localtax1 = 0;
4182 $this->total_localtax2 = 0;
4183 $this->total_ttc = 0;
4184 $total_ht_by_vats = array();
4185 $total_tva_by_vats = array();
4186 $total_ttc_by_vats = array();
4187 $this->multicurrency_total_ht = 0;
4188 $this->multicurrency_total_tva = 0;
4189 $this->multicurrency_total_ttc = 0;
4190
4191 $this->db->begin();
4192
4193 $num = $this->db->num_rows($resql);
4194 $i = 0;
4195 while ($i < $num) {
4196 $obj = $this->db->fetch_object($resql);
4197
4198 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4199 $parameters = array('fk_element' => $obj->rowid);
4200 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4201
4202 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'
4203 // This part of code is to fix data. We should not call it too often.
4204 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4205 $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);
4206
4207 $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.
4208 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4209 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4210 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4211
4212 if ($diff_on_current_total) {
4213 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4214 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4215 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4216 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4217 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4218 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);
4219 $resqlfix = $this->db->query($sqlfix);
4220 if (!$resqlfix) {
4221 dol_print_error($this->db, 'Failed to update line');
4222 }
4223 $obj->total_tva = $tmpcal[1];
4224 $obj->total_ttc = $tmpcal[2];
4225 $obj->multicurrency_total_tva = $tmpcal[17];
4226 $obj->multicurrency_total_ttc = $tmpcal[18];
4227 } elseif ($diff_when_using_price_ht) {
4228 // 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
4229 // 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),
4230 // so we continue only if price HT are same.
4231 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4232 // 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
4233 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4234 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4235 // 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,
4236 // 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.
4237 // 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.
4238 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4239 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4240 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4241 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4242
4243 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);
4244
4245 $resqlfix = $this->db->query($sqlfix);
4246 if (!$resqlfix) {
4247 dol_print_error($this->db, 'Failed to update line');
4248 }
4249 $obj->total_tva = $tmpcal[1];
4250 $obj->total_ttc = $tmpcal[2];
4251 $obj->multicurrency_total_tva = $tmpcal[17];
4252 $obj->multicurrency_total_ttc = $tmpcal[18];
4253 }
4254 }
4255 }
4256 }
4257
4258 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4259 $this->total_tva += $obj->total_tva;
4260 $this->total_localtax1 += $obj->total_localtax1;
4261 $this->total_localtax2 += $obj->total_localtax2;
4262 $this->total_ttc += $obj->total_ttc;
4263 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4264 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4265 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4266
4267 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4268 $total_ht_by_vats[$obj->vatrate] = 0;
4269 }
4270 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4271 $total_tva_by_vats[$obj->vatrate] = 0;
4272 }
4273 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4274 $total_ttc_by_vats[$obj->vatrate] = 0;
4275 }
4276 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4277 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4278 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4279
4280 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4281 if ($base_price_type == 'TTC') {
4282 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4283 } else {
4284 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4285 }
4286 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4287 //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";
4288 if ($diff) {
4289 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4290 if (abs((float) $diff) > $maxdiff) {
4291 // If error is more than 10 times the accuracy of rounding. This should not happen.
4292 $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.';
4293 dol_syslog($errmsg, LOG_WARNING);
4294 $this->error = $errmsg;
4295 $error++;
4296 break;
4297 }
4298
4299 if ($base_price_type == 'TTC') {
4300 $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);
4301 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);
4302 } else {
4303 $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);
4304 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);
4305 }
4306
4307 $resqlfix = $this->db->query($sqlfix);
4308
4309 if (!$resqlfix) {
4310 dol_print_error($this->db, 'Failed to update line');
4311 }
4312
4313 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4314 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4315 if ($base_price_type == 'TTC') {
4316 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4317 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4318 } else {
4319 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4320 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4321 }
4322 }
4323 }
4324
4325 $i++;
4326 }
4327
4328 // Add revenue stamp to total
4329 // @phan-suppress-next-line PhanUndeclaredProperty
4330 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4331 // @phan-suppress-next-line PhanUndeclaredProperty
4332 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4333
4334 // Situations totals
4335 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
4336 '@phan-var-force Facture $this';
4337 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
4338 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4339 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4340 $prev_sits = $this->get_prev_sits();
4341
4342 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4343 $this->total_ht -= $sit->total_ht;
4344 $this->total_tva -= $sit->total_tva;
4345 $this->total_localtax1 -= $sit->total_localtax1;
4346 $this->total_localtax2 -= $sit->total_localtax2;
4347 $this->total_ttc -= $sit->total_ttc;
4348 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4349 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4350 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4351 }
4352 }
4353 }
4354 }
4355
4356 // Clean total
4357 $this->total_ht = (float) price2num($this->total_ht);
4358 $this->total_tva = (float) price2num($this->total_tva);
4359 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4360 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4361 $this->total_ttc = (float) price2num($this->total_ttc);
4362
4363 $this->db->free($resql);
4364
4365 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4366 $fieldht = 'total_ht';
4367 $fieldtva = 'tva';
4368 $fieldlocaltax1 = 'localtax1';
4369 $fieldlocaltax2 = 'localtax2';
4370 $fieldttc = 'total_ttc';
4371 // Specific code for backward compatibility with old field names
4372 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4373 $fieldtva = 'total_tva';
4374 }
4375
4376 if (!$error && empty($nodatabaseupdate)) {
4377 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4378 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4379 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4380 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4381 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4382 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4383 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4384 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4385 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4386 $sql .= " WHERE rowid = ".((int) $this->id);
4387
4388 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4389 $resql = $this->db->query($sql);
4390
4391 if (!$resql) {
4392 $error++;
4393 $this->error = $this->db->lasterror();
4394 $this->errors[] = $this->db->lasterror();
4395 }
4396 }
4397
4398 if (!$error) {
4399 $this->db->commit();
4400 return 1;
4401 } else {
4402 $this->db->rollback();
4403 return -1;
4404 }
4405 } else {
4406 dol_print_error($this->db, 'Bad request in update_price');
4407 return -1;
4408 }
4409 }
4410
4411 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4422 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4423 {
4424 // phpcs:enable
4425 global $user, $hookmanager, $action;
4426 dol_syslog(__METHOD__, LOG_DEBUG);
4427
4428 if (empty($this->origin_type) && !empty($this->origin)) {
4429 $this->origin_type = $this->origin;
4430 }
4431 $origin = (!empty($origin) ? $origin : $this->origin_type);
4432 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4433 $f_user = isset($f_user) ? $f_user : $user;
4434
4435 // Special case
4436 if ($origin == 'order') {
4437 $origin = 'commande';
4438 }
4439 if ($origin == 'invoice') {
4440 $origin = 'facture';
4441 }
4442 if ($origin == 'invoice_template') {
4443 $origin = 'facturerec';
4444 }
4445 if ($origin == 'supplierorder') {
4446 $origin = 'order_supplier';
4447 }
4448
4449 // Add module part to target type
4450 $targettype = $this->getElementType();
4451
4452 $parameters = array('targettype' => $targettype);
4453 // Hook for explicitly set the targettype if it must be different than $this->element
4454 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4455 if ($reshook > 0) {
4456 if (!empty($hookmanager->resArray['targettype'])) {
4457 $targettype = $hookmanager->resArray['targettype'];
4458 }
4459 }
4460
4461 $this->db->begin();
4462 $error = 0;
4463
4464 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4465 $sql .= "fk_source";
4466 $sql .= ", sourcetype";
4467 $sql .= ", fk_target";
4468 $sql .= ", targettype";
4469 $sql .= ") VALUES (";
4470 $sql .= ((int) $origin_id);
4471 $sql .= ", '" . $this->db->escape($origin) . "'";
4472 $sql .= ", " . ((int) $this->id);
4473 $sql .= ", '" . $this->db->escape($targettype) . "'";
4474 $sql .= ")";
4475
4476 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4477 if ($this->db->query($sql)) {
4478 // If we link a supplier order to a supplier invoice that already uses discounts coming from other supplier invoices
4479 // (deposit invoice / credit note used as payment), also link those source invoices to the same supplier order.
4480 // This helps keep "linked elements" consistent when totals are impacted by such discounts.
4481 if ($origin === 'order_supplier' && $targettype === 'invoice_supplier' && $this->element === 'invoice_supplier' && (int) $origin_id > 0 && (int) $this->id > 0) {
4482 $sourceinvoices = array();
4483
4484 $sqlsources = "SELECT DISTINCT rc.fk_invoice_supplier_source as rowid";
4485 $sqlsources .= " FROM ".$this->db->prefix()."societe_remise_except as rc";
4486 $sqlsources .= " WHERE rc.fk_invoice_supplier = ".((int) $this->id);
4487 $sqlsources .= " AND rc.fk_invoice_supplier_source IS NOT NULL";
4488
4489 $resqlsources = $this->db->query($sqlsources);
4490 if ($resqlsources) {
4491 while ($objsrc = $this->db->fetch_object($resqlsources)) {
4492 $srcid = (int) $objsrc->rowid;
4493 if ($srcid > 0 && $srcid !== (int) $this->id) {
4494 $sourceinvoices[$srcid] = $srcid;
4495 }
4496 }
4497 } else {
4498 dol_syslog(get_class($this)."::add_object_linked Failed to read supplier invoice discounts sources", LOG_WARNING);
4499 }
4500
4501 if (!empty($sourceinvoices)) {
4502 $existing = array();
4503 $sourcelist = implode(',', $sourceinvoices);
4504
4505 $sqlexists = "SELECT fk_target";
4506 $sqlexists .= " FROM ".$this->db->prefix()."element_element";
4507 $sqlexists .= " WHERE fk_source = ".((int) $origin_id);
4508 $sqlexists .= " AND sourcetype = '".$this->db->escape($origin)."'";
4509 $sqlexists .= " AND targettype = '".$this->db->escape($targettype)."'";
4510 $sqlexists .= " AND fk_target IN (".$this->db->sanitize($sourcelist).")";
4511
4512 $resqlexists = $this->db->query($sqlexists);
4513 if ($resqlexists) {
4514 while ($objexist = $this->db->fetch_object($resqlexists)) {
4515 $existing[(int) $objexist->fk_target] = 1;
4516 }
4517 } else {
4518 dol_syslog(get_class($this)."::add_object_linked Failed to read existing linked supplier invoices", LOG_WARNING);
4519 }
4520
4521 foreach ($sourceinvoices as $srcid) {
4522 if (!empty($existing[(int) $srcid])) {
4523 continue;
4524 }
4525
4526 $sqladd = "INSERT INTO " . $this->db->prefix() . "element_element (fk_source, sourcetype, fk_target, targettype)";
4527 $sqladd .= " VALUES (".((int) $origin_id).", '".$this->db->escape($origin)."', ".((int) $srcid).", '".$this->db->escape($targettype)."')";
4528 $this->db->query($sqladd); // Best-effort: do not fail original link action
4529 }
4530 }
4531 }
4532
4533 if (!$notrigger) {
4534 // Call trigger
4535 $this->context['link_origin'] = $origin;
4536 $this->context['link_origin_id'] = $origin_id;
4537
4538 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4539 if ($result < 0) {
4540 $error++;
4541 }
4542 // End call triggers
4543 }
4544 } else {
4545 $this->error = $this->db->lasterror();
4546 $error++;
4547 }
4548
4549 if (!$error) {
4550 $this->db->commit();
4551 return 1;
4552 } else {
4553 $this->db->rollback();
4554 return 0;
4555 }
4556 }
4557
4563 public function getElementType()
4564 {
4565 // 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.
4566 // 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).
4567 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4568 // Add module part to target type if object has $module property and isn't in core modules.
4569 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4570 }
4571
4572
4595 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4596 {
4597 global $hookmanager, $action;
4598
4599 // Important for pdf generation time reduction
4600 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4601 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4602 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4603 return 1;
4604 }
4605
4606 $this->linkedObjectsIds = array();
4607 $this->linkedObjects = array();
4608
4609 // Hook for allowing modules to completely alter the behavior of the method
4610 $parameters = array(
4611 'sourceid' => $sourceid,
4612 'sourcetype' => $sourcetype,
4613 'targetid' => $targetid,
4614 'targettype' => $targettype,
4615 'clause' => $clause,
4616 'alsosametype' => $alsosametype,
4617 'orderby' => $orderby,
4618 'loadalsoobjects' => $loadalsoobjects
4619 );
4620 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4621 if ($reshook > 0) {
4622 return $reshook;
4623 }
4624
4625 $justsource = false;
4626 $justtarget = false;
4627 $withtargettype = false;
4628 $withsourcetype = false;
4629
4630 // Hook for explicitly set the targettype if it must be differtent than $this->element
4631 if (is_object($hookmanager)) {
4632 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4633 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4634 if ($reshook > 0) {
4635 if (!empty($hookmanager->resArray['sourcetype'])) {
4636 $sourcetype = $hookmanager->resArray['sourcetype'];
4637 }
4638 if (!empty($hookmanager->resArray['sourceid'])) {
4639 $sourceid = $hookmanager->resArray['sourceid'];
4640 }
4641 if (!empty($hookmanager->resArray['targettype'])) {
4642 $targettype = $hookmanager->resArray['targettype'];
4643 }
4644 if (!empty($hookmanager->resArray['targetid'])) {
4645 $targetid = $hookmanager->resArray['targetid'];
4646 }
4647 }
4648 }
4649
4650 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4651 $justsource = true; // the source (id and type) is a search criteria
4652 if (!empty($targettype)) {
4653 $withtargettype = true;
4654 }
4655 }
4656 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4657 $justtarget = true; // the target (id and type) is a search criteria
4658 if (!empty($sourcetype)) {
4659 $withsourcetype = true;
4660 }
4661 }
4662
4663 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4664 $targetid = (!empty($targetid) ? $targetid : $this->id);
4665 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4666 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4667
4668 /*if (empty($sourceid) && empty($targetid))
4669 {
4670 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4671 return -1;
4672 }*/
4673
4674 // Links between objects are stored in table element_element
4675 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4676 $sql .= " FROM ".$this->db->prefix()."element_element";
4677 $sql .= " WHERE ";
4678 if ($justsource || $justtarget) {
4679 if ($justsource) {
4680 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4681 if ($withtargettype) {
4682 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4683 }
4684 } elseif ($justtarget) {
4685 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4686 if ($withsourcetype) {
4687 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4688 }
4689 }
4690 } else {
4691 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4692 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4693 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4694 $this->linkedObjectsFullLoaded[$this->id] = true;
4695 }
4696 }
4697 $sql .= $this->db->order($orderby);
4698
4699 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4700
4701 $resql = $this->db->query($sql);
4702 if ($resql) {
4703 $num = $this->db->num_rows($resql);
4704 $i = 0;
4705 while ($i < $num) {
4706 $obj = $this->db->fetch_object($resql);
4707 if ($justsource || $justtarget) {
4708 if ($justsource) {
4709 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4710 } elseif ($justtarget) {
4711 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4712 }
4713 } else {
4714 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4715 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4716 }
4717 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4718 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4719 }
4720 }
4721 $i++;
4722 }
4723
4724 if (!empty($this->linkedObjectsIds)) {
4725 $tmparray = $this->linkedObjectsIds;
4726 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4727 $element_properties = getElementProperties($objecttype);
4728 $element = $element_properties['element'];
4729 $classPath = $element_properties['classpath'];
4730 $classFile = $element_properties['classfile'];
4731 $className = $element_properties['classname'];
4732 $module = $element_properties['module'];
4733
4734 // Here $module, $classFile and $className are set, we can use them.
4735 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4736 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4737 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4738 if (class_exists($className)) {
4739 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4740 $object = new $className($this->db);
4741 '@phan-var-force CommonObject $object';
4742 $ret = $object->fetch($objectid);
4743 if ($ret > 0) {
4744 $this->linkedObjects[$objecttype][$i] = $object;
4745 }
4746 }
4747 }
4748 }
4749 } else {
4750 unset($this->linkedObjectsIds[$objecttype]);
4751 }
4752 }
4753 }
4754 return 1;
4755 } else {
4756 dol_print_error($this->db);
4757 return -1;
4758 }
4759 }
4760
4767 public function clearObjectLinkedCache()
4768 {
4769 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4770 unset($this->linkedObjectsFullLoaded[$this->id]);
4771 }
4772
4773 return 1;
4774 }
4775
4788 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4789 {
4790 global $user;
4791 $updatesource = false;
4792 $updatetarget = false;
4793 $f_user = isset($f_user) ? $f_user : $user;
4794 dol_syslog(__METHOD__, LOG_DEBUG);
4795
4796 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4797 $updatesource = true;
4798 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4799 $updatetarget = true;
4800 }
4801
4802 $this->db->begin();
4803 $error = 0;
4804
4805 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4806 if ($updatesource) {
4807 $sql .= "fk_source = " . ((int) $sourceid);
4808 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4809 $sql .= " WHERE fk_target = " . ((int) $this->id);
4810 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4811 } elseif ($updatetarget) {
4812 $sql .= "fk_target = " . ((int) $targetid);
4813 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4814 $sql .= " WHERE fk_source = " . ((int) $this->id);
4815 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4816 }
4817
4818 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4819 if ($this->db->query($sql)) {
4820 if (!$notrigger) {
4821 // Call trigger
4822 $this->context['link_source_id'] = $sourceid;
4823 $this->context['link_source_type'] = $sourcetype;
4824 $this->context['link_target_id'] = $targetid;
4825 $this->context['link_target_type'] = $targettype;
4826
4827 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4828 if ($result < 0) {
4829 $error++;
4830 }
4831 // End call triggers
4832 }
4833 } else {
4834 $this->error = $this->db->lasterror();
4835 $error++;
4836 }
4837
4838 if (!$error) {
4839 $this->db->commit();
4840 return 1;
4841 } else {
4842 $this->db->rollback();
4843 return -1;
4844 }
4845 }
4846
4860 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4861 {
4862 global $user;
4863 $deletesource = false;
4864 $deletetarget = false;
4865 $f_user = isset($f_user) ? $f_user : $user;
4866
4867 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4868 $deletesource = true;
4869 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4870 $deletetarget = true;
4871 }
4872
4873 $element = $this->getElementType();
4874 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4875 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4876 $targetid = (!empty($targetid) ? $targetid : $this->id);
4877 $targettype = (!empty($targettype) ? $targettype : $element);
4878 $this->db->begin();
4879 $error = 0;
4880
4881 if (!$notrigger) {
4882 // Call trigger
4883 $this->context['link_id'] = $rowid;
4884 $this->context['link_source_id'] = $sourceid;
4885 $this->context['link_source_type'] = $sourcetype;
4886 $this->context['link_target_id'] = $targetid;
4887 $this->context['link_target_type'] = $targettype;
4888
4889 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4890 if ($result < 0) {
4891 $error++;
4892 }
4893 // End call triggers
4894 }
4895
4896 if (!$error) {
4897 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4898 $sql .= " WHERE";
4899 if ($rowid > 0) {
4900 $sql .= " rowid = " . ((int) $rowid);
4901 } else {
4902 if ($deletesource) {
4903 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4904 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4905 } elseif ($deletetarget) {
4906 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4907 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4908 } else {
4909 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4910 $sql .= " OR";
4911 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4912 }
4913 }
4914
4915 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4916 if (!$this->db->query($sql)) {
4917 $this->error = $this->db->lasterror();
4918 $this->errors[] = $this->error;
4919 $error++;
4920 }
4921 }
4922
4923 if (!$error) {
4924 $this->db->commit();
4925 return 1;
4926 } else {
4927 $this->db->rollback();
4928 return 0;
4929 }
4930 }
4931
4941 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4942 {
4943 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4944 return -1;
4945 }
4946 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4947 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4948 }
4949
4950 global $db;
4951
4952 $sql = "SELECT ".$db->sanitize($field_select)." FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4953 $resql = $db->query($sql);
4954
4955 $TRes = array();
4956 if (!empty($resql)) {
4957 while ($res = $db->fetch_object($resql)) {
4958 $TRes[] = $res->{$field_select};
4959 }
4960 }
4961
4962 return $TRes;
4963 }
4964
4973 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4974 {
4975 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4976 return -1;
4977 }
4978
4979 global $db;
4980
4981 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4982 $resql = $db->query($sql);
4983 $n = 0;
4984 if ($resql) {
4985 $res = $db->fetch_object($resql);
4986 if ($res) {
4987 $n = $res->nb;
4988 }
4989 }
4990
4991 return $n;
4992 }
4993
5002 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
5003 {
5004 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
5005 return -1;
5006 }
5007
5008 global $db;
5009
5010 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
5011 $resql = $db->query($sql);
5012
5013 if (empty($resql)) {
5014 return 0;
5015 }
5016
5017 return 1;
5018 }
5019
5031 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
5032 {
5033 global $user;
5034
5035 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
5036
5037 $elementId = (!empty($elementId) ? $elementId : $this->id);
5038 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
5039
5040 $this->db->begin();
5041
5042 if (empty($fieldstatus)) {
5043 if ($elementTable == 'facture_rec') {
5044 $fieldstatus = "suspended";
5045 }
5046 if ($elementTable == 'mailing') {
5047 $fieldstatus = "statut";
5048 }
5049 if ($elementTable == 'cronjob') {
5050 $fieldstatus = "status";
5051 }
5052 if ($elementTable == 'user') {
5053 $fieldstatus = "statut";
5054 }
5055 if ($elementTable == 'expensereport') {
5056 $fieldstatus = "fk_statut";
5057 }
5058 if ($elementTable == 'receptiondet_batch') {
5059 $fieldstatus = "status";
5060 }
5061 if ($elementTable == 'prelevement_bons') {
5062 $fieldstatus = "statut";
5063 }
5064 if ($elementTable == 'bank_account') {
5065 $fieldstatus = "clos";
5066 }
5067 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
5068 $fieldstatus = 'status';
5069 }
5070 // If still empty
5071 if (empty($fieldstatus)) {
5072 $fieldstatus = 'fk_statut';
5073 }
5074 }
5075
5076 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
5077 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
5078 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
5079 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
5080 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
5081 $sql .= ", fk_user_valid = ".((int) $user->id);
5082 }
5083 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
5084 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
5085 }
5086 if ($status == 1 && in_array($elementTable, array('inventory'))) {
5087 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
5088 }
5089 $sql .= " WHERE rowid = ".((int) $elementId);
5090 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
5091
5092 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
5093 $resql = $this->db->query($sql);
5094 if ($resql) {
5095 $error = 0;
5096
5097 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
5098
5099 if ($nb_rows_affected > 0) {
5100 if (empty($trigkey)) {
5101 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5102 if ($this->element == 'supplier_proposal' && $status == 2) {
5103 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5104 }
5105 if ($this->element == 'supplier_proposal' && $status == 3) {
5106 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5107 }
5108 if ($this->element == 'supplier_proposal' && $status == 4) {
5109 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5110 }
5111 if ($this->element == 'fichinter' && $status == 3) {
5112 $trigkey = 'FICHINTER_CLASSIFY_DONE';
5113 }
5114 if ($this->element == 'fichinter' && $status == 2) {
5115 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5116 }
5117 if ($this->element == 'fichinter' && $status == 1) {
5118 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5119 }
5120 }
5121
5122 $this->context = array_merge($this->context, array('newstatus' => $status));
5123
5124 if ($trigkey && $trigkey != 'none') {
5125 $this->oldcopy = dol_clone($this);
5126
5127 // Call trigger
5128 $result = $this->call_trigger($trigkey, $user);
5129 if ($result < 0) {
5130 $error++;
5131 }
5132 // End call triggers
5133 }
5134 } else {
5135 // The status was probably already good. We do nothing more, no triggers.
5136 }
5137
5138 if (!$error) {
5139 $this->db->commit();
5140
5141 if (empty($savElementId)) {
5142 // If the element we update is $this (so $elementId was provided as null)
5143 if ($fieldstatus == 'dispute_status') {
5144 $this->dispute_status = $status;
5145 } elseif ($fieldstatus == 'tosell') {
5146 $this->status = $status;
5147 } elseif ($fieldstatus == 'tobuy') {
5148 $this->status_buy = $status; // @phpstan-ignore-line
5149 } elseif ($fieldstatus == 'tobatch') {
5150 $this->status_batch = $status; // @phpstan-ignore-line
5151 } else {
5152 $this->status = $status;
5153 }
5154 }
5155
5156 return 1;
5157 } else {
5158 $this->db->rollback();
5159 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5160 return -1;
5161 }
5162 } else {
5163 $this->error = $this->db->lasterror();
5164 $this->db->rollback();
5165 return -1;
5166 }
5167 }
5168
5169
5177 public function getCanvas($id = 0, $ref = '')
5178 {
5179 if (empty($id) && empty($ref)) {
5180 return 0;
5181 }
5182 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5183 return 0; // To increase speed. Not enabled by default.
5184 }
5185
5186 // Clean parameters
5187 $ref = trim($ref);
5188
5189 $sql = "SELECT rowid, canvas";
5190 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5191 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5192 if (!empty($id)) {
5193 $sql .= " AND rowid = ".((int) $id);
5194 }
5195 if (!empty($ref)) {
5196 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5197 }
5198
5199 $resql = $this->db->query($sql);
5200 if ($resql) {
5201 $obj = $this->db->fetch_object($resql);
5202 if ($obj) {
5203 $this->canvas = $obj->canvas;
5204 return 1;
5205 } else {
5206 return 0;
5207 }
5208 } else {
5209 dol_print_error($this->db);
5210 return -1;
5211 }
5212 }
5213
5214
5221 public function getSpecialCode($lineid)
5222 {
5223 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
5224 $sql .= " WHERE rowid = ".((int) $lineid);
5225 $resql = $this->db->query($sql);
5226 if ($resql) {
5227 $row = $this->db->fetch_row($resql);
5228 return (!empty($row[0]) ? $row[0] : 0);
5229 }
5230
5231 return 0;
5232 }
5233
5242 public function isObjectUsed($id = 0, $entity = 0)
5243 {
5244 global $langs;
5245
5246 if (empty($id)) {
5247 $id = $this->id;
5248 }
5249
5250 // Check parameters
5251 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5252 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5253 return -1;
5254 }
5255
5256 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5257 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5258 $tmparray = array_keys($this->childtables);
5259 if (is_numeric($tmparray[0])) {
5260 $arraytoscan = array_flip($this->childtables);
5261 }
5262
5263 // Test on all child tables to scan if child exists
5264 $haschild = 0;
5265 foreach ($arraytoscan as $table => $element) {
5266 //print $id.'-'.$table.'-'.$elementname.'<br>';
5267
5268 // Check if module is enabled (to avoid error if tables of module not created)
5269 if (isset($element['enabled']) && !empty($element['enabled'])) {
5270 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5271 if (empty($enabled)) {
5272 continue;
5273 }
5274 }
5275
5276 // Check if element can be deleted
5277 $sql = "SELECT COUNT(*) as nb";
5278 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($table)." as c";
5279 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5280 $sql .= ", ".$this->db->prefix().$this->db->sanitize($element['parent'])." as p";
5281 }
5282 if (!empty($element['fk_element'])) {
5283 $sql .= " WHERE c.".$this->db->sanitize($element['fk_element'])." = ".((int) $id);
5284 } else {
5285 $sql .= " WHERE c.".$this->db->sanitize($this->fk_element)." = ".((int) $id);
5286 }
5287 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5288 $sql .= " AND c.".$this->db->sanitize($element['parentkey'])." = p.rowid";
5289 }
5290 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5291 $sql .= " AND c.".$this->db->sanitize($element['parenttypefield'])." = '".$this->db->escape($element['parenttypevalue'])."'";
5292 }
5293 if (!empty($entity)) {
5294 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5295 $sql .= " AND p.entity = ".((int) $entity);
5296 } else {
5297 $sql .= " AND c.entity = ".((int) $entity);
5298 }
5299 }
5300 //var_dump($table, $element, $sql);
5301
5302 $resql = $this->db->query($sql);
5303 if ($resql) {
5304 $obj = $this->db->fetch_object($resql);
5305 if ($obj->nb > 0) {
5306 $langs->load("errors");
5307 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5308 $haschild += (int) $obj->nb;
5309 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5310 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5311 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5312 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5313 } else { // new usage: $element['name']=Translation key
5314 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5315 }
5316 break; // We found at least one, we stop here
5317 }
5318 } else {
5319 $this->errors[] = $this->db->lasterror();
5320 return -1;
5321 }
5322 }
5323
5324 if ($haschild > 0) {
5325 $this->errors[] = "ErrorRecordHasChildren";
5326 return $haschild;
5327 } else {
5328 return 0;
5329 }
5330 }
5331
5338 public function hasProductsOrServices($predefined = -1)
5339 {
5340 $nb = 0;
5341
5342 foreach ($this->lines as $key => $val) {
5343 $qualified = 0;
5344 if ($predefined == -1) {
5345 $qualified = 1;
5346 }
5347 if ($predefined == 1 && $val->fk_product > 0) {
5348 $qualified = 1;
5349 }
5350 if ($predefined == 0 && $val->fk_product <= 0) {
5351 $qualified = 1;
5352 }
5353 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5354 $qualified = 1;
5355 }
5356 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5357 $qualified = 1;
5358 }
5359 if ($qualified) {
5360 $nb++;
5361 }
5362 }
5363 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5364 return $nb;
5365 }
5366
5372 public function getTotalDiscount()
5373 {
5374 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5375 $total_discount = 0.00;
5376
5377 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5378 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5379 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
5380
5381 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5382 $resql = $this->db->query($sql);
5383 if ($resql) {
5384 $num = $this->db->num_rows($resql);
5385 $i = 0;
5386 while ($i < $num) {
5387 $obj = $this->db->fetch_object($resql);
5388
5389 $pu_ht = $obj->pu_ht;
5390 $qty = $obj->qty;
5391 $total_ht = $obj->total_ht;
5392
5393 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5394 $total_discount += $total_discount_line;
5395
5396 $i++;
5397 }
5398 }
5399
5400 //print $total_discount; exit;
5401 return (float) price2num($total_discount);
5402 }
5403
5404 return null;
5405 }
5406
5407
5414 public function getTotalWeightVolume()
5415 {
5416 $totalWeight = 0;
5417 $totalVolume = 0;
5418 // defined for shipment only
5419 $totalOrdered = 0;
5420 // defined for shipment only
5421 $totalToShip = 0;
5422
5423 if (empty($this->lines)) {
5424 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5425 }
5426
5427 foreach ($this->lines as $line) {
5428 if (isset($line->qty_asked)) {
5429 $totalOrdered += $line->qty_asked; // defined for shipment only
5430 }
5431 if (isset($line->qty_shipped)) {
5432 $totalToShip += $line->qty_shipped; // defined for shipment only
5433 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5434 if (empty($totalToShip)) {
5435 $totalToShip = 0;
5436 }
5437 $totalToShip += $line->qty; // defined for reception only
5438 }
5439
5440 // Define qty, weight, volume, weight_units, volume_units
5441 if ($this->element == 'shipping') {
5442 // for shipments
5443 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5444 } else {
5445 $qty = $line->qty ? $line->qty : 0;
5446 }
5447
5448 $weight = !empty($line->weight) ? $line->weight : 0;
5449 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5450 $volume = !empty($line->volume) ? $line->volume : 0;
5451 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5452
5453 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5454 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5455 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5456 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5457
5458 $weightUnit = 0;
5459 $volumeUnit = 0;
5460 if (!empty($weight_units)) {
5461 $weightUnit = $weight_units;
5462 }
5463 if (!empty($volume_units)) {
5464 $volumeUnit = $volume_units;
5465 }
5466
5467 if (empty($totalWeight)) {
5468 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5469 }
5470 if (empty($totalVolume)) {
5471 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5472 }
5473
5474 //var_dump($line->volume_units);
5475 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5476 $trueWeightUnit = pow(10, $weightUnit);
5477 $totalWeight += $weight * $qty * $trueWeightUnit;
5478 } else {
5479 if ($weight_units == 99) {
5480 // conversion 1 Pound = 0.45359237 KG
5481 $trueWeightUnit = 0.45359237;
5482 $totalWeight += $weight * $qty * $trueWeightUnit;
5483 } elseif ($weight_units == 98) {
5484 // conversion 1 Ounce = 0.0283495 KG
5485 $trueWeightUnit = 0.0283495;
5486 $totalWeight += $weight * $qty * $trueWeightUnit;
5487 } else {
5488 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5489 }
5490 }
5491 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5492 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5493 $trueVolumeUnit = pow(10, $volumeUnit);
5494 //print $line->volume;
5495 $totalVolume += $volume * $qty * $trueVolumeUnit;
5496 } else {
5497 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5498 }
5499 }
5500
5501 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5502 }
5503
5504
5510 public function setExtraParameters()
5511 {
5512 $this->db->begin();
5513
5514 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5515 $extraparams = dol_trunc($extraparams, 250);
5516
5517 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5518 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5519 $sql .= " WHERE rowid = ".((int) $this->id);
5520
5521 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5522 $resql = $this->db->query($sql);
5523 if (!$resql) {
5524 $this->error = $this->db->lasterror();
5525 $this->db->rollback();
5526 return -1;
5527 } else {
5528 $this->db->commit();
5529 return 1;
5530 }
5531 }
5532
5533
5534 // --------------------
5535 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5536 // --------------------
5537
5538 /* This is to show add lines */
5539
5549 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5550 {
5551 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5552
5553 // Line extrafield
5554 if (!is_object($extrafields)) {
5555 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5556 $extrafields = new ExtraFields($this->db);
5557 }
5558 $extrafields->fetch_name_optionals_label($this->table_element_line);
5559
5560 // Output template part (modules that overwrite templates must declare this into descriptor)
5561 // Use global variables + $dateSelector + $seller and $buyer
5562 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5563 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5564 foreach ($dirtpls as $module => $reldir) {
5565 if (!empty($module)) {
5566 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5567 } else {
5568 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5569 }
5570
5571 if (empty($conf->file->strict_mode)) {
5572 $res = @include $tpl;
5573 } else {
5574 $res = include $tpl; // for debug
5575 }
5576 if ($res) {
5577 break;
5578 }
5579 }
5580 }
5581
5582
5583
5584 /* This is to show array of line of details */
5585
5586
5601 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5602 {
5603 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5604 // TODO We should not use global var for this
5605 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5606
5607 // Define $usemargins (used by objectline_xxx.tpl.php files)
5608 $usemargins = 0;
5609 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5610 $usemargins = 1;
5611 }
5612
5613 $num = count($this->lines);
5614
5615 // Line extrafield
5616 if (!is_object($extrafields)) {
5617 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5618 $extrafields = new ExtraFields($this->db);
5619 }
5620 $extrafields->fetch_name_optionals_label($this->table_element_line);
5621
5622 if (method_exists($this, 'loadExpeditions')) {
5623 // 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.
5624 $this->loadExpeditions();
5625 }
5626
5627 $parameters = array();
5628 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5629 if (empty($reshook)) {
5630 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5631 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5632 if (empty($reshook)) {
5633 // Output template part (modules that overwrite templates must declare this into descriptor)
5634 // Use global variables + $dateSelector + $seller and $buyer
5635 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5636 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5637 foreach ($dirtpls as $module => $reldir) {
5638 $res = 0;
5639 if (!empty($module)) {
5640 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5641 } else {
5642 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5643 }
5644 if (file_exists($tpl)) {
5645 if (empty($conf->file->strict_mode)) {
5646 $res = @include $tpl;
5647 } else {
5648 $res = include $tpl; // for debug
5649 }
5650 }
5651 if ($res) {
5652 break;
5653 }
5654 }
5655 }
5656
5657 $i = 0;
5658
5659 print "<!-- begin printObjectLines() -->\n";
5660 foreach ($this->lines as $line) {
5661 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5662 //$line->fetch_optionals();
5663
5664 if (is_object($hookmanager)) {
5665 if (empty($line->fk_parent_line)) {
5666 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5667 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5668 } else {
5669 $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);
5670 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5671 }
5672 }
5673 if (empty($reshook)) {
5674 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5675 }
5676
5677 $i++;
5678 }
5679 print "<!-- end printObjectLines() -->\n";
5680 }
5681 }
5682
5700 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5701 {
5702 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5703 global $form;
5704 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5705
5706 // var used into tpl
5707 $text = '';
5708 $description = '';
5709
5710 // Line in view mode
5711 if ($action != 'editline' || $selected != $line->id) {
5712 // Product
5713 if (!empty($line->fk_product) && $line->fk_product > 0) {
5714 $product_static = new Product($this->db);
5715 $product_static->fetch($line->fk_product);
5716
5717 $product_static->ref = (string) $line->ref; //can change ref in hook
5718 // label is checked with empty @phan-suppress-next-line PhanUndeclaredProperty
5719 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5720
5721 $text = $product_static->getNomUrl(1);
5722
5723 // Define output language and label
5724 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5725 // @phan-suppress-next-line PhanUndeclaredProperty
5726 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5727 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5728 return;
5729 }
5730
5731 $prod = new Product($this->db);
5732 $prod->fetch($line->fk_product);
5733
5734 $outputlangs = $langs;
5735 $newlang = '';
5736 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5737 $newlang = GETPOST('lang_id', 'aZ09');
5738 }
5739 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5740 $newlang = $this->thirdparty->default_lang; // To use language of customer
5741 }
5742 if (!empty($newlang)) {
5743 $outputlangs = new Translate("", $conf);
5744 $outputlangs->setDefaultLang($newlang);
5745 }
5746
5747 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5748 } else {
5749 $label = $line->product_label;
5750 }
5751
5752 // label is checked with empty @phan-suppress-next-line PhanUndeclaredProperty
5753 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5754 $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.
5755 }
5756
5757 // Recalculate unit price with tax if not defined
5758 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)
5759 // So we calculate an estimated value just to show something on screen.
5760 // Not: the unit price is always for 100% of line, it is not a prorata of situation invoice (when total is)
5761 if ($line->remise_percent != 100) {
5762 // Suppose situation_percent is defined @phan-suppress-next-line PhanUndeclaredProperty
5763 $line->subprice_ttc = (float) price2num($line->total_ttc * ($line->situation_percent ? 100 / $line->situation_percent : 1) / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5764 } else {
5765 // Other method is less accurate
5766 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5767 }
5768 }
5769 $line->pu_ttc = $line->subprice_ttc; // deprecated
5770
5771 // Output template part (modules that overwrite templates must declare this into descriptor)
5772 // Use global variables + $dateSelector + $seller and $buyer
5773 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5774
5775 $qty_shipped = 0;
5776 if (isset($this->expeditions[$line->id])) { // @phan-suppress-current-line PhanUndeclaredProperty
5777 // Suppose existence of expeditions is tested @phan-suppress-next-line PhanUndeclaredProperty
5778 $qty_shipped = $this->expeditions[$line->id];
5779 }
5780 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5781
5782 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5783 foreach ($dirtpls as $module => $reldir) {
5784 $res = 0;
5785 if (!empty($module)) {
5786 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5787 } else {
5788 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5789 }
5790 //var_dump($tpl);
5791 if (file_exists($tpl)) {
5792 if (empty($conf->file->strict_mode)) {
5793 $res = @include $tpl;
5794 } else {
5795 $res = include $tpl; // for debug
5796 }
5797 }
5798 if ($res) {
5799 break;
5800 }
5801 }
5802 }
5803
5804 // Line in update mode
5805 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5806 // label is tested @phan-suppress-next-line PhanUndeclaredProperty
5807 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5808
5809 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5810 $line->pu_ttc = $line->subprice_ttc; // deprecated
5811
5812 // Output template part (modules that overwrite templates must declare this into descriptor)
5813 // Use global variables + $dateSelector + $seller and $buyer
5814 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5815 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5816 foreach ($dirtpls as $module => $reldir) {
5817 if (!empty($module)) {
5818 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5819 } else {
5820 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5821 }
5822
5823 if (empty($conf->file->strict_mode)) {
5824 $res = @include $tpl;
5825 } else {
5826 $res = include $tpl; // for debug
5827 }
5828 if ($res) {
5829 break;
5830 }
5831 }
5832 }
5833 }
5834
5835
5836 /* This is to show array of line of details of source object */
5837
5838
5849 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5850 {
5851 global $langs, $hookmanager, $form, $action;
5852
5853 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5854 print '<tr class="liste_titre">';
5855 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5856 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5857 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5858 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5859 if (isModEnabled("multicurrency")) {
5860 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5861 }
5862 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5863 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5864 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5865 }
5866 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5867 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5868 print '<td class="center">';
5869 print $form->showCheckAddButtons('checkforselect', 1);
5870 print '</td>';
5871 print '</tr>';
5872 $i = 0;
5873
5874 if (!empty($this->lines)) {
5875 foreach ($this->lines as $line) {
5876 $reshook = 0;
5877 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5878 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5879 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5880 if (!empty($line->fk_parent_line)) {
5881 $parameters['fk_parent_line'] = $line->fk_parent_line;
5882 }
5883 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5884 }
5885 if (empty($reshook)) {
5886 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5887 }
5888
5889 $i++;
5890 }
5891 }
5892 }
5893
5907 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5908 {
5909 global $langs, $conf;
5910
5911 //var_dump($line);
5912 if (!empty($line->date_start)) {
5913 // @phan-suppress-next-line PhanUndeclaredProperty
5914 $date_start = $line->date_start;
5915 } else {
5916 $date_start = $line->date_debut_prevue;
5917 if ($line->date_debut_reel) {
5918 $date_start = $line->date_debut_reel;
5919 }
5920 }
5921 if (!empty($line->date_end)) {
5922 // @phan-suppress-next-line PhanUndeclaredProperty
5923 $date_end = $line->date_end;
5924 } else {
5925 $date_end = $line->date_fin_prevue;
5926 if ($line->date_fin_reel) {
5927 $date_end = $line->date_fin_reel;
5928 }
5929 }
5930
5931 // Set thevalue into ->tpl[] array.
5932 $this->tpl['id'] = $line->id;
5933
5934 $this->tpl['label'] = '';
5935 if (!empty($line->fk_parent_line)) {
5936 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5937 }
5938
5939 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5940 $discount = new DiscountAbsolute($this->db);
5941 if (property_exists($this, 'socid')) {
5942 // Tested if socid exists @phan-suppress-next-line PhanUndeclaredProperty
5943 $discount->fk_soc = $this->socid;
5944 // Tested if socid exists @phan-suppress-next-line PhanUndeclaredProperty
5945 $discount->socid = $this->socid;
5946 }
5947 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5948 } elseif (!empty($line->fk_product)) {
5949 $productstatic = new Product($this->db);
5950 $productstatic->id = $line->fk_product;
5951 $productstatic->ref = (string) $line->ref;
5952 $productstatic->type = $line->fk_product_type;
5953 if (empty($productstatic->ref)) {
5954 $line->fetch_product();
5955 $productstatic = $line->product;
5956 }
5957
5958 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5959 // Existence of $line->label is tested @phan-suppress-next-line PhanUndeclaredProperty
5960 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5961 // Dates
5962 if ($line->product_type == 1 && ($date_start || $date_end)) {
5963 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5964 }
5965 } else {
5966 $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"')));
5967 if (!empty($line->desc)) {
5968 $this->tpl['label'] .= $line->desc;
5969 } else {
5970 // Supposes existence of $line->label @phan-suppress-next-line PhanUndeclaredProperty
5971 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5972 }
5973
5974 // Dates
5975 if ($line->product_type == 1 && ($date_start || $date_end)) {
5976 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5977 }
5978 }
5979
5980 if (!empty($line->desc)) {
5981 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5982 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5983 $discount = new DiscountAbsolute($this->db);
5984 $discount->fetch($line->fk_remise_except);
5985 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5986 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5987 $discount = new DiscountAbsolute($this->db);
5988 $discount->fetch($line->fk_remise_except);
5989 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5990 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5991 $discount = new DiscountAbsolute($this->db);
5992 $discount->fetch($line->fk_remise_except);
5993 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5994 } elseif ($line->desc == '(EXCESS PAID)') {
5995 $discount = new DiscountAbsolute($this->db);
5996 $discount->fetch($line->fk_remise_except);
5997 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5998 } else {
5999 $this->tpl['description'] = dol_trunc($line->desc, 60);
6000 }
6001 } else {
6002 $this->tpl['description'] = '&nbsp;';
6003 }
6004
6005 // VAT Rate
6006 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
6007 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
6008 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
6009 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
6010 }
6011
6012 $this->tpl['price'] = price($line->subprice);
6013 $this->tpl['total_ht'] = price($line->total_ht);
6014 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
6015 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
6016 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
6017 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
6018 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
6019 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
6020 }
6021 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
6022
6023 // Is the line strike or not
6024 $this->tpl['strike'] = 0;
6025 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
6026 $this->tpl['strike'] = 1;
6027 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
6028 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
6029 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
6030 // Without the guard, printing origin lines from a PO into a vendor
6031 // invoice fatals (#37663). Other tpl files already follow this pattern.
6032 $this->tpl['strike'] = 1;
6033 }
6034
6035 // Output template part (modules that overwrite templates must declare this into descriptor)
6036 // Use global variables + $dateSelector + $seller and $buyer
6037 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
6038 foreach ($dirtpls as $module => $reldir) {
6039 if (!empty($module)) {
6040 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
6041 } else {
6042 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
6043 }
6044
6045 if (empty($conf->file->strict_mode)) {
6046 $res = @include $tpl;
6047 } else {
6048 $res = include $tpl; // for debug
6049 }
6050 if ($res) {
6051 break;
6052 }
6053 }
6054 }
6055
6056
6057 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6069 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
6070 {
6071 // phpcs:enable
6072 global $user;
6073 $this->db->begin();
6074
6075 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
6076 $sql .= "resource_id";
6077 $sql .= ", resource_type";
6078 $sql .= ", element_id";
6079 $sql .= ", element_type";
6080 $sql .= ", busy";
6081 $sql .= ", mandatory";
6082 $sql .= ") VALUES (";
6083 $sql .= ((int) $resource_id);
6084 $sql .= ", '".$this->db->escape($resource_type)."'";
6085 $sql .= ", '".$this->db->escape((string) $this->id)."'";
6086 $sql .= ", '".$this->db->escape($this->element)."'";
6087 $sql .= ", '".$this->db->escape((string) $busy)."'";
6088 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
6089 $sql .= ")";
6090
6091 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
6092 if ($this->db->query($sql)) {
6093 if (!$notrigger) {
6094 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
6095 if ($result < 0) {
6096 $this->db->rollback();
6097 return -1;
6098 }
6099 }
6100 $this->db->commit();
6101 return 1;
6102 } else {
6103 $this->error = $this->db->lasterror();
6104 $this->db->rollback();
6105 return 0;
6106 }
6107 }
6108
6109 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6118 public function delete_resource($rowid, $element = '', $notrigger = 0)
6119 {
6120 // phpcs:enable
6121 global $user;
6122
6123 $this->db->begin();
6124
6125 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6126 $sql .= " WHERE rowid = ".((int) $rowid);
6127
6128 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6129
6130 $resql = $this->db->query($sql);
6131 if (!$resql) {
6132 $this->error = $this->db->lasterror();
6133 $this->db->rollback();
6134 return -1;
6135 } else {
6136 if (!$notrigger) {
6137 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6138 if ($result < 0) {
6139 $this->db->rollback();
6140 return -1;
6141 }
6142 }
6143 $this->db->commit();
6144 return 1;
6145 }
6146 }
6147
6148
6154 public function __clone()
6155 {
6156 // Force a copy of this->lines, otherwise it will point to same object.
6157 if (isset($this->lines) && is_array($this->lines)) {
6158 $nboflines = count($this->lines);
6159 for ($i = 0; $i < $nboflines; $i++) {
6160 if (is_object($this->lines[$i])) {
6161 $this->lines[$i] = clone $this->lines[$i];
6162 }
6163 }
6164 }
6165 }
6166
6180 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6181 {
6182 global $conf, $langs, $hookmanager, $action;
6183
6184 $srctemplatepath = '';
6185
6186 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6187 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6188
6189 if (!empty($reshook)) {
6190 return $reshook;
6191 }
6192
6193 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6194
6195 if (empty($modele)) {
6196 $this->error = 'BadValueForParameterModele';
6197 return -1;
6198 }
6199
6200 // Increase limit for PDF build
6201 $err = error_reporting();
6202 error_reporting(0);
6203 @set_time_limit(120);
6204 error_reporting($err);
6205
6206 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6207 $tmp = explode(':', $modele, 2);
6208 $saved_model = $modele;
6209 if (!empty($tmp[1])) {
6210 $modele = $tmp[0];
6211 $srctemplatepath = $tmp[1];
6212
6213 if (!preg_match('/^'.preg_quote(DOL_DATA_ROOT, '/').'\/(ecm|doctemplates)/', $srctemplatepath)) {
6214 $this->error = 'BadDirForTemplateFile';
6215 return -1;
6216 }
6217 }
6218
6219 // Search template files
6220 $file = '';
6221 $classname = '';
6222 $filefound = '';
6223 $dirmodels = array('/');
6224 if (is_array($conf->modules_parts['models'])) {
6225 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6226 }
6227 foreach ($dirmodels as $reldir) {
6228 foreach (array('doc', 'pdf') as $prefix) {
6229 if (in_array(get_class($this), array('Adherent'))) {
6230 // Member module use prefix_modele.class.php
6231 $file = $prefix."_".$modele.".class.php";
6232 } else {
6233 // Other module use prefix_modele.modules.php
6234 $file = $prefix."_".$modele.".modules.php";
6235 }
6236
6237 $file = dol_sanitizeFileName($file);
6238
6239 // We check if the file exists
6240 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6241 if (file_exists($file)) {
6242 $filefound = $file;
6243 $classname = $prefix.'_'.$modele;
6244 break;
6245 }
6246 }
6247 if ($filefound) {
6248 break;
6249 }
6250 }
6251
6252 if ($filefound === '' || $classname === '') {
6253 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6254 $this->errors[] = $this->error;
6255 dol_syslog($this->error, LOG_ERR);
6256 return -1;
6257 }
6258
6259 // Sanitize $filefound
6260 $filefound = dol_sanitizePathName($filefound);
6261
6262 // If generator was found
6263 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6264
6265 require_once $filefound;
6266
6267 $obj = new $classname($this->db);
6268
6269 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6270 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6271 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6272 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6273 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6274 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6275 // ModelePDFStock|ModelePDFStockTransfer|
6276 // ModeleDon|ModelePDFTask|
6277 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6278 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6279 '@phan-var-force ModelePDFMember $obj';
6280 // '@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';
6281
6282 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6283 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6284 $varfortemplatedir = $obj->scandir;
6285 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6286 $dirtoscan = getDolGlobalString($varfortemplatedir);
6287
6288 $listoffiles = array();
6289
6290 // Now we add first model found in directories scanned
6291 $listofdir = explode(',', $dirtoscan);
6292 foreach ($listofdir as $key => $tmpdir) {
6293 $tmpdir = trim($tmpdir);
6294 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6295 if (!$tmpdir) {
6296 unset($listofdir[$key]);
6297 continue;
6298 }
6299 if (is_dir($tmpdir)) {
6300 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6301 if (count($tmpfiles)) {
6302 $listoffiles = array_merge($listoffiles, $tmpfiles);
6303 }
6304 }
6305 }
6306
6307 if (count($listoffiles)) {
6308 foreach ($listoffiles as $record) {
6309 $srctemplatepath = $record['fullname'];
6310 break;
6311 }
6312 }
6313 }
6314
6315 if (empty($srctemplatepath)) {
6316 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6317 return -1;
6318 }
6319 }
6320
6321 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6322 if (!dol_is_file($srctemplatepath)) {
6323 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6324 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6325 return -1;
6326 }
6327 }
6328
6329 // We save charset_output to restore it because write_file can change it if needed for
6330 // output format that does not support UTF8.
6331 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6332
6333 // update model_pdf in object
6334 $this->model_pdf = $saved_model;
6335
6336 try {
6337 if ($obj instanceof ModelePDFMember) {
6338 if ($this instanceof Adherent) {
6339 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6340 } else {
6341 $resultwritefile = -1;
6342 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6343 }
6344 } elseif ($obj instanceof ModeleDon) {
6345 // Only 3 arguments
6346 if ($this instanceof Don) {
6347 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6348 } else {
6349 $resultwritefile = -1;
6350 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6351 }
6352 } else {
6353 // TODO: Try to set type above again
6354 '@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';
6355 $this->context['moreparams'] = $moreparams;
6356 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref); // @phan-suppress-current-line PhanTypeMismatchArgument
6357 }
6358 } catch (\Throwable $e) {
6359 $resultwritefile = -1;
6360 $this->error = $e->getMessage();
6361 dol_syslog("Exception in write_file() for ".get_class($this).": ".$e->getMessage(), LOG_ERR);
6362 }
6363 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6364
6365
6366 if ($resultwritefile > 0) {
6367 $outputlangs->charset_output = $sav_charset_output;
6368
6369 // We delete old preview
6370 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6371 dol_delete_preview($this);
6372
6373 // Index file in database
6374 if (!empty($obj->result['fullpath'])) {
6375 $destfull = $obj->result['fullpath'];
6376
6377 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6378 $update_main_doc_field = 0;
6379 if (!empty($obj->update_main_doc_field)) {
6380 $update_main_doc_field = 1;
6381 }
6382
6383 // Check that the file exists, before indexing it.
6384 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6385 if (dol_is_file($destfull)) {
6386 $this->indexFile($destfull, $update_main_doc_field);
6387 }
6388 } else {
6389 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);
6390 }
6391
6392 // Success in building document. We build meta file.
6393 dol_meta_create($this);
6394
6395 $this->warnings = $obj->warnings;
6396
6397 return 1;
6398 } else {
6399 $outputlangs->charset_output = $sav_charset_output;
6400 if (empty($this->error)) {
6401 $this->error = $obj->error;
6402 }
6403 if (empty($this->errors)) {
6404 $this->errors = $obj->errors;
6405 }
6406 $this->warnings = $obj->warnings;
6407 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$this->error, LOG_ERR);
6408 return -1;
6409 }
6410 }
6411
6421 public function indexFile($destfull, $update_main_doc_field)
6422 {
6423 global $conf, $user;
6424
6425 $upload_dir = dirname($destfull);
6426 $destfile = basename($destfull);
6427 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6428
6429 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6430 $filename = basename($destfile);
6431 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6432 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6433
6434 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6435 $ecmfile = new EcmFiles($this->db);
6436 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6437
6438 // Set the public "share" key
6439 $setsharekey = false;
6440 if (
6441 !empty($this->TRIGGER_PREFIX)
6442 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6443 ) {
6444 $setsharekey = true;
6445 }
6446 // TODO Remove case covered by trigger prefix
6447 if ($this->element == 'propal' || $this->element == 'proposal') {
6448 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6449 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6450 }
6451 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6452 $setsharekey = true;
6453 }
6454 }
6455 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6456 $setsharekey = true;
6457 }
6458 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6459 $setsharekey = true;
6460 }
6461 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6462 $setsharekey = true;
6463 }
6464 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6465 $setsharekey = true;
6466 }
6467 if ($this->element == 'order_supplier' && getDolGlobalInt("SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6468 $setsharekey = true;
6469 }
6470 if ($this->element == 'invoice_supplier' && getDolGlobalInt("SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6471 $setsharekey = true;
6472 }
6473 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6474 $setsharekey = true;
6475 }
6476
6477 if ($setsharekey) {
6478 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6479 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6480 $ecmfile->share = getRandomPassword(true);
6481 }
6482 }
6483
6484 if ($result > 0) {
6485 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6486 $ecmfile->fullpath_orig = '';
6487 $ecmfile->gen_or_uploaded = 'generated';
6488 $ecmfile->description = ''; // indexed content
6489 $ecmfile->keywords = ''; // keyword content
6490 $ecmfile->src_object_type = $this->table_element;
6491 $ecmfile->src_object_id = $this->id;
6492 $result = $ecmfile->update($user);
6493 if ($result < 0) {
6494 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6495 return -1;
6496 }
6497 } else {
6498 $ecmfile->entity = $conf->entity;
6499 $ecmfile->filepath = $rel_dir;
6500 $ecmfile->filename = $filename;
6501 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6502 $ecmfile->fullpath_orig = '';
6503 $ecmfile->gen_or_uploaded = 'generated';
6504 $ecmfile->description = ''; // indexed content
6505 $ecmfile->keywords = ''; // keyword content
6506 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6507 $ecmfile->src_object_id = $this->id;
6508
6509 $result = $ecmfile->create($user);
6510 if ($result < 0) {
6511 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6512 return -1;
6513 }
6514 }
6515
6516 /*$this->result['fullname']=$destfull;
6517 $this->result['filepath']=$ecmfile->filepath;
6518 $this->result['filename']=$ecmfile->filename;*/
6519 //var_dump($obj->update_main_doc_field);exit;
6520
6521 if ($update_main_doc_field && !empty($this->table_element)) {
6522 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6523 $sql .= " WHERE rowid = ".((int) $this->id);
6524
6525 $resql = $this->db->query($sql);
6526 if (!$resql) {
6527 dol_print_error($this->db);
6528 return -1;
6529 } else {
6530 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6531 }
6532 }
6533 }
6534
6535 return 1;
6536 }
6537
6546 public function addThumbs($file, $quality = 50)
6547 {
6548 $file_osencoded = dol_osencode($file);
6549
6550 if (file_exists($file_osencoded)) {
6551 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6552
6553 $tmparraysize = getDefaultImageSizes();
6554 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6555 $maxheightsmall = $tmparraysize['maxheightsmall'];
6556 $maxwidthmini = $tmparraysize['maxwidthmini'];
6557 $maxheightmini = $tmparraysize['maxheightmini'];
6558 //$quality = $tmparraysize['quality'];
6559
6560 // Create small thumbs for company (Ratio is near 16/9)
6561 // Used on logon for example
6562 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6563
6564 // Create mini thumbs for company (Ratio is near 16/9)
6565 // Used on menu or for setup page for example
6566 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6567 }
6568 }
6569
6577 public function delThumbs($file)
6578 {
6579 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6580 dol_delete_file($imgThumbName);
6581 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6582 dol_delete_file($imgThumbName);
6583 }
6584
6585
6586 /* Functions common to commonobject and commonobjectline */
6587
6588 /* For default values */
6589
6603 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6604 {
6605 /* TODO Remove this. Must use now something like:
6606 $note_private = GETPOST('note_private', 'restricthtml');
6607 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6608 $note_private = $objectsrc->note_private;
6609 }
6610 */
6611
6612 // If param here has been posted, we use this value first.
6613 if (GETPOSTISSET($fieldname)) {
6614 return GETPOST($fieldname, $type, 3);
6615 }
6616
6617 if (isset($alternatevalue)) {
6618 return $alternatevalue;
6619 }
6620
6621 $newelement = $this->element;
6622 if ($newelement == 'facture') {
6623 $newelement = 'invoice';
6624 }
6625 if ($newelement == 'commande') {
6626 $newelement = 'order';
6627 }
6628 if (empty($newelement)) {
6629 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6630 return '';
6631 }
6632
6633 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6634 //var_dump($keyforfieldname);
6635 if (getDolGlobalString($keyforfieldname)) {
6636 return getDolGlobalString($keyforfieldname);
6637 }
6638
6639 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6640 // store content into $conf->cache['overwrite_default']
6641
6642 return '';
6643 }
6644
6645
6646 /* Functions for data in other language */
6647
6648
6658 {
6659 // To avoid SQL errors. Probably not the better solution though
6660 if (!$this->element) {
6661 return 0;
6662 }
6663 if (!($this->id > 0)) {
6664 return 0;
6665 }
6666 if (is_array($this->array_languages)) {
6667 return 1;
6668 }
6669
6670 $this->array_languages = array();
6671
6672 $element = $this->element;
6673 if ($element == 'categorie') {
6674 $element = 'categories'; // For compatibility
6675 }
6676
6677 // Request to get translation values for object
6678 $sql = "SELECT rowid, property, lang , value";
6679 $sql .= " FROM ".$this->db->prefix()."object_lang";
6680 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6681 $sql .= " AND fk_object = ".((int) $this->id);
6682
6683 $resql = $this->db->query($sql);
6684 if ($resql) {
6685 $numrows = $this->db->num_rows($resql);
6686 if ($numrows) {
6687 $i = 0;
6688 while ($i < $numrows) {
6689 $obj = $this->db->fetch_object($resql);
6690 $key = $obj->property;
6691 $value = $obj->value;
6692 $codelang = $obj->lang;
6693 $type = $this->fields[$key]['type'];
6694
6695 // we can add this attribute to object
6696 if (preg_match('/date/', $type)) {
6697 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6698 } else {
6699 $this->array_languages[$key][$codelang] = $value;
6700 }
6701
6702 $i++;
6703 }
6704 }
6705
6706 $this->db->free($resql);
6707
6708 if ($numrows) {
6709 return $numrows;
6710 } else {
6711 return 0;
6712 }
6713 } else {
6714 dol_print_error($this->db);
6715 return -1;
6716 }
6717 }
6718
6726 public function setValuesForExtraLanguages($onlykey = '')
6727 {
6728 // Get extra fields
6729 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6730 $tmparray = explode('-', $postfieldkey);
6731 if ($tmparray[0] != 'field') {
6732 continue;
6733 }
6734
6735 $element = $tmparray[1];
6736 $key = $tmparray[2];
6737 $codelang = $tmparray[3];
6738 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6739
6740 if (!empty($onlykey) && $key != $onlykey) {
6741 continue;
6742 }
6743 if ($element != $this->element) {
6744 continue;
6745 }
6746
6747 $key_type = $this->fields[$key]['type'];
6748
6749 $enabled = 1;
6750 if (isset($this->fields[$key]['enabled'])) {
6751 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6752 }
6753 /*$perms = 1;
6754 if (isset($this->fields[$key]['perms']))
6755 {
6756 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6757 }*/
6758 if (empty($enabled)) {
6759 continue;
6760 }
6761 //if (empty($perms)) continue;
6762
6763 if (in_array($key_type, array('date'))) {
6764 // Clean parameters
6765 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6766 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6767 } elseif (in_array($key_type, array('datetime'))) {
6768 // Clean parameters
6769 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6770 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6771 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6772 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6773 if (!empty($value_arr)) {
6774 $value_key = implode(',', $value_arr);
6775 } else {
6776 $value_key = '';
6777 }
6778 } elseif (in_array($key_type, array('price', 'double'))) {
6779 $value_arr = GETPOST($postfieldkey, 'alpha');
6780 $value_key = price2num($value_arr);
6781 } else {
6782 $value_key = GETPOST($postfieldkey);
6783 if (in_array($key_type, array('link')) && $value_key == '-1') {
6784 $value_key = '';
6785 }
6786 }
6787
6788 $this->array_languages[$key][$codelang] = $value_key;
6789
6790 /*if ($nofillrequired) {
6791 $langs->load('errors');
6792 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6793 return -1;
6794 }*/
6795 }
6796
6797 return 1;
6798 }
6799
6800
6801 /* Functions for extrafields */
6802
6809 public function fetchNoCompute($id)
6810 {
6811 global $conf;
6812
6813 $savDisableCompute = $conf->disable_compute;
6814 $conf->disable_compute = 1;
6815
6816 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6817
6818 $conf->disable_compute = $savDisableCompute;
6819
6820 return $ret;
6821 }
6822
6823 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6833 public function fetch_optionals($rowid = null, $optionsArray = null)
6834 {
6835 // phpcs:enable
6836 global $conf, $extrafields;
6837
6838 if (empty($rowid)) {
6839 $rowid = $this->id;
6840 }
6841 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6842 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6843 }
6844
6845 // To avoid SQL errors. Probably not the better solution though
6846 if (!$this->table_element) {
6847 return 0;
6848 }
6849 $this->array_options = array();
6850
6851 if (!is_array($optionsArray)) {
6852 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6853 if (!isset($extrafields) || !is_object($extrafields)) {
6854 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6855 $extrafields = new ExtraFields($this->db);
6856 }
6857
6858 // Load array of extrafields for elementype = $this->table_element
6859 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6860 $extrafields->fetch_name_optionals_label($this->table_element);
6861 }
6862 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6863 } else {
6864 global $extrafields;
6865 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6866 }
6867
6868 $table_element = $this->table_element;
6869 if ($table_element == 'categorie') {
6870 $table_element = 'categories'; // For compatibility
6871 }
6872
6873 // Request to get complementary values
6874 if (is_array($optionsArray) && count($optionsArray) > 0) {
6875 $sql = "SELECT rowid";
6876 foreach ($optionsArray as $name => $label) {
6877 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6878 $sql .= ", ".$this->db->sanitize($name);
6879 }
6880 // use geo sql function to read as text
6881 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6882 // TODO Add an abstraction method in the database driver
6883 $sql .= ", ST_AsWKT(".$this->db->sanitize($name).") as ".$this->db->sanitize($name);
6884 }
6885 }
6886 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6887 $sql .= " WHERE fk_object = ".((int) $rowid);
6888
6889 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6890 $resql = $this->db->query($sql);
6891 if ($resql) {
6892 $numrows = $this->db->num_rows($resql);
6893 if ($numrows) {
6894 $tab = $this->db->fetch_array($resql);
6895 foreach ($tab as $key => $value) {
6896 // 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)
6897 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6898 // we can add this attribute to object
6899 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6900 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6901 $this->array_options["options_".$key] = $this->db->jdate($value);
6902 } else {
6903 $this->array_options["options_".$key] = $value;
6904 }
6905
6906 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6907 }
6908 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6909 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6910 $this->array_options["options_".$key] = dolDecrypt($value);
6911 }
6912 }
6913 }
6914 } else {
6919 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6920 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6921 $this->array_options['options_' . $key] = null;
6922 }
6923 }
6924 }
6925
6926 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6927 // in the element's extrafields table)
6928 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6929 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6930 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6931 //var_dump($conf->disable_compute);
6932 if (empty($conf->disable_compute)) {
6933 // We set a global variable to $objectoffield so we can use it inside computed formula
6934 $objectoffield = dol_clone($this, 2);
6935 global $objectoffield;
6936 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6937 }
6938 }
6939 }
6940 }
6941
6942 $this->db->free($resql);
6943
6944 if ($numrows) {
6945 return $numrows;
6946 } else {
6947 return 0;
6948 }
6949 } else {
6950 $this->errors[] = $this->db->lasterror();
6951 return -1;
6952 }
6953 }
6954 return 0;
6955 }
6956
6963 public function deleteExtraFields()
6964 {
6965 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6966 return 0;
6967 }
6968
6969 $this->db->begin();
6970
6971 $table_element = $this->table_element;
6972 if ($table_element == 'categorie') {
6973 $table_element = 'categories'; // For compatibility
6974 }
6975
6976 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6977
6978 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6979
6980 $resql = $this->db->query($sql_del);
6981 if (!$resql) {
6982 $this->error = $this->db->lasterror();
6983 $this->db->rollback();
6984 return -1;
6985 } else {
6986 $this->db->commit();
6987 return 1;
6988 }
6989 }
6990
7001 public function insertExtraFields($trigger = '', $userused = null)
7002 {
7003 global $langs, $extrafields, $user;
7004
7005 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
7006 return 0;
7007 }
7008
7009 if (empty($userused)) {
7010 $userused = $user;
7011 }
7012
7013 $error = 0;
7014
7015 // Check parameters
7016 $langs->load('admin');
7017 if (!isset($extrafields) || !is_object($extrafields)) {
7018 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7019 $extrafields = new ExtraFields($this->db);
7020 }
7021 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
7022
7023 // Eliminate copied source object extra fields that do not exist in target object
7024 $new_array_options = array();
7025 foreach ($this->array_options as $key => $value) {
7026 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
7027 $new_array_options[$key] = $value;
7028 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
7029 $new_array_options['options_'.$key] = $value;
7030 }
7031 }
7032
7033 foreach ($new_array_options as $key => $value) {
7034 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7035 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7036 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
7037 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7038 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7039 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7040 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7041 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
7042 /*
7043 $attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7044 $enabled = 0;
7045 if (!empty($extrafields->attributes[$this->table_element]['enabled'][$key])) {
7046 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
7047 }
7048 */
7049
7050 // If we clone, we have to clean unique extrafields to prevent duplicates.
7051 // If we clone, we have to clean extrafields having "empty on clone" option on.
7052 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
7053 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
7054 $new_array_options[$key] = null;
7055 continue;
7056 }
7057
7058 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
7059 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
7060 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
7061 $new_array_options[$key] = null;
7062 continue;
7063 }
7064
7065 // Similar code than into insertExtraFields
7066 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'.
7067 $v = $this->array_options[$key];
7068 if (ExtraFields::isEmptyValue($v, $attributeType)) {
7069 $langs->load("errors");
7070 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
7071 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7072 return -1;
7073 }
7074 }
7075
7076 if (!empty($attrfieldcomputed)) {
7077 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7078 global $objectoffield; // We set a global variable to $objectoffield so
7079 $objectoffield = $this; // we can use it inside the computed formula
7080 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7081 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7082 $new_array_options[$key] = $value;
7083 } else {
7084 $new_array_options[$key] = null;
7085 }
7086 continue;
7087 }
7088
7089 switch ($attributeType) {
7090 case 'int':
7091 case 'duration':
7092 case 'stars':
7093 if (!is_numeric($value) && $value != '') {
7094 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7095 return -1;
7096 } elseif ($value === '') {
7097 $new_array_options[$key] = null;
7098 }
7099 break;
7100 case 'boolean':
7101 if ($value === '' || $value === false || $value === null) {
7102 $new_array_options[$key] = null;
7103 }
7104 break;
7105 case 'price':
7106 case 'double':
7107 $value = price2num($value);
7108 if (!is_numeric($value) && $value != '') {
7109 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7110 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7111 return -1;
7112 } elseif ($value == '') {
7113 $value = null;
7114 }
7115 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7116 $new_array_options[$key] = $value;
7117 break;
7118 /*case 'select': // Not required, we chose value='0' for undefined values
7119 if ($value=='-1')
7120 {
7121 $this->array_options[$key] = null;
7122 }
7123 break;*/
7124 case 'password':
7125 $algo = '';
7126 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7127 // If there is an encryption choice, we use it to encrypt data before insert
7128 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7129 $algo = reset($tmparrays);
7130 if ($algo != '') {
7131 //global $action; // $action may be 'create', 'update', 'update_extras'...
7132 //var_dump($action);
7133 //var_dump($this->oldcopy);exit;
7134 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
7135 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7136 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7137 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7138 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7139 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7140 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7141 } else {
7142 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7143 }
7144 } else {
7145 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7146 }
7147 } else {
7148 // If value has changed
7149 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7150 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7151 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7152 } else {
7153 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7154 }
7155 } else {
7156 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7157 }
7158 }
7159 } else {
7160 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7161 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7162 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7163 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7164 } else {
7165 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7166 }
7167 }
7168 } else {
7169 // No encryption
7170 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7171 }
7172 } else { // Common usage
7173 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7174 }
7175 break;
7176 case 'date':
7177 case 'datetime':
7178 // If data is a string instead of a timestamp, we convert it
7179 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7180 $this->array_options[$key] = strtotime($this->array_options[$key]);
7181 }
7182 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7183 break;
7184 case 'datetimegmt':
7185 // If data is a string instead of a timestamp, we convert it
7186 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7187 $this->array_options[$key] = strtotime($this->array_options[$key]);
7188 }
7189 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7190 break;
7191 case 'link':
7192 $param_list = array_keys($attributeParam['options']);
7193 // 0 : ObjectName
7194 // 1 : classPath
7195 $InfoFieldList = explode(":", $param_list[0]);
7196 dol_include_once($InfoFieldList[1]);
7197 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7198 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7199 $new_array_options[$key] = '';
7200 } elseif ($value) {
7201 $object = new $InfoFieldList[0]($this->db);
7202 '@phan-var-force CommonObject $object';
7203
7204 $objectId = 0;
7205
7206 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7207 if (is_numeric($value)) {
7208 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7209 } else {
7210 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7211 }
7212
7213 $obj = $this->db->getRow($sqlFetchObject);
7214
7215 if ($obj !== false) {
7216 $objectId = $obj->rowid;
7217 $res = 1;
7218 } else {
7219 $res = -1;
7220 }
7221
7222 if ($res > 0) {
7223 $new_array_options[$key] = $objectId;
7224 } else {
7225 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7226 return -1;
7227 }
7228 }
7229 } else {
7230 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7231 }
7232 break;
7233 case 'checkbox':
7234 case 'chkbxlst':
7235 if (is_array($this->array_options[$key])) {
7236 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7237 } else {
7238 $new_array_options[$key] = $this->array_options[$key];
7239 }
7240 break;
7241 }
7242 }
7243
7244 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7245 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7246
7247 foreach ($new_array_options as $key => $newValue) {
7248 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7249 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7250 if ($attributeType === 'separate') {
7251 // separator extrafields don't have data per object so they don't have a comlumn in the database
7252 continue;
7253 }
7254 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7255 // Add field of attribute
7256 if (! $geoDataType) {
7257 // not a geodata type
7258 if ($newValue != '') {
7259 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7260 } else {
7261 $sqlColumnValues[$attributeKey] = 'null';
7262 }
7263 continue;
7264 }
7265 if (empty($newValue)) {
7266 $sqlColumnValues[$attributeKey] = 'null';
7267 continue;
7268 }
7269 if (preg_match('/error/i', $newValue)) {
7270 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7271 $sqlColumnValues[$attributeKey] = 'null';
7272 continue;
7273 }
7274
7275 // Geodata type: Text must be a WKT string. Examples:
7276 // - point => "POINT(15 20)"
7277 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7278 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7279 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7280 $sqlColumnValues[$attributeKey] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')"; // @phan-suppress-current-line SqlInjection
7281 }
7282
7283 $this->db->begin();
7284
7285 $table_element = $this->table_element;
7286 if ($table_element == 'categorie') {
7287 $table_element = 'categories'; // For compatibility
7288 }
7289 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7290
7291 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7292
7293 $linealreadyfound = 0;
7294 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7295 $resql = $this->db->query($sql);
7296 if ($resql) {
7297 $tmpobj = $this->db->fetch_object($resql);
7298 if ($tmpobj) {
7299 $linealreadyfound = $tmpobj->nb;
7300 }
7301 }
7302
7303 // if the extrafields row already exists for the object, we update it
7304 if ($linealreadyfound) {
7305 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7306 if (empty($sqlColumnValues)) {
7307 // No column of the target element to update. This happens when the object was created from
7308 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7309 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7310 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7311 // that would produce an invalid SQL statement.
7312 $this->db->commit();
7313 return 1;
7314 }
7315 $sqlColumnValueString = implode(
7316 ',',
7321 array_map(function ($key) use ($sqlColumnValues) {
7322 return "{$key} = {$sqlColumnValues[$key]}";
7323 }, array_keys($sqlColumnValues))
7324 );
7325 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7326 } else {
7327 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7328 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7329 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7330 if (isset($sqlColumnValues[$key])) {
7331 // extrafield value already provided: no need to add it
7332 continue;
7333 }
7334 // default value: empty string, except for 'int', 'double' and 'price'
7335 $sqlColumnValues[$key] = "''";
7336 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7337 $sqlColumnValues[$key] = 0;
7338 }
7339 }
7340 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7341 $sqlValues = implode(',', array_values($sqlColumnValues));
7342 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7343 }
7344
7345 $resql = $this->db->query($sql);
7346 if (!$resql) {
7347 $this->error = $this->db->lasterror();
7348 $error++;
7349 }
7350
7351 // Update also the user of last modification in parent table
7352 if (!$error && !empty($this->fields['fk_user_modif'])) { // @phan-suppress-current-line PhanTypeMismatchProperty
7353 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7354 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7355 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7356 $this->db->query($sql);
7357 }
7358
7359 if (!$error && $trigger) {
7360 // Call trigger
7361 $this->context = array('extrafieldaddupdate' => 1);
7362 $result = $this->call_trigger($trigger, $userused);
7363 if ($result < 0) {
7364 $error++;
7365 }
7366 // End call trigger
7367 }
7368
7369 if ($error) {
7370 $this->db->rollback();
7371 return -1;
7372 } else {
7373 $this->db->commit();
7374 return 1;
7375 }
7376 }
7377
7389 public function insertExtraLanguages($trigger = '', $userused = null)
7390 {
7391 global $langs, $user;
7392
7393 if (empty($userused)) {
7394 $userused = $user;
7395 }
7396
7397 $error = 0;
7398
7399 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7400 return 0; // For avoid conflicts if trigger used
7401 }
7402
7403 if (is_array($this->array_languages)) {
7404 $new_array_languages = $this->array_languages;
7405
7406 foreach ($new_array_languages as $key => $value) {
7407 $attributeKey = $key;
7408 $attributeType = $this->fields[$attributeKey]['type'];
7409 $attributeLabel = $this->fields[$attributeKey]['label'];
7410
7411 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7412 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7413
7414 switch ($attributeType) {
7415 case 'int':
7416 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7417 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7418 return -1;
7419 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7420 $new_array_languages[$key] = null;
7421 }
7422 break;
7423 case 'double':
7424 $value = price2num((string) $value);
7425 if (!is_numeric($value) && $value != '') {
7426 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7427 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7428 return -1;
7429 } elseif ($value == '') {
7430 $new_array_languages[$key] = null;
7431 } else {
7432 $new_array_languages[$key] = $value;
7433 }
7434 break;
7435 /*case 'select': // Not required, we chose value='0' for undefined values
7436 if ($value=='-1')
7437 {
7438 $this->array_options[$key] = null;
7439 }
7440 break;*/
7441 }
7442 }
7443
7444 $this->db->begin();
7445
7446 $table_element = $this->table_element;
7447 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7448 $table_element = 'categories'; // For compatibility
7449 }
7450
7451 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7452
7453 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7454 foreach ($langcodearray as $langcode => $value) {
7455 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7456 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7457 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7458 $this->db->query($sql_del);
7459
7460 if ($value !== '') {
7461 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7462 $sql .= ") VALUES (".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7463 $sql .= ")";
7464
7465 $resql = $this->db->query($sql);
7466 if (!$resql) {
7467 $this->error = $this->db->lasterror();
7468 $error++;
7469 break;
7470 }
7471 }
7472 }
7473 }
7474
7475 if (!$error && $trigger) {
7476 // Call trigger
7477 $this->context = array('extralanguagesaddupdate' => 1);
7478 $result = $this->call_trigger($trigger, $userused);
7479 if ($result < 0) {
7480 $error++;
7481 }
7482 // End call trigger
7483 }
7484
7485 if ($error) {
7486 $this->db->rollback();
7487 return -1;
7488 } else {
7489 $this->db->commit();
7490 return 1;
7491 }
7492 } else {
7493 return 0;
7494 }
7495 }
7496
7507 public function updateExtraField($key, $trigger = null, $userused = null)
7508 {
7509 global $langs, $user, $extrafields, $hookmanager;
7510
7511 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7512 return 0;
7513 }
7514
7515 if (empty($userused)) {
7516 $userused = $user;
7517 }
7518
7519 $error = 0;
7520
7521 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7522 // Check parameters
7523 $langs->load('admin');
7524 if (!isset($extrafields) || !is_object($extrafields)) {
7525 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7526 $extrafields = new ExtraFields($this->db);
7527 }
7528 $extrafields->fetch_name_optionals_label($this->table_element);
7529 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7530 // Extrafield not defined for this object type: nothing to update
7531 return 0;
7532 }
7533 $value = $this->array_options["options_".$key];
7534
7535 $attributeKey = $key;
7536 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7537 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7538 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7539 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7540 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7541 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7542 //$attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7543
7544 // Similar code than into insertExtraFields
7545 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'.
7546 $mandatorypb = false;
7547 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7548 $mandatorypb = true;
7549 }
7550 if ($this->array_options["options_".$key] === '') {
7551 $mandatorypb = true;
7552 }
7553 if ($mandatorypb) {
7554 $langs->load("errors");
7555 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7556 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7557 return -1;
7558 }
7559 }
7560
7561 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7562 $new_array_options = $this->array_options;
7563
7564 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7565 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7566 if (!empty($attrfieldcomputed)) {
7567 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7568 global $objectoffield; // We set a global variable to $objectoffield so
7569 $objectoffield = $this; // we can use it inside the computed formula
7570 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7571 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7572
7573 $new_array_options["options_".$key] = $value;
7574
7575 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7576 } else {
7577 $new_array_options["options_".$key] = null;
7578
7579 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7580 }
7581 }
7582
7583 switch ($attributeType) {
7584 case 'int':
7585 if (!is_numeric($value) && $value != '') {
7586 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7587 return -1;
7588 } elseif ($value === '') {
7589 $new_array_options["options_".$key] = null;
7590
7591 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7592 }
7593 break;
7594 case 'price':
7595 case 'double':
7596 $value = price2num($value);
7597 if (!is_numeric($value) && $value != '') {
7598 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7599 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7600 return -1;
7601 } elseif ($value === '') {
7602 $value = null;
7603 }
7604 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7605 $new_array_options["options_".$key] = $value;
7606
7607 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7608 break;
7609 /*case 'select': // Not required, we chose value='0' for undefined values
7610 if ($value=='-1')
7611 {
7612 $new_array_options["options_".$key] = $value;
7613
7614 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7615 }
7616 break;*/
7617 case 'password':
7618 $algo = '';
7619 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7620 // If there is an encryption choice, we use it to encrypt data before insert
7621 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7622 $algo = reset($tmparrays);
7623 if ($algo != '') {
7624 //global $action; // $action may be 'create', 'update', 'update_extras'...
7625 //var_dump($action);
7626 //var_dump($this->oldcopy);exit;
7627 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7628 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
7629 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7630 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.
7631 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7632 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7633 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7634 } else {
7635 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7636 }
7637 } else {
7638 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7639 }
7640 } else {
7641 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7642 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7643 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7644 } else {
7645 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7646 }
7647 } else {
7648 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7649 }
7650 }
7651 } else {
7652 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7653 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7654 } else {
7655 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7656 }
7657 }
7658 } else {
7659 // No encryption
7660 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7661 }
7662 } else { // Common usage
7663 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7664 }
7665
7666 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7667 break;
7668 case 'date':
7669 case 'datetime':
7670 if (empty($this->array_options["options_".$key])) {
7671 $new_array_options["options_".$key] = null;
7672
7673 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7674 } else {
7675 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7676 }
7677 break;
7678 case 'datetimegmt':
7679 if (empty($this->array_options["options_".$key])) {
7680 $new_array_options["options_".$key] = null;
7681
7682 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7683 } else {
7684 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7685 }
7686 break;
7687 case 'boolean':
7688 if (empty($this->array_options["options_".$key])) {
7689 $new_array_options["options_".$key] = null;
7690
7691 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7692 }
7693 break;
7694 case 'link':
7695 if ($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 /*
7702 case 'link':
7703 $param_list = array_keys($attributeParam['options']);
7704 // 0 : ObjectName
7705 // 1 : classPath
7706 $InfoFieldList = explode(":", $param_list[0]);
7707 dol_include_once($InfoFieldList[1]);
7708 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7709 {
7710 if ($value == '-1') // -1 is key for no defined in combo list of objects
7711 {
7712 $new_array_options[$key] = '';
7713 } elseif ($value) {
7714 $object = new $InfoFieldList[0]($this->db);
7715 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7716 else $res = $object->fetch(0, $value); // For compatibility
7717
7718 if ($res > 0) $new_array_options[$key] = $object->id;
7719 else {
7720 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7721 $this->db->rollback();
7722 return -1;
7723 }
7724 }
7725 } else {
7726 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7727 }
7728 break;
7729 */
7730 case 'checkbox':
7731 case 'chkbxlst':
7732 $new_array_options = array();
7733 if (is_array($this->array_options["options_".$key])) {
7734 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7735 } else {
7736 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7737 }
7738
7739 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7740 break;
7741 }
7742
7743 $this->db->begin();
7744
7745 $linealreadyfound = 0;
7746
7747 // 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)
7748 $table_element = $this->table_element;
7749 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7750 $table_element = 'categories'; // For compatibility
7751 }
7752
7753 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7754 $resql = $this->db->query($sql);
7755 if ($resql) {
7756 $tmpobj = $this->db->fetch_object($resql);
7757 if ($tmpobj) {
7758 $linealreadyfound = $tmpobj->nb;
7759 }
7760 }
7761
7762 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7763 if ($linealreadyfound) {
7764 $sanitizedGeoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null; // @phan-suppress-current-line SqlInjection
7765 if ($this->array_options["options_".$key] === null) {
7766 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7767 $sql .= " SET ".$this->db->sanitize($key)." = null";
7768 } elseif (!empty($sanitizedGeoDataType['ST_Function'])) {
7769 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7770 $sql .= " SET ".$this->db->sanitize($key)." = ".$this->db->sanitize($sanitizedGeoDataType['ST_Function'])."('".$this->db->escape($this->array_options["options_".$key])."')";
7771 } else {
7772 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7773 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7774 $sql .= " SET ".$this->db->sanitize($key)." = '".$this->db->escape($new_array_options["options_".$key])."'";
7775 }
7776 $sql .= " WHERE fk_object = ".((int) $this->id);
7777
7778 $resql = $this->db->query($sql);
7779 if (!$resql) {
7780 $error++;
7781 $this->error = $this->db->lasterror();
7782 }
7783 } else {
7784 $result = $this->insertExtraFields('', $user);
7785 if ($result < 0) {
7786 $error++;
7787 }
7788 }
7789
7790 // Update also the user of last modification in parent table
7791 if (!$error && !empty($this->fields['fk_user_modif'])) {
7792 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7793 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7794 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7795 $this->db->query($sql);
7796 }
7797
7798 if (!$error) {
7799 $parameters = array('key' => $key);
7800 global $action;
7801 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7802 if ($reshook < 0) {
7803 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7804 }
7805 }
7806
7807 if (!$error && $trigger) {
7808 // Call trigger
7809 $this->context = array('extrafieldupdate' => 1);
7810 $result = $this->call_trigger($trigger, $userused);
7811 if ($result < 0) {
7812 $error++;
7813 }
7814 // End call trigger
7815 }
7816
7817 if ($error) {
7818 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7819 $this->db->rollback();
7820 return -1;
7821 } else {
7822 $this->db->commit();
7823 return 1;
7824 }
7825 } else {
7826 return 0;
7827 }
7828 }
7829
7836 public function getExtraField($key)
7837 {
7838 return $this->array_options['options_'.$key] ?? null;
7839 }
7840
7848 public function setExtraField($key, $value)
7849 {
7850 $this->array_options['options_'.$key] = $value;
7851 }
7852
7863 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7864 {
7865 global $user;
7866
7867 if (empty($userused)) {
7868 $userused = $user;
7869 }
7870
7871 //$error = 0;
7872
7873 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7874 return 0; // For avoid conflicts if trigger used
7875 }
7876
7877 return 0;
7878 }
7879
7880
7895 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7896 {
7897 global $conf, $langs, $form;
7898
7899
7900 // 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...)
7901
7902 if (!is_object($form)) {
7903 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7904 $form = new Form($this->db);
7905 }
7906
7907 if (!empty($this->fields)) {
7908 $val = $this->fields[$key];
7909 }
7910
7911 // Validation tests and output
7912 $fieldValidationErrorMsg = '';
7913 $validationClass = '';
7914 $fieldValidationErrorMsg = $this->getFieldError($key);
7915 if (!empty($fieldValidationErrorMsg)) {
7916 $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
7917 } else {
7918 $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
7919 }
7920
7921 //$valuemultiselectinput = array();
7922 $out = '';
7923 $type = '';
7924 $isDependList = 0;
7925 $param = array();
7926 $param['options'] = array();
7927 $reg = array();
7928 // @phan-suppress-next-line PhanTypeMismatchProperty
7929 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7930 // Because we work on extrafields
7931 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7932 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7933 $type = 'link';
7934 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7935 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7936 $type = 'link';
7937 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7938 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7939 $type = 'link';
7940 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7941 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7942 $type = 'sellist';
7943 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7944 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7945 $type = 'sellist';
7946 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7947 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7948 $type = 'sellist';
7949 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7950 $param['options'] = array($reg[1] => 'N');
7951 $type = 'chkbxlst';
7952 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7953 $param['options'] = array();
7954 $type = 'varchar';
7955 $size = $reg[1];
7956 } elseif (preg_match('/varchar/', $val['type'])) {
7957 $param['options'] = array();
7958 $type = 'varchar';
7959 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7960 $param['options'] = array();
7961 $type = 'stars';
7962 $size = $reg[1];
7963 } else {
7964 $param['options'] = array();
7965 $type = $this->fields[$key]['type'];
7966 }
7967 //var_dump($type); var_dump($param['options']);
7968
7969 // Special case that force options and type ($type can be integer, varchar, ...)
7970 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7971 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7972 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7973 if (empty($this->fields[$key]['multiinput'])) {
7974 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7975 }
7976 }
7977
7978 $label = $this->fields[$key]['label'];
7979 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7980 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7981 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7982 // @phan-suppress-next-line PhanTypeMismatchProperty
7983 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7984 // @phan-suppress-next-line PhanTypeMismatchProperty
7985 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7986 // @phan-suppress-next-line PhanTypeMismatchProperty
7987 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7988 // @phan-suppress-next-line PhanTypeMismatchProperty
7989 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7990 // @phan-suppress-next-line PhanTypeMismatchProperty
7991 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7992
7993 // @phan-suppress-next-line PhanTypeMismatchProperty
7994 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7995 // @phan-suppress-next-line PhanTypeMismatchProperty
7996 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7997 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7998
7999 $objectid = $this->id;
8000
8001 if ($computed) {
8002 if (!preg_match('/^search_/', $keyprefix)) {
8003 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
8004 } else {
8005 return '';
8006 }
8007 }
8008
8009 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
8010 if (empty($morecss) && !empty($val['css'])) {
8011 $morecss = $val['css'];
8012 } elseif (empty($morecss)) {
8013 if ($type == 'date') {
8014 $morecss = 'minwidth100imp';
8015 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
8016 $morecss = 'minwidth200imp';
8017 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8018 $morecss = 'maxwidth75';
8019 } elseif ($type == 'url') {
8020 $morecss = 'minwidth400';
8021 } elseif ($type == 'boolean') {
8022 $morecss = '';
8023 } else {
8024 if (is_numeric($size) && round((float) $size) < 12) {
8025 $morecss = 'minwidth100';
8026 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8027 $morecss = 'minwidth200';
8028 } else {
8029 $morecss = 'minwidth400';
8030 }
8031 }
8032 }
8033
8034 // Add validation state class
8035 if (!empty($validationClass)) {
8036 $morecss .= $validationClass;
8037 }
8038
8039 if (in_array($type, array('date'))) {
8040 $tmp = explode(',', $size);
8041 $newsize = $tmp[0];
8042 $showtime = 0;
8043
8044 // Do not show current date when field not required (see selectDate() method)
8045 if (!$required && $value == '') {
8046 $value = '-1';
8047 }
8048
8049 // TODO Must also support $moreparam
8050 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
8051 } elseif (in_array($type, array('datetime'))) {
8052 $tmp = explode(',', $size);
8053 $newsize = $tmp[0];
8054 $showtime = 1;
8055
8056 // Do not show current date when field not required (see selectDate() method)
8057 if (!$required && $value == '') {
8058 $value = '-1';
8059 }
8060
8061 // TODO Must also support $moreparam
8062 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
8063 } elseif (in_array($type, array('duration'))) {
8064 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
8065 } elseif (in_array($type, array('int', 'integer'))) {
8066 $tmp = explode(',', $size);
8067 $newsize = $tmp[0];
8068 $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' : '').'>';
8069 } elseif (in_array($type, array('real'))) {
8070 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8071 } elseif (preg_match('/varchar/', (string) $type)) {
8072 $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' : '').'>';
8073 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
8074 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8075 } elseif (preg_match('/^text/', (string) $type)) {
8076 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8077 if (!empty($param['options'])) {
8078 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
8079 //var_dump($param['options']);
8080 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
8081 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
8082 $out .= "<script>";
8083 $out .= '
8084 function handlemultiinputdisabling(htmlname){
8085 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
8086 multiinput = $("#"+htmlname+"_multiinput");
8087 multiinput.find("option").each(function(){
8088 tmpval = $("#"+htmlname).val();
8089 tmpvalarray = tmpval.split("\n");
8090 valtotest = $(this).val();
8091 if(tmpvalarray.includes(valtotest)){
8092 $(this).prop("disabled",true);
8093 } else {
8094 if($(this).prop("disabled") == true){
8095 console.log(valtotest)
8096 $(this).prop("disabled", false);
8097 }
8098 }
8099 });
8100 }
8101
8102 $(document).ready(function () {
8103 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
8104 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
8105 tmpvalarray = tmpval.split(",");
8106 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
8107 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8108 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
8109 if(tmpval == ""){
8110 tmpval = valtotest;
8111 } else {
8112 tmpval = tmpval + "\n" + valtotest;
8113 }
8114 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
8115 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8116 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
8117 } else {
8118 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
8119 }
8120 });
8121 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
8122 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8123 });
8124 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8125 })';
8126 $out .= "</script>";
8127 $value = str_replace(',', "\n", $value);
8128 }
8129 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8130 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8131 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
8132 } else {
8133 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8134 }
8135 } elseif (preg_match('/^html/', (string) $type)) {
8136 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8137 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8138 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8139 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8140 } else {
8141 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8142 }
8143 } elseif ($type == 'boolean') {
8144 $checked = '';
8145 if (!empty($value)) {
8146 $checked = ' checked value="1" ';
8147 } else {
8148 $checked = ' value="1" ';
8149 }
8150 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
8151 } elseif ($type == 'price') {
8152 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8153 $value = price($value);
8154 }
8155 $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>';
8156 } elseif ($type == 'stars') {
8157 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8158 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8159 $i = 1;
8160 while ($i <= $size) {
8161 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8162 $i++;
8163 }
8164 $out .= '</div>';
8165 $out .= '<script>
8166 jQuery(function($) { /* commonobject.class.php 1 */
8167 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8168 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8169 container.find(".star").each(function() {
8170 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8171 });
8172 container.find(".star").on("mouseover", function() {
8173 let selectedStar = $(this).data("value");
8174 container.find(".star").each(function() {
8175 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8176 });
8177 });
8178 container.on("mouseout", function() {
8179 container.find(".star").each(function() {
8180 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8181 });
8182 });
8183 container.find(".star").off("click").on("click", function() {
8184 selectedStars = $(this).data("value");
8185 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8186 selectedStars = 0;
8187 }
8188 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8189 container.find(".star").each(function() {
8190 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8191 });
8192 });
8193 });
8194 </script>';
8195 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8196 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8197 $value = price($value);
8198 }
8199 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8200 } elseif ($type == 'select') { // combo list
8201 $out = '';
8202 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8203 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8204 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8205 }
8206
8207 $tmpselect = '';
8208 $nbchoice = 0;
8209
8210 foreach ($param['options'] as $keyb => $valb) {
8211 if ((string) $keyb == '') {
8212 continue;
8213 }
8214 if (strpos($valb, "|") !== false) {
8215 list($valb, $parent) = explode('|', $valb);
8216 }
8217 $nbchoice++;
8218 $tmpselect .= '<option value="'.$keyb.'"';
8219 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8220 if (!empty($parent)) {
8221 $isDependList = 1;
8222 }
8223 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8224 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8225 }
8226
8227 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8228 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8229 $out .= '<option value="0">&nbsp;</option>';
8230 }
8231 $out .= $tmpselect;
8232 $out .= '</select>';
8233 } elseif ($type == 'sellist') {
8234 $out = '';
8235 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8236 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8237 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8238 }
8239
8240 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8241 if (is_array($param['options'])) {
8242 $tmpparamoptions = array_keys($param['options']);
8243 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8244
8245 $InfoFieldList = explode(":", $paramoptions[0], 5);
8246 // 0 : tableName
8247 // 1 : label field name
8248 // 2 : key fields name (if different of rowid)
8249 // optional parameters...
8250 // 3 : key field parent (for dependent lists). How this is used ?
8251 // 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".
8252 // 5 : string category type. This replace the filter.
8253 // 6 : ids categories list separated by comma for category root. This replace the filter.
8254 // 7 : sort field
8255
8256 // If there is filter
8257 if (! empty($InfoFieldList[4])) {
8258 $pos = 0;
8259 $parenthesisopen = 0;
8260 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8261 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8262 $parenthesisopen++;
8263 }
8264 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8265 $parenthesisopen--;
8266 }
8267 $pos++;
8268 }
8269 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8270 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8271 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8272 $InfoFieldList[4] = $tmpbefore;
8273 if ($tmpafter !== '') {
8274 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8275 }
8276 //var_dump($InfoFieldList);
8277
8278 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8279 $reg = array();
8280 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8281 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8282 }
8283
8284 //var_dump($InfoFieldList);
8285 }
8286
8287 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8288
8289 $parentName = '';
8290 $parentField = '';
8291
8292 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8293
8294 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8295 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8296 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8297 } else {
8298 $keyList = $InfoFieldList[2].' as rowid';
8299 }
8300 }
8301 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8302 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8303 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8304 $keyList .= ', main.'.$parentField;
8305 } else {
8306 $keyList .= ', '.$parentField;
8307 }
8308 }
8309
8310 $filter_categorie = false;
8311 if (count($InfoFieldList) > 5) {
8312 if ($InfoFieldList[0] == 'categorie') {
8313 $filter_categorie = true;
8314 }
8315 }
8316
8317 if (!$filter_categorie) {
8318 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8319 if (!empty($fields_label)) {
8320 $keyList .= ', ';
8321 $keyList .= implode(', ', $fields_label);
8322 }
8323
8324 $sqlwhere = '';
8325 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8326 $sql .= " FROM " . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8327
8328 if (!empty($InfoFieldList[4])) {
8329 // can use SELECT request
8330 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8331 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8332 }
8333
8334 // current object id can be use into filter
8335 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8336 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8337 } else {
8338 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8339 }
8340
8341 // We have to join on extrafield table
8342 $errstr = '';
8343 if (strpos($InfoFieldList[4], 'extra') !== false) {
8344 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8345 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8346 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8347 } else {
8348 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8349 }
8350 } else {
8351 $sqlwhere .= ' WHERE 1=1';
8352 }
8353
8354 // Add Usf filter on second line
8355 /*
8356 if ($Usf) {
8357 $errorstr = '';
8358 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8359 if (!$errorstr) {
8360 $sqlwhere .= $sqlusf;
8361 } else {
8362 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8363 }
8364 }
8365 */
8366
8367 // Some tables may have field, some other not. For the moment we disable it.
8368 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8369 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8370 }
8371 $sql .= $sqlwhere;
8372
8373 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8374 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8375 $sql .= " ORDER BY ".$this->db->sanitize($InfoFieldList[7]);
8376 } else {
8377 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8378 }
8379 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8380 // print $sql;
8381
8382 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8383 $resql = $this->db->query($sql);
8384 if ($resql) {
8385 $out .= '<option value="0">&nbsp;</option>';
8386 $num = $this->db->num_rows($resql);
8387 $i = 0;
8388 while ($i < $num) {
8389 $labeltoshow = '';
8390 $obj = $this->db->fetch_object($resql);
8391
8392 // Several field into label (eq table:code|libelle:rowid)
8393 $notrans = false;
8394 $fields_label = explode('|', $InfoFieldList[1]);
8395 if (count($fields_label) > 1) {
8396 $notrans = true;
8397 foreach ($fields_label as $field_toshow) {
8398 $labeltoshow .= $obj->$field_toshow . ' ';
8399 }
8400 } else {
8401 $labeltoshow = $obj->{$InfoFieldList[1]};
8402 }
8403 $labeltoshow = dol_trunc($labeltoshow, 45);
8404
8405 if ($value == $obj->rowid) {
8406 foreach ($fields_label as $field_toshow) {
8407 $translabel = $langs->trans($obj->$field_toshow);
8408 if ($translabel != $obj->$field_toshow) {
8409 $labeltoshow = dol_trunc($translabel) . ' ';
8410 } else {
8411 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8412 }
8413 }
8414 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8415 } else {
8416 if (!$notrans) {
8417 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8418 if ($translabel != $obj->{$InfoFieldList[1]}) {
8419 $labeltoshow = dol_trunc($translabel, 18);
8420 } else {
8421 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8422 }
8423 }
8424 if (empty($labeltoshow)) {
8425 $labeltoshow = '(not defined)';
8426 }
8427 if ($value == $obj->rowid) {
8428 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8429 }
8430
8431 if (!empty($InfoFieldList[3]) && $parentField) {
8432 $parent = $parentName . ':' . $obj->{$parentField};
8433 $isDependList = 1;
8434 }
8435
8436 $out .= '<option value="' . $obj->rowid . '"';
8437 $out .= ($value == $obj->rowid ? ' selected' : '');
8438 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8439 $out .= '>' . $labeltoshow . '</option>';
8440 }
8441
8442 $i++;
8443 }
8444 $this->db->free($resql);
8445 } else {
8446 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8447 }
8448 } else {
8449 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8450 $categcode = $InfoFieldList[5];
8451 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8452 $tmpcategory = new Categorie($this->db);
8453 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8454 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8455 }
8456
8457 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8458 $out .= '<option value="0">&nbsp;</option>';
8459 foreach ($data as $data_key => $data_value) {
8460 $out .= '<option value="' . $data_key . '"';
8461 $out .= ($value == $data_key ? ' selected' : '');
8462 $out .= '>' . $data_value . '</option>';
8463 }
8464 }
8465 }
8466 $out .= '</select>';
8467 } elseif ($type == 'checkbox') {
8468 $value_arr = explode(',', $value);
8469 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8470 } elseif ($type == 'radio') {
8471 $out = '';
8472 foreach ($param['options'] as $keyopt => $valopt) {
8473 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8474 $out .= ' value="'.$keyopt.'"';
8475 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8476 $out .= ($value == $keyopt ? 'checked' : '');
8477 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8478 }
8479 } elseif ($type == 'chkbxlst') {
8480 if (is_array($value)) {
8481 $value_arr = $value;
8482 } else {
8483 $value_arr = explode(',', $value);
8484 }
8485
8486 if (is_array($param['options'])) {
8487 $tmpparamoptions = array_keys($param['options']);
8488 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8489
8490 $InfoFieldList = explode(":", $paramoptions[0], 5);
8491 // 0 : tableName
8492 // 1 : label field name
8493 // 2 : key fields name (if different of rowid)
8494 // optional parameters...
8495 // 3 : key field parent (for dependent lists). How this is used ?
8496 // 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".
8497 // 5 : string category type. This replace the filter.
8498 // 6 : ids categories list separated by comma for category root. This replace the filter.
8499 // 7 : sort field
8500
8501 // If there is a filter
8502 if (! empty($InfoFieldList[4])) {
8503 $pos = 0;
8504 $parenthesisopen = 0;
8505 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8506 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8507 $parenthesisopen++;
8508 }
8509 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8510 $parenthesisopen--;
8511 }
8512 $pos++;
8513 }
8514 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8515 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8516 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8517 $InfoFieldList[4] = $tmpbefore;
8518 if ($tmpafter !== '') {
8519 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8520 }
8521
8522 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8523 $reg = array();
8524 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8525 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8526 }
8527
8528 //var_dump($InfoFieldList);
8529 }
8530
8531 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8532
8533 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8534
8535 $parentName = '';
8536 $parentField = '';
8537
8538 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8539
8540 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8541 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8542 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8543 } else {
8544 $keyList = $InfoFieldList[2].' as rowid';
8545 }
8546 }
8547 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8548 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8549 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8550 $keyList .= ', main.'.$parentField;
8551 } else {
8552 $keyList .= ', '.$parentField;
8553 }
8554 }
8555
8556 $filter_categorie = false;
8557 if (count($InfoFieldList) > 5) {
8558 if ($InfoFieldList[0] == 'categorie') {
8559 $filter_categorie = true;
8560 }
8561 }
8562
8563 // Common filter
8564 if (!$filter_categorie) {
8565 $fields_label = explode('|', $InfoFieldList[1]);
8566 if (is_array($fields_label)) {
8567 $keyList .= ', ';
8568 $keyList .= implode(', ', $fields_label);
8569 }
8570
8571 $sqlwhere = '';
8572 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8573 $sql .= ' FROM ' . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8574
8575 if (!empty($InfoFieldList[4])) {
8576 // can use SELECT request
8577 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8578 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8579 }
8580
8581 // current object id can be use into filter
8582 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8583 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8584 } else {
8585 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8586 }
8587
8588 // We have to join on extrafield table
8589 $errstr = '';
8590 if (strpos($InfoFieldList[4], 'extra') !== false) {
8591 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8592 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8593 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8594 } else {
8595 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8596 }
8597 } else {
8598 $sqlwhere .= ' WHERE 1=1';
8599 }
8600
8601 // Add Usf filter on second line
8602 /*
8603 if ($Usf) {
8604 $errorstr = '';
8605 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8606 if (!$errorstr) {
8607 $sqlwhere .= $sqlusf;
8608 } else {
8609 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8610 }
8611 }
8612 */
8613
8614 // Some tables may have field, some other not. For the moment we disable it.
8615 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8616 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8617 }
8618 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8619 // print $sql;
8620
8621 $sql .= $sqlwhere;
8622
8623 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8624
8625 $resql = $this->db->query($sql);
8626 if ($resql) {
8627 $num = $this->db->num_rows($resql);
8628 $i = 0;
8629
8630 $data = array();
8631
8632 while ($i < $num) {
8633 $labeltoshow = '';
8634 $obj = $this->db->fetch_object($resql);
8635
8636 $notrans = false;
8637 // Several field into label (eq table:code|libelle:rowid)
8638 $fields_label = explode('|', $InfoFieldList[1]);
8639 if (count($fields_label) > 1) {
8640 $notrans = true;
8641 foreach ($fields_label as $field_toshow) {
8642 $labeltoshow .= $obj->$field_toshow . ' ';
8643 }
8644 } else {
8645 $labeltoshow = $obj->{$InfoFieldList[1]};
8646 }
8647 $labeltoshow = dol_trunc($labeltoshow, 45);
8648
8649 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8650 foreach ($fields_label as $field_toshow) {
8651 $translabel = $langs->trans($obj->$field_toshow);
8652 if ($translabel != $obj->$field_toshow) {
8653 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8654 } else {
8655 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8656 }
8657 }
8658
8659 $data[$obj->rowid] = $labeltoshow;
8660 } else {
8661 if (!$notrans) {
8662 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8663 if ($translabel != $obj->{$InfoFieldList[1]}) {
8664 $labeltoshow = dol_trunc($translabel, 18);
8665 } else {
8666 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8667 }
8668 }
8669 if (empty($labeltoshow)) {
8670 $labeltoshow = '(not defined)';
8671 }
8672
8673 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8674 $data[$obj->rowid] = $labeltoshow;
8675 }
8676
8677 if (!empty($InfoFieldList[3]) && $parentField) {
8678 $parent = $parentName . ':' . $obj->{$parentField};
8679 $isDependList = 1;
8680 }
8681
8682 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8683 }
8684
8685 $i++;
8686 }
8687 $this->db->free($resql);
8688
8689 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8690 } else {
8691 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8692 }
8693 } else {
8694 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8695 $categcode = $InfoFieldList[5];
8696 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8697 $tmpcategory = new Categorie($this->db);
8698 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8699 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8700 }
8701
8702 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8703 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8704 }
8705 }
8706 } elseif ($type == 'link') {
8707 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8708 // Filter can contains some ':' inside.
8709 $param_list = array_keys($param['options']);
8710 $param_list_array = explode(':', $param_list[0], 4);
8711
8712 $showempty = (($required && $default != '') ? 0 : 1);
8713
8714 if (!preg_match('/search_/', $keyprefix)) {
8715 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8716 // @phan-suppress-next-line PhanTypeMismatchProperty
8717 if (!empty($this->fields[$key]['picto'])) {
8718 $morecss .= ' widthcentpercentminusxx';
8719 } else {
8720 $morecss .= ' widthcentpercentminusx';
8721 }
8722 } else {
8723 // @phan-suppress-next-line PhanTypeMismatchProperty
8724 if (!empty($this->fields[$key]['picto'])) {
8725 $morecss .= ' widthcentpercentminusx';
8726 }
8727 }
8728 }
8729
8730 // $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.
8731
8732 // $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.
8733
8734 $objectfield = $val;
8735
8736 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8737 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8738
8739 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8740 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8741 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8742 list($class, $classfile) = explode(':', $param_list[0]);
8743 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8744 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8745 } else {
8746 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8747 }
8748 $paramforthenewlink = '';
8749 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8750 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8751 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8752 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8753 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8754 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8755 $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>';
8756 }
8757 }
8758 } elseif ($type == 'password') {
8759 // If prefix is 'search_', field is used as a filter, we use a common text field.
8760 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8761 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8762 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8763 } else {
8764 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8765 }
8766 } elseif ($type == 'array') {
8767 $newval = $val;
8768 $newval['type'] = 'varchar(256)';
8769
8770 $out = '';
8771 if (!empty($value)) {
8772 foreach ($value as $option) {
8773 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8774 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8775 }
8776 }
8777 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8778
8779 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8780 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8781
8782 if (!empty($conf->use_javascript_ajax)) {
8783 $out .= '
8784 <script nonce="'.getNonce().'">
8785 $(document).ready(function() {
8786 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8787 $("'.dol_escape_js($newInput).'").insertBefore(this);
8788 });
8789
8790 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8791 $(this).parent().remove();
8792 });
8793 });
8794 </script>';
8795 }
8796 }
8797 if (!empty($hidden)) {
8798 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8799 }
8800
8801 if ($isDependList == 1) {
8802 $out .= $this->getJSListDependancies('_common');
8803 }
8804 /* Add comments
8805 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8806 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8807 */
8808
8809 // Display error message for field
8810 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8811 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8812 }
8813
8814 return $out;
8815 }
8816
8830 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8831 {
8832 global $conf, $langs, $form;
8833
8834 // 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...)
8835
8836 if (!is_object($form)) {
8837 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8838 $form = new Form($this->db);
8839 }
8840
8841 //$label = empty($val['label']) ? '' : $val['label'];
8842 $type = empty($val['type']) ? '' : $val['type'];
8843 $size = empty($val['css']) ? '' : $val['css'];
8844
8845 $reg = array();
8846
8847 // Convert var to be able to share same code than showOutputField of extrafields
8848 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8849 $type = 'varchar'; // convert varchar(xx) int varchar
8850 $size = $reg[1];
8851 } elseif (preg_match('/varchar/', $type)) {
8852 $type = 'varchar'; // convert varchar(xx) int varchar
8853 }
8854 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8855 // @phan-suppress-next-line PhanTypeMismatchProperty
8856 if (empty($this->fields[$key]['multiinput'])) {
8857 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8858 }
8859 }
8860 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8861 $type = 'link';
8862 }
8863
8864 $default = empty($val['default']) ? '' : $val['default'];
8865 $computed = empty($val['computed']) ? '' : $val['computed'];
8866 $unique = empty($val['unique']) ? '' : $val['unique'];
8867 $required = empty($val['required']) ? '' : $val['required'];
8868 $param = array();
8869 $param['options'] = array();
8870
8871 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8872 $param['options'] = $val['arrayofkeyval'];
8873 }
8874 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8875 $type = 'link';
8876 $stringforoptions = $reg[1].':'.$reg[2];
8877 // Special case: Force addition of getnomurlparam1 to -1 for users
8878 if ($reg[1] == 'User') {
8879 $stringforoptions .= ':#getnomurlparam1=-1';
8880 }
8881 $param['options'] = array($stringforoptions => $stringforoptions);
8882 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8883 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8884 $type = 'sellist';
8885 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8886 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8887 $type = 'sellist';
8888 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8889 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8890 $type = 'sellist';
8891 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8892 $param['options'] = array($reg[1] => 'N');
8893 $type = 'chkbxlst';
8894 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8895 $param['options'] = array();
8896 $type = 'stars';
8897 $size = $reg[1];
8898 }
8899
8900 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8901 $list = (empty($val['list']) ? '' : $val['list']);
8902 $help = (empty($val['help']) ? '' : $val['help']);
8903 $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)
8904
8905 if ($hidden) {
8906 return '';
8907 }
8908
8909 // If field is a computed field, value must become result of compute
8910 if ($computed) {
8911 // Make the eval of compute string
8912 //var_dump($computed);
8913 $value = dol_eval((string) $computed, 1, 0, '2');
8914 }
8915
8916 if (empty($morecss)) {
8917 if ($type == 'date') {
8918 $morecss = 'minwidth100imp';
8919 } elseif ($type == 'datetime' || $type == 'timestamp') {
8920 $morecss = 'minwidth200imp';
8921 } elseif (in_array($type, array('int', 'double', 'price'))) {
8922 $morecss = 'maxwidth75';
8923 } elseif ($type == 'url') {
8924 $morecss = 'minwidth400';
8925 } elseif ($type == 'boolean') {
8926 $morecss = '';
8927 } else {
8928 if (is_numeric($size) && round((float) $size) < 12) {
8929 $morecss = 'minwidth100';
8930 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8931 $morecss = 'minwidth200';
8932 } else {
8933 $morecss = 'minwidth400';
8934 }
8935 }
8936 }
8937
8938 // Format output value differently according to properties of field
8939 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8940 // @phan-suppress-next-line PhanTypeMismatchProperty
8941 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.
8942 $value = $this->getNomUrl(1, '', 0, '', 1);
8943 }
8944 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8945 $value = $this->getLibStatut(3);
8946 } elseif ($type == 'date') {
8947 if (!empty($value)) {
8948 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8949 } else {
8950 $value = '';
8951 }
8952 } elseif ($type == 'datetime' || $type == 'timestamp') {
8953 if (!empty($value)) {
8954 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8955 } else {
8956 $value = '';
8957 }
8958 } elseif ($type == 'duration') {
8959 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8960 if (!is_null($value) && $value !== '') {
8961 $value = convertSecondToTime((int) $value, 'allhourmin');
8962 }
8963 } elseif ($type == 'double' || $type == 'real') {
8964 if (!is_null($value) && $value !== '') {
8965 $value = price($value);
8966 }
8967 } elseif ($type == 'boolean') {
8968 $checked = '';
8969 if (!empty($value)) {
8970 $checked = ' checked ';
8971 }
8972 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8973 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8974 } else {
8975 $value = yn($value ? 1 : 0);
8976 }
8977 } elseif ($type == 'mail' || $type == 'email') {
8978 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8979 } elseif ($type == 'url') {
8980 $value = dol_print_url((string) $value, '_blank', 32, 1);
8981 } elseif ($type == 'phone') {
8982 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8983 } elseif ($type == 'ip') {
8984 $value = dol_print_ip((string) $value, 0);
8985 } elseif ($type == 'stars') {
8986 $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 : '').'>';
8987 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8988 $i = 1;
8989 while ($i <= $size) {
8990 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8991 $i++;
8992 }
8993 $value .= '</div>';
8994 $value .= '<script>
8995 $(document).ready(function() { /* commonobject.class.php 2 */
8996 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8997 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8998 container.find(".star").each(function() {
8999 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9000 });
9001 container.find(".star").on("mouseover", function() {
9002 let selectedStar = $(this).data("value");
9003 container.find(".star").each(function() {
9004 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
9005 });
9006 });
9007 container.on("mouseout", function() {
9008 container.find(".star").each(function() {
9009 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9010 });
9011 });
9012 container.find(".star").off("click").on("click", function() {
9013 selectedStars = $(this).data("value");
9014 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
9015 selectedStars = 0;
9016 }
9017 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
9018 container.find(".star").each(function() {
9019 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
9020 });
9021 $.ajax({
9022 url: "ajax/'.$this->element.'.php",
9023 method: "POST",
9024 data: {
9025 objectId: "'.$this->id.'",
9026 field: "'.$keyprefix.$key.$keysuffix.'",
9027 value: selectedStars,
9028 token: "'.newToken().'"
9029 },
9030 success: function(response) {
9031 var res = JSON.parse(response);
9032 console[res.status === "success" ? "log" : "error"](res.message);
9033 },
9034 error: function(xhr, status, error) {
9035 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
9036 }
9037 });
9038 });
9039 });
9040 </script>';
9041 } elseif ($type == 'price') {
9042 if (!is_null($value) && $value !== '') {
9043 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
9044 }
9045 } elseif ($type == 'select') {
9046 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
9047 if (strpos($value, "|") !== false) {
9048 $value = $langs->trans(explode('|', $value)[0]);
9049 } elseif (! is_numeric($value)) {
9050 $value = $langs->trans($value);
9051 }
9052 } elseif ($type == 'sellist') {
9053 $param_list = array_keys($param['options']);
9054 $InfoFieldList = explode(":", $param_list[0]);
9055
9056 $selectkey = "rowid";
9057 $keyList = 'rowid';
9058
9059 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
9060 $selectkey = $InfoFieldList[2];
9061 $keyList = $InfoFieldList[2].' as rowid';
9062 }
9063
9064 $fields_label = explode('|', $InfoFieldList[1]);
9065 if (is_array($fields_label)) {
9066 $keyList .= ', ';
9067 $keyList .= implode(', ', $fields_label);
9068 }
9069
9070 $filter_categorie = false;
9071 if (count($InfoFieldList) > 5) {
9072 if ($InfoFieldList[0] == 'categorie') {
9073 $filter_categorie = true;
9074 }
9075 }
9076
9077 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9078 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9079 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9080 $sql .= ' as main';
9081 }
9082 if ($selectkey == 'rowid' && empty($value)) {
9083 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = 0";
9084 } elseif ($selectkey == 'rowid') {
9085 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = ".((int) $value);
9086 } else {
9087 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = '".$this->db->escape((string) $value)."'";
9088 }
9089
9090 //$sql.= ' AND entity = '.((int) $conf->entity);
9091
9092 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
9093 $resql = $this->db->query($sql);
9094 if ($resql) {
9095 if (!$filter_categorie) {
9096 $value = ''; // value was used, so now we reset it to use it to build final output
9097 $numrows = $this->db->num_rows($resql);
9098 if ($numrows) {
9099 $obj = $this->db->fetch_object($resql);
9100
9101 // Several field into label (eq table:code|libelle:rowid)
9102 $fields_label = explode('|', $InfoFieldList[1]);
9103
9104 if (is_array($fields_label) && count($fields_label) > 1) {
9105 foreach ($fields_label as $field_toshow) {
9106 $translabel = '';
9107 if (!empty($obj->$field_toshow)) {
9108 $translabel = $langs->trans($obj->$field_toshow);
9109 }
9110 if ($translabel != $field_toshow) {
9111 $value .= dol_trunc($translabel, 18) . ' ';
9112 } else {
9113 $value .= $obj->$field_toshow . ' ';
9114 }
9115 }
9116 } else {
9117 $translabel = '';
9118 if (!empty($obj->{$InfoFieldList[1]})) {
9119 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9120 }
9121 if ($translabel != $obj->{$InfoFieldList[1]}) {
9122 $value = dol_trunc($translabel, 18);
9123 } else {
9124 $value = $obj->{$InfoFieldList[1]};
9125 }
9126 }
9127 }
9128 } else {
9129 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9130
9131 $toprint = array();
9132 $obj = $this->db->fetch_object($resql);
9133 $c = new Categorie($this->db);
9134 $c->fetch($obj->rowid);
9135 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9136 foreach ($ways as $way) {
9137 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9138 }
9139 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9140 }
9141 } else {
9142 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9143 }
9144 } elseif ($type == 'radio') {
9145 $value = $param['options'][(string) $value];
9146 } elseif ($type == 'checkbox') {
9147 $value_arr = explode(',', (string) $value);
9148 $value = '';
9149 if (is_array($value_arr) && count($value_arr) > 0) {
9150 $toprint = array();
9151 foreach ($value_arr as $keyval => $valueval) {
9152 if (!empty($valueval)) {
9153 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9154 }
9155 }
9156 if (!empty($toprint)) {
9157 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9158 }
9159 }
9160 } elseif ($type == 'chkbxlst') {
9161 $value_arr = (isset($value) ? explode(',', $value) : array());
9162
9163 $param_list = array_keys($param['options']);
9164 $InfoFieldList = explode(":", $param_list[0]);
9165
9166 $selectkey = "rowid";
9167 $keyList = 'rowid';
9168
9169 if (count($InfoFieldList) >= 3) {
9170 $selectkey = $InfoFieldList[2];
9171 $keyList = $InfoFieldList[2].' as rowid';
9172 }
9173
9174 $fields_label = explode('|', $InfoFieldList[1]);
9175 if (is_array($fields_label)) {
9176 $keyList .= ', ';
9177 $keyList .= implode(', ', $fields_label);
9178 }
9179
9180 $filter_categorie = false;
9181 if (count($InfoFieldList) > 5) {
9182 if ($InfoFieldList[0] == 'categorie') {
9183 $filter_categorie = true;
9184 }
9185 }
9186
9187 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9188 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9189 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9190 $sql .= ' as main';
9191 }
9192 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9193 // $sql.= ' AND entity = '.((int) $conf->entity);
9194
9195 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9196 $resql = $this->db->query($sql);
9197 if ($resql) {
9198 if (!$filter_categorie) {
9199 $value = ''; // value was used, so now we reset it to use it to build final output
9200 $toprint = array();
9201 while ($obj = $this->db->fetch_object($resql)) {
9202 // Several field into label (eq table:code|libelle:rowid)
9203 $fields_label = explode('|', $InfoFieldList[1]);
9204 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9205 if (is_array($fields_label) && count($fields_label) > 1) {
9206 foreach ($fields_label as $field_toshow) {
9207 $translabel = '';
9208 if (!empty($obj->$field_toshow)) {
9209 $translabel = $langs->trans($obj->$field_toshow);
9210 }
9211 if ($translabel != $field_toshow) {
9212 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9213 } else {
9214 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9215 }
9216 }
9217 } else {
9218 $translabel = '';
9219 if (!empty($obj->{$InfoFieldList[1]})) {
9220 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9221 }
9222 if ($translabel != $obj->{$InfoFieldList[1]}) {
9223 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9224 } else {
9225 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9226 }
9227 }
9228 }
9229 }
9230 } else {
9231 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9232
9233 $toprint = array();
9234 while ($obj = $this->db->fetch_object($resql)) {
9235 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9236 $c = new Categorie($this->db);
9237 $c->fetch($obj->rowid);
9238 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9239 foreach ($ways as $way) {
9240 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9241 }
9242 }
9243 }
9244 }
9245 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9246 } else {
9247 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9248 }
9249 } elseif ($type == 'link') {
9250 $out = '';
9251
9252 // only if something to display (perf)
9253 if ($value) {
9254 $param_list = array_keys($param['options']);
9255 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9256 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9257
9258 $InfoFieldList = explode(":", $param_list[0]);
9259
9260 $classname = $InfoFieldList[0];
9261 $classpath = $InfoFieldList[1];
9262
9263 // Set $getnomurlparam1 et getnomurlparam2
9264 $getnomurlparam = 3;
9265 $getnomurlparam2 = '';
9266 $regtmp = array();
9267 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9268 $getnomurlparam = $regtmp[1];
9269 }
9270 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9271 $getnomurlparam2 = $regtmp[1];
9272 }
9273
9274 if (!empty($classpath)) {
9275 dol_include_once($InfoFieldList[1]);
9276
9277 if ($classname && !class_exists($classname)) {
9278 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9279 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9280 $element_prop = getElementProperties($classname);
9281 if ($element_prop) {
9282 $classname = $element_prop['classname'];
9283 }
9284 }
9285
9286
9287 if ($classname && class_exists($classname)) {
9288 $object = new $classname($this->db);
9289 '@phan-var-force CommonObject $object';
9290 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9291 '@phan-var-force Product $object';
9292 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9293 } else {
9294 $result = $object->fetch($value);
9295 }
9296 if ($result > 0) {
9297 if ($object->element === 'product') {
9298 '@phan-var-force Product $object';
9299 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9300 if (isset($val['get_name_url_params'])) {
9301 $get_name_url_params = explode(':', $val['get_name_url_params']);
9302 if (!empty($get_name_url_params)) {
9303 $param_num_max = count($get_name_url_param_arr) - 1;
9304 foreach ($get_name_url_params as $param_num => $param_value) {
9305 if ($param_num > $param_num_max) {
9306 break;
9307 }
9308 $get_name_url_param_arr[$param_num] = $param_value;
9309 }
9310 }
9311 }
9312
9316 $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]);
9317 } else {
9318 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9319 }
9320 } else {
9321 $value = '';
9322 }
9323 }
9324 } else {
9325 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9326 return 'Error bad setup of extrafield';
9327 }
9328 } else {
9329 $value = '';
9330 }
9331 } elseif ($type == 'password') {
9332 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9333 //$value = preg_replace('/./i', '*', $value);
9334 } elseif ($type == 'array') {
9335 if (is_array($value)) {
9336 $value = implode('<br>', $value);
9337 } else {
9338 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9339 return 'Error unexpected result from code evaluation';
9340 }
9341 } else { // text|html|varchar
9342 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9343 $value = str_replace(',', "\n", $value);
9344 }
9345 $value = dol_htmlentitiesbr((string) $value);
9346 }
9347
9348 //print $type.'-'.$size.'-'.$value;
9349 $out = $value;
9350
9351 return is_null($out) ? '' : $out;
9352 }
9353
9360 public function clearFieldError($fieldKey)
9361 {
9362 $this->error = '';
9363 unset($this->validateFieldsErrors[$fieldKey]);
9364 }
9365
9373 public function setFieldError($fieldKey, $msg = '')
9374 {
9375 global $langs;
9376 if (empty($msg)) {
9377 $msg = $langs->trans("UnknownError");
9378 }
9379
9380 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9381 }
9382
9389 public function getFieldError($fieldKey)
9390 {
9391 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9392 return $this->validateFieldsErrors[$fieldKey];
9393 }
9394 return '';
9395 }
9396
9405 public function validateField($fields, $fieldKey, $fieldValue)
9406 {
9407 global $langs;
9408
9409 if (!class_exists('Validate')) {
9410 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9411 }
9412
9413 $this->clearFieldError($fieldKey);
9414
9415 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9416 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9417 return false;
9418 }
9419
9420 $val = $fields[$fieldKey];
9421
9422 $param = array();
9423 $param['options'] = array();
9424 $type = $val['type'];
9425
9426 $required = false;
9427 if (isset($val['notnull']) && $val['notnull'] === 1) {
9428 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9429 $required = true;
9430 }
9431
9432 $maxSize = 0;
9433 $minSize = 0;
9434
9435 //
9436 // PREPARE Elements
9437 //
9438 $reg = array();
9439
9440 // Convert var to be able to share same code than showOutputField of extrafields
9441 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9442 $type = 'varchar'; // convert varchar(xx) int varchar
9443 $maxSize = (int) $reg[1];
9444 } elseif (preg_match('/varchar/', $type)) {
9445 $type = 'varchar'; // convert varchar(xx) int varchar
9446 }
9447
9448 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9449 $type = 'select';
9450 }
9451
9452 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9453 $type = 'link';
9454 }
9455
9456 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9457 $param['options'] = $val['arrayofkeyval'];
9458 }
9459
9460 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9461 $type = 'link';
9462 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9463 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9464 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9465 $type = 'sellist';
9466 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9467 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9468 $type = 'sellist';
9469 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9470 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9471 $type = 'sellist';
9472 }
9473
9474 //
9475 // TEST Value
9476 //
9477
9478 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9479 $validate = new Validate($this->db, $langs);
9480
9481
9482 // little trick : to perform tests with good performances sort tests by quick to low
9483
9484 //
9485 // COMMON TESTS
9486 //
9487
9488 // Required test and empty value
9489 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9490 $this->setFieldError($fieldKey, $validate->error);
9491 return false;
9492 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9493 // if no value sent and the field is not mandatory, no need to perform tests
9494 return true;
9495 }
9496
9497 // MAX Size test
9498 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9499 $this->setFieldError($fieldKey, $validate->error);
9500 return false;
9501 }
9502
9503 // MIN Size test
9504 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9505 $this->setFieldError($fieldKey, $validate->error);
9506 return false;
9507 }
9508
9509 //
9510 // TESTS for TYPE
9511 //
9512
9513 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9514 if (!$validate->isTimestamp($fieldValue)) {
9515 $this->setFieldError($fieldKey, $validate->error);
9516 return false;
9517 } else {
9518 return true;
9519 }
9520 } elseif ($type == 'duration') {
9521 if (!$validate->isDuration($fieldValue)) {
9522 $this->setFieldError($fieldKey, $validate->error);
9523 return false;
9524 } else {
9525 return true;
9526 }
9527 } elseif (in_array($type, array('double', 'real', 'price'))) {
9528 // is numeric
9529 if (!$validate->isNumeric($fieldValue)) {
9530 $this->setFieldError($fieldKey, $validate->error);
9531 return false;
9532 } else {
9533 return true;
9534 }
9535 } elseif ($type == 'boolean') {
9536 if (!$validate->isBool($fieldValue)) {
9537 $this->setFieldError($fieldKey, $validate->error);
9538 return false;
9539 } else {
9540 return true;
9541 }
9542 } elseif ($type == 'mail' || $type == 'email') {
9543 if (!$validate->isEmail($fieldValue)) {
9544 $this->setFieldError($fieldKey, $validate->error);
9545 return false;
9546 }
9547 } elseif ($type == 'url') {
9548 if (!$validate->isUrl($fieldValue)) {
9549 $this->setFieldError($fieldKey, $validate->error);
9550 return false;
9551 } else {
9552 return true;
9553 }
9554 } elseif ($type == 'phone') {
9555 if (!$validate->isPhone($fieldValue)) {
9556 $this->setFieldError($fieldKey, $validate->error);
9557 return false;
9558 } else {
9559 return true;
9560 }
9561 } elseif ($type == 'select' || $type == 'radio') {
9562 if (!isset($param['options'][$fieldValue])) {
9563 $this->error = $langs->trans('RequireValidValue');
9564 return false;
9565 } else {
9566 return true;
9567 }
9568 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9569 $param_list = array_keys($param['options']);
9570 $InfoFieldList = explode(":", $param_list[0]);
9571 $value_arr = explode(',', $fieldValue);
9572
9573 $selectkey = "rowid";
9574 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9575 $selectkey = $InfoFieldList[2];
9576 }
9577
9578 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9579 $this->setFieldError($fieldKey, $validate->error);
9580 return false;
9581 } else {
9582 return true;
9583 }
9584 } elseif ($type == 'link') {
9585 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9586 $InfoFieldList = explode(":", $param_list[0]);
9587 $classname = $InfoFieldList[0];
9588 $classpath = $InfoFieldList[1];
9589 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9590 $lastIsFetchableError = $validate->error;
9591
9592 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9593 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9594 return true;
9595 }
9596
9597 $this->setFieldError($fieldKey, $lastIsFetchableError);
9598 return false;
9599 } else {
9600 return true;
9601 }
9602 }
9603
9604 // if no test failed all is ok
9605 return true;
9606 }
9607
9621 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9622 {
9623 global $db, $conf, $langs, $action, $form, $hookmanager;
9624 global $objectoffield;
9625
9626 if (!is_object($form)) {
9627 $form = new Form($db);
9628 }
9629 if (!is_object($extrafields)) {
9630 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9631 return 'Bad parameter extrafields for showOptionals';
9632 }
9633 if (!is_array($extrafields->attributes[$this->table_element])) {
9634 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9635 }
9636
9637 // Ensure $objectoffield is available for dol_eval visibility formulas.
9638 $objectoffield = $this;
9639
9640 $out = '';
9641
9642 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9643 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9644 $hookResPrint = $hookmanager->resPrint;
9645
9646 if (empty($reshook)) {
9647 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) {
9648 $out .= "\n";
9649 $out .= '<!-- commonobject:showOptionals --> ';
9650 $out .= "\n";
9651
9652 $nbofextrafieldsshown = 0;
9653 $e = 0; // var to manage the modulo (odd/even)
9654
9655 $lastseparatorkeyfound = '';
9656 $extrafields_collapse_num = '';
9657 $extrafields_collapse_num_old = '';
9658 $i = 0;
9659
9660 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9661 $i++;
9662
9663 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9664 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9665 continue;
9666 }
9667
9668 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9669 $enabled = 1;
9670 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9671 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9672 }
9673 if (empty($enabled)) {
9674 continue;
9675 }
9676
9677 $visibility = 1;
9678 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9679 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9680 }
9681
9682 $perms = 1;
9683 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9684 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9685 }
9686
9687 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9688 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9689 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9690 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9691 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9692 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9693 $ef_name = 'options_' . $key;
9694 $ef_value = $this->array_options[$ef_name] ?? '';
9695 $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).
9696 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9697 } elseif ($mode == 'view' && empty($visibility)) {
9698 continue;
9699 }
9700 if (empty($perms)) {
9701 continue;
9702 }
9703
9704 // Load language if required
9705 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9706 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9707 }
9708
9709 $colspan = 0;
9710 $value = null;
9711 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9712 if (array_key_exists('cols', $params)) {
9713 $colspan = $params['cols'];
9714 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9715 $reg = array();
9716 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9717 $colspan = $reg[1];
9718 } else {
9719 $colspan = $params['colspan'];
9720 }
9721 }
9722 }
9723 $colspan = intval($colspan);
9724
9725 switch ($mode) {
9726 case "view":
9727 $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
9728 break;
9729 case "create":
9730 case "edit":
9731 // We get the value of property found with GETPOST so it takes into account:
9732 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9733 $check = 'alphanohtml';
9734 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9735 $check = 'restricthtml';
9736 }
9737 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9738 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9739 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9740 if (is_array($getposttemp)) {
9741 // $getposttemp is an array but following code expects a comma separated string
9742 $value = implode(",", $getposttemp);
9743 } else {
9744 $value = $getposttemp;
9745 }
9746 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9747 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9748 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('varchar', 'char'))) {
9749 $value = (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] !== '') ? $this->array_options["options_".$key] : '';
9750 } else {
9751 $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.
9752 }
9753 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9754 break;
9755 }
9756
9757 $nbofextrafieldsshown++;
9758
9759 // Output value of the current field
9760 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9761 $extrafields_collapse_num = $key;
9762 /*
9763 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9764 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9765 $extrafield_param_list = array_keys($extrafield_param['options']);
9766
9767 if (count($extrafield_param_list) > 0) {
9768 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9769
9770 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9771 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9772 $extrafields_collapse_num = $key;
9773 }
9774 }
9775 }
9776 */
9777
9778 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9779 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9780
9781 $lastseparatorkeyfound = $key;
9782 } else {
9783 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9784
9785 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9786 $csstyle = '';
9787 if (is_array($params) && count($params) > 0) {
9788 if (array_key_exists('class', $params)) {
9789 $class .= $params['class'].' ';
9790 }
9791 if (array_key_exists('style', $params)) {
9792 $csstyle = $params['style'];
9793 }
9794 }
9795
9796 // add html5 elements
9797 $domData = ' data-element="extrafield"';
9798 $domData .= ' data-targetelement="'.$this->element.'"';
9799 $domData .= ' data-targetid="'.$this->id.'"';
9800
9801 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9802 if ($display_type == 'card') {
9803 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9804 $colspan = 0;
9805 }
9806
9807 if ($action == 'selectlines') {
9808 $colspan++;
9809 }
9810 }
9811
9812 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9813 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9814 $force_values_on_change_company = (
9815 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9816 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9817 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9818 );
9819 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9820 $value = $this->array_options['options_'.$key] ?? $value;
9821 }
9822
9823 // Convert date into timestamp format (value in memory must be a timestamp)
9824 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9825 $datenotinstring = null;
9826 if (array_key_exists('options_'.$key, $this->array_options)) {
9827 $datenotinstring = $this->array_options['options_'.$key];
9828 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9829 $datenotinstring = $this->db->jdate($datenotinstring);
9830 }
9831 }
9832 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9833 $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;
9834 }
9835 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9836 $datenotinstring = null;
9837 if (array_key_exists('options_'.$key, $this->array_options)) {
9838 $datenotinstring = $this->array_options['options_'.$key];
9839 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9840 $datenotinstring = $this->db->jdate($datenotinstring);
9841 }
9842 }
9843 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9844 $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;
9845 }
9846 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9847 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9848 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9849 $value = price2num($value);
9850 } elseif (isset($this->array_options['options_'.$key])) {
9851 $value = $this->array_options['options_'.$key];
9852 }
9853 }
9854
9855 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9856 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9857 if ($action == 'create' || $mode == 'create') {
9858 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9859 }
9860 }
9861
9862 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9863 if ($action == 'create') {
9864 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9865 }
9866 }
9867
9868
9869 $labeltoshow = $langs->trans($label);
9870 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9871 if ($display_type == 'card') {
9872 $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.' >';
9873 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9874 $out .= '<td></td>';
9875 }
9876 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9877 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9878 $out .= ' tdtop';
9879 }
9880 } elseif ($display_type == 'line') {
9881 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9882 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9883 }
9884 //$out .= "titlefield";
9885 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9886 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9887 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9888 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9889 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9890 $out .= ' fieldrequired';
9891 }
9892 }
9893 $out .= '">';
9894 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9895 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9896 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9897 } else {
9898 $out .= $labeltoshow;
9899 }
9900 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9901 $out .= '&nbsp;<span style="color: red">*</span>';
9902 }
9903 } else {
9904 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9905 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9906 } else {
9907 $out .= $labeltoshow;
9908 }
9909 }
9910
9911 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9912
9913 // Second column
9914 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9915 if ($display_type == 'card') {
9916 // 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
9917 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9918 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9919 $out .= '>';
9920 } elseif ($display_type == 'line') {
9921 $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].'">';
9922 }
9923
9924 switch ($mode) {
9925 case "view":
9926 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9927 break;
9928 case "create":
9929 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9930 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9931 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9932 }
9933 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9934 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9935 break;
9936 case "edit":
9937 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9938 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9939 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9940 }
9941 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9942 break;
9943 }
9944
9945 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9946 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9947 $e++;
9948 }
9949 }
9950 $out .= "\n";
9951 // Add code to manage list depending on others
9952 if (!empty($conf->use_javascript_ajax)) {
9953 $out .= $this->getJSListDependancies();
9954 }
9955
9956 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9957
9958 if (empty($nbofextrafieldsshown)) {
9959 $out = '';
9960 }
9961 }
9962 }
9963
9964 $out .= $hookResPrint;
9965
9966 return $out;
9967 }
9968
9973 public function getJSListDependancies($type = '_extra')
9974 {
9975 $out = '
9976 <script nonce="'.getNonce().'">
9977 jQuery(document).ready(function() {
9978 function showOptions'.$type.'(child_list, parent_list, orig_select)
9979 {
9980 var val = $("select[name=\""+parent_list+"\"]").val();
9981 var parentVal = parent_list + ":" + val;
9982 if(typeof val == "string"){
9983 if(val != "") {
9984 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9985 $("select[name=\""+child_list+"\"] option[parent]").remove();
9986 $("select[name=\""+child_list+"\"]").append(options);
9987 } else {
9988 var options = orig_select.find("option[parent]").clone();
9989 $("select[name=\""+child_list+"\"] option[parent]").remove();
9990 $("select[name=\""+child_list+"\"]").append(options);
9991 }
9992 } else if(val > 0) {
9993 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9994 $("select[name=\""+child_list+"\"] option[parent]").remove();
9995 $("select[name=\""+child_list+"\"]").append(options);
9996 } else {
9997 var options = orig_select.find("option[parent]").clone();
9998 $("select[name=\""+child_list+"\"] option[parent]").remove();
9999 $("select[name=\""+child_list+"\"]").append(options);
10000 }
10001 }
10002 function setListDependencies'.$type.'() {
10003 jQuery("select option[parent]").parent().each(function() {
10004 var orig_select = {};
10005 var child_list = $(this).attr("name");
10006 orig_select[child_list] = $(this).clone();
10007 var parent = $(this).find("option[parent]:first").attr("parent");
10008 var infos = parent.split(":");
10009 var parent_list = infos[0];
10010
10011 //Hide daughters lists
10012 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
10013 $("#"+child_list).hide();
10014 //Show mother lists
10015 } else if ($("#"+parent_list).val() != 0){
10016 $("#"+parent_list).show();
10017 }
10018 //Show the child list if the parent list value is selected
10019 $("select[name=\""+parent_list+"\"]").click(function() {
10020 if ($(this).val() != 0){
10021 $("#"+child_list).show()
10022 }
10023 });
10024
10025 //When we change parent list
10026 $("select[name=\""+parent_list+"\"]").change(function() {
10027 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
10028 //Select the value 0 on child list after a change on the parent list
10029 $("#"+child_list).val(0).trigger("change");
10030 //Hide child lists if the parent value is set to 0
10031 if ($(this).val() == 0){
10032 $("#"+child_list).hide();
10033 }
10034 });
10035 });
10036 }
10037
10038 setListDependencies'.$type.'();
10039 });
10040 </script>'."\n";
10041 return $out;
10042 }
10043
10049 public function getRights()
10050 {
10051 global $user;
10052
10053 $module = empty($this->module) ? '' : $this->module;
10054 $element = $this->element;
10055
10056 if ($element == 'facturerec') {
10057 $element = 'facture';
10058 } elseif ($element == 'invoice_supplier_rec') {
10059 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
10060 } elseif ($module && $user->hasRight($module, $element)) {
10061 // for modules built with ModuleBuilder
10062 return $user->hasRight($module, $element);
10063 }
10064
10065 return isset($user->rights->$element) ? $user->rights->$element : null;
10066 }
10067
10080 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10081 {
10082 global $hookmanager;
10083
10084 $parameters = array(
10085 'origin_id' => $origin_id,
10086 'dest_id' => $dest_id,
10087 'tables' => $tables,
10088 );
10089 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
10090 if ($reshook) {
10091 return true; // replacement code
10092 } elseif ($reshook < 0) {
10093 return $ignoreerrors === 1; // failure
10094 } // reshook = 0 => execute normal code
10095
10096 foreach ($tables as $table) {
10097 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
10098
10099 if (!$dbs->query($sql)) {
10100 if ($ignoreerrors) {
10101 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.
10102 }
10103 //$this->errors = $db->lasterror();
10104 return false;
10105 }
10106 }
10107
10108 return true;
10109 }
10110
10123 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10124 {
10125 foreach ($tables as $table) {
10126 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
10127
10128 if (!$dbs->query($sql)) {
10129 if ($ignoreerrors) {
10130 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.
10131 }
10132 //$this->errors = $db->lasterror();
10133 return false;
10134 }
10135 }
10136
10137 return true;
10138 }
10139
10154 public static function commonReplaceContact(DoliDB $dbs, $origin_id, $dest_id, array $tables, $fieldname = 'fk_socpeople', $ignoreerrors = 0)
10155 {
10156 global $hookmanager;
10157
10158 // Table and column names are concatenated into the SQL, so they are validated as a defence in
10159 // depth: this method is public and static, hence callable from any module.
10160 if (!preg_match('/^[a-z0-9_]+$/', $fieldname)) {
10161 dol_syslog(__METHOD__.' Refused an invalid column name: '.$fieldname, LOG_ERR);
10162 return false;
10163 }
10164
10165 $parameters = array(
10166 'origin_id' => $origin_id,
10167 'dest_id' => $dest_id,
10168 'tables' => $tables,
10169 'fieldname' => $fieldname,
10170 );
10171 $reshook = $hookmanager->executeHooks('commonReplaceContact', $parameters);
10172 if ($reshook > 0) {
10173 return true; // replacement code
10174 } elseif ($reshook < 0) {
10175 return $ignoreerrors === 1; // failure
10176 } // reshook = 0 => execute normal code
10177
10178 foreach ($tables as $table) {
10179 if (!preg_match('/^[a-z0-9_]+$/', $table)) {
10180 dol_syslog(__METHOD__.' Refused an invalid table name: '.$table, LOG_ERR);
10181 return false;
10182 }
10183
10184 $sanitizedtable = $dbs->sanitize($table);
10185 $sanitizedfieldname = $dbs->sanitize($fieldname);
10186
10187 $sql = "UPDATE ".$dbs->prefix().$sanitizedtable;
10188 $sql .= " SET ".$sanitizedfieldname." = ".((int) $dest_id);
10189 $sql .= " WHERE ".$sanitizedfieldname." = ".((int) $origin_id);
10190
10191 if (!$dbs->query($sql)) {
10192 if ($ignoreerrors) {
10193 return true;
10194 }
10195 return false;
10196 }
10197 }
10198
10199 return true;
10200 }
10201
10214 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
10215 {
10216 global $conf;
10217
10218 $buyPrice = 0;
10219
10220 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10221 // When ForceBuyingPriceIfNull is set
10222 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10223 } else {
10224 // Get cost price for margin calculation
10225 if (!empty($fk_product) && $fk_product > 0) {
10226 $result = 0;
10227 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10228 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10229 $product = new Product($this->db);
10230 $result = $product->fetch($fk_product);
10231 if ($result <= 0) {
10232 $this->errors[] = 'ErrorProductIdDoesNotExists';
10233 return -1;
10234 }
10235 if ($product->cost_price > 0) {
10236 $buyPrice = $product->cost_price;
10237 } elseif ($product->pmp > 0) {
10238 $buyPrice = $product->pmp;
10239 }
10240 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10241 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10242 $product = new Product($this->db);
10243 $result = $product->fetch($fk_product);
10244 if ($result <= 0) {
10245 $this->errors[] = 'ErrorProductIdDoesNotExists';
10246 return -1;
10247 }
10248 if ($product->pmp > 0) {
10249 $buyPrice = $product->pmp;
10250 }
10251 }
10252
10253 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10254 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10255 $productFournisseur = new ProductFournisseur($this->db);
10256 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
10257 $buyPrice = $productFournisseur->fourn_unitprice;
10258 } elseif ($result < 0) {
10259 $this->errors[] = $productFournisseur->error;
10260 return -2;
10261 }
10262 }
10263 }
10264 }
10265
10266 return (float) $buyPrice;
10267 }
10268
10276 public function getDataToShowPhoto($modulepart, $imagesize)
10277 {
10278 // See getDataToShowPhoto() implemented by Product for example.
10279 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10280 }
10281
10282
10283 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10303 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')
10304 {
10305 // phpcs:enable
10306 global $user, $langs;
10307
10308 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10309 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10310 include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
10311
10312 $sortfield = 'position_name';
10313 $sortorder = 'asc';
10314
10315 $dir = $sdir.'/';
10316 $pdir = '/';
10317
10318 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10319 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10320
10321 // For backward compatibility
10322 if ($modulepart == 'product') {
10323 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10324 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10325 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10326 }
10327 }
10328 if ($modulepart == 'category') {
10329 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10330 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10331 }
10332
10333 // Defined relative dir to DOL_DATA_ROOT
10334 $relativedir = '';
10335 if ($dir) {
10336 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10337 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10338 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10339 }
10340
10341 $dirthumb = $dir.'thumbs/';
10342 $pdirthumb = $pdir.'thumbs/';
10343
10344 $return = '<!-- Photo -->'."\n";
10345 $nbphoto = 0;
10346
10347 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10348
10349 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10350 {
10351 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10352 $filearray=array_merge($filearray, $filearrayold);
10353 }*/
10354
10355 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10356 '@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';
10357
10358 $useLinkPathPhoto = false;
10359
10360 if (getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO')) {
10361 $link = new Link($this->db);
10362 $links = array();
10363 $link->fetchAll($links, 'product', $this->id);
10364
10365 if (count($links) > 0) {
10366 $useLinkPathPhoto = true;
10367 }
10368
10369 if ($useLinkPathPhoto) {
10370 // Start table or div based on nbbyrow
10371 if ($nbbyrow > 0) {
10372 $return .= '<table class="valigntop center centpercent" style="border:0; padding:2px; border-spacing:2px; border-collapse: separate;">';
10373 }
10374
10375 $i = 0;
10376 foreach ($links as $link) {
10377 if (!empty($link->url) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $link->url)) {
10378 $url = dol_escape_htmltag($link->url);
10379 $i++;
10380 $nbphoto++;
10381
10382 if ($nbbyrow > 0 && ($i % $nbbyrow == 1)) {
10383 $return .= '<tr class="center valignmiddle">';
10384 }
10385
10386 if ($nbbyrow > 0) {
10387 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%" class="photo">';
10388 } else {
10389 $return .= '<div class="inline-block">';
10390 }
10391
10392 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.$url.'" title="External image">';
10393
10394 if ($nbbyrow > 0) {
10395 $return .= '</td>';
10396 if ($i % $nbbyrow == 0) {
10397 $return .= '</tr>';
10398 }
10399 } else {
10400 $return .= '</div>';
10401 }
10402
10403 if ($nbmax && $nbphoto >= $nbmax) {
10404 break;
10405 }
10406 }
10407 }
10408
10409 if ($nbbyrow > 0) {
10410 while ($i % $nbbyrow) {
10411 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10412 $i++;
10413 }
10414 if ($nbphoto) {
10415 $return .= '</table>';
10416 }
10417 }
10418 }
10419 }
10420
10421 if (count($filearray)) {
10422 if ($sortfield && $sortorder) {
10423 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10424 }
10425
10426 foreach ($filearray as $key => $val) {
10427 $photo = '';
10428 $file = $val['name'];
10429
10430 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10431 if (image_format_supported($file) >= 0) {
10432 $nbphoto++;
10433 $photo = $file;
10434 $viewfilename = $file;
10435
10436 if ($size == 1 || $size == 'small') { // Format vignette
10437 // Find name of thumb file
10438 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10439 if (!dol_is_file($dirthumb.$photo_vignette)) {
10440 // The thumb does not exists, so we will use the original file
10441 $dirthumb = $dir;
10442 $pdirthumb = $pdir;
10443 $photo_vignette = basename($file);
10444 }
10445
10446 // Get filesize of original file
10447 $imgarray = dol_getImageSize($dir.$photo);
10448
10449 if ($nbbyrow > 0) {
10450 if ($nbphoto == 1) {
10451 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10452 }
10453
10454 if ($nbphoto % $nbbyrow == 1) {
10455 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10456 }
10457 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10458 } elseif ($nbbyrow < 0) {
10459 $return .= '<div class="inline-block">'."\n";
10460 }
10461
10462 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10463 if (empty($nolink)) {
10464 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10465 if ($urladvanced) {
10466 $return .= '<a href="'.$urladvanced.'">';
10467 } else {
10468 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10469 }
10470 }
10471
10472 // Show image (width height=$maxHeight)
10473 // If thumb file available and image source is too large, we use the thumb, otherwise we use the original photo
10474 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10475 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10476 if ($overwritetitle) {
10477 if (is_numeric($overwritetitle)) {
10478 $alt = '';
10479 } else {
10480 $alt = $overwritetitle;
10481 }
10482 }
10483 if (empty($cache) && !empty($val['label'])) {
10484 // label is md5 of file
10485 // use it in url to say we want to cache this version of the file
10486 $cache = $val['label'];
10487 }
10488 if ($usesharelink) {
10489 if (array_key_exists('share', $val) && $val['share']) {
10490 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10491 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10492 $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).'">';
10493 } else {
10494 $return .= '<!-- Show original file -->';
10495 $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).'">';
10496 }
10497 } else {
10498 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10499 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10500 }
10501 } else {
10502 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10503 $return .= '<!-- Show thumb -->';
10504 $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).'">';
10505 } else {
10506 $return .= '<!-- Show original file -->';
10507 $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).'">';
10508 }
10509 }
10510
10511 if (empty($nolink)) {
10512 $return .= '</a>';
10513 }
10514
10515 if ($showfilename) {
10516 $return .= '<br>'.$viewfilename;
10517 }
10518 if ($showaction) {
10519 $return .= '<br>';
10520 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10521 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10522 $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>';
10523 }
10524 // Special case for product
10525 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10526 // Link to resize
10527 $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; ';
10528
10529 // Link to delete
10530 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10531 $return .= img_delete().'</a>';
10532 }
10533 }
10534 $return .= "\n";
10535
10536 if ($nbbyrow > 0) {
10537 $return .= '</td>';
10538 if (($nbphoto % $nbbyrow) == 0) {
10539 $return .= '</tr>';
10540 }
10541 } elseif ($nbbyrow < 0) {
10542 $return .= '</div>'."\n";
10543 }
10544 }
10545
10546 if (empty($size)) { // Format origine
10547 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10548
10549 if ($showfilename) {
10550 $return .= '<br>'.$viewfilename;
10551 }
10552 if ($showaction) {
10553 // Special case for product
10554 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10555 // Link to resize
10556 $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; ';
10557
10558 // Link to delete
10559 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10560 $return .= img_delete().'</a>';
10561 }
10562 }
10563 }
10564
10565 // On continue ou on arrete de boucler ?
10566 if ($nbmax && $nbphoto >= $nbmax) {
10567 break;
10568 }
10569 }
10570 }
10571
10572 if ($size == 1 || $size == 'small') {
10573 if ($nbbyrow > 0) {
10574 // Ferme tableau
10575 while ($nbphoto % $nbbyrow) {
10576 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10577 $nbphoto++;
10578 }
10579
10580 if ($nbphoto) {
10581 $return .= '</table>';
10582 }
10583 }
10584 }
10585 }
10586
10587 $this->nbphoto = $nbphoto;
10588
10589 return $return;
10590 }
10591
10592
10599 protected function isArray($info)
10600 {
10601 if (is_array($info)) {
10602 if (isset($info['type']) && $info['type'] == 'array') {
10603 return true;
10604 } else {
10605 return false;
10606 }
10607 }
10608 return false;
10609 }
10610
10617 public function isDate($info)
10618 {
10619 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10620 return true;
10621 }
10622 return false;
10623 }
10624
10631 public function isDuration($info)
10632 {
10633 if (is_array($info)) {
10634 if (isset($info['type']) && ($info['type'] == 'duration')) {
10635 return true;
10636 } else {
10637 return false;
10638 }
10639 } else {
10640 return false;
10641 }
10642 }
10643
10650 public function isInt($info)
10651 {
10652 if (is_array($info)) {
10653 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10654 return true;
10655 } else {
10656 return false;
10657 }
10658 } else {
10659 return false;
10660 }
10661 }
10662
10669 public function isFloat($info)
10670 {
10671 if (is_array($info)) {
10672 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10673 return true;
10674 } else {
10675 return false;
10676 }
10677 }
10678 return false;
10679 }
10680
10687 public function isText($info)
10688 {
10689 if (is_array($info)) {
10690 if (isset($info['type']) && $info['type'] == 'text') {
10691 return true;
10692 } else {
10693 return false;
10694 }
10695 }
10696 return false;
10697 }
10698
10705 protected function canBeNull($info)
10706 {
10707 if (is_array($info)) {
10708 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10709 return true;
10710 } else {
10711 return false;
10712 }
10713 }
10714 return true;
10715 }
10716
10723 protected function isForcedToNullIfZero($info)
10724 {
10725 if (is_array($info)) {
10726 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10727 return true;
10728 } else {
10729 return false;
10730 }
10731 }
10732 return false;
10733 }
10734
10741 protected function isIndex($info)
10742 {
10743 if (is_array($info)) {
10744 if (array_key_exists('index', $info) && $info['index'] == true) {
10745 return true;
10746 } else {
10747 return false;
10748 }
10749 }
10750 return false;
10751 }
10752
10753
10763 protected function setSaveQuery()
10764 {
10765 global $conf;
10766
10767 $queryarray = array();
10768 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10769 // Depending on field type ('datetime', ...)
10770 if ($this->isDate($info)) {
10771 if (empty($this->{$field})) {
10772 $queryarray[$field] = null;
10773 } else {
10774 $queryarray[$field] = $this->db->idate($this->{$field});
10775 }
10776 } elseif ($this->isDuration($info)) {
10777 // $this->{$field} may be null, '', 0, '0', 123, '123'
10778 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10779 if (!isset($this->{$field})) {
10780 if (!empty($info['default'])) {
10781 $queryarray[$field] = $info['default']; // From safe source @phan-suppress-current-line SqlInjection
10782 } else {
10783 $queryarray[$field] = 0;
10784 }
10785 } else {
10786 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10787 }
10788 } else {
10789 $queryarray[$field] = null;
10790 }
10791 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10792 if ($field == 'entity' && is_null($this->{$field})) {
10793 $queryarray[$field] = ((int) $conf->entity);
10794 } else {
10795 // $this->{$field} may be null, '', 0, '0', 123, '123'
10796 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10797 if (!isset($this->{$field})) {
10798 $queryarray[$field] = 0;
10799 } elseif ($this->isInt($info)) {
10800 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10801 } elseif ($this->isFloat($info)) {
10802 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10803 }
10804 } else {
10805 $queryarray[$field] = null;
10806 }
10807 }
10808 } else {
10809 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10810 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10811 $queryarray[$field] = $this->{$field}; // @phan-suppress-current-line SqlInjection
10812 }
10813
10814 if (is_array($info) && array_key_exists('type', $info) && !empty($info['type']) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10815 unset($queryarray[$field]);
10816 }
10817 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10818 $queryarray[$field] = null; // May force 0 to null
10819 }
10820 }
10821
10822 return $queryarray;
10823 }
10824
10831 public function setVarsFromFetchObj(&$obj)
10832 {
10833 global $db;
10834
10835 foreach ($this->fields as $field => $info) {
10836 if ($this->isDate($info)) {
10837 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') {
10838 $this->$field = '';
10839 } else {
10840 $this->$field = $db->jdate($obj->$field);
10841 }
10842 } elseif ($this->isInt($info)) {
10843 if ($field == 'rowid') {
10844 $this->id = (int) $obj->$field;
10845 } else {
10846 if ($this->isForcedToNullIfZero($info)) {
10847 if (empty($obj->$field)) {
10848 $this->$field = null;
10849 } else {
10850 $this->$field = (int) $obj->$field;
10851 }
10852 } else {
10853 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10854 $this->$field = (int) $obj->$field;
10855 } else {
10856 $this->$field = null;
10857 }
10858 }
10859 }
10860 } elseif ($this->isFloat($info)) {
10861 if ($this->isForcedToNullIfZero($info)) {
10862 if (empty($obj->$field)) {
10863 $this->$field = null;
10864 } else {
10865 $this->$field = (float) $obj->$field;
10866 }
10867 } else {
10868 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10869 $this->$field = (float) $obj->$field;
10870 } else {
10871 $this->$field = null;
10872 }
10873 }
10874 } else {
10875 $this->$field = isset($obj->$field) ? $obj->$field : null;
10876 }
10877 }
10878
10879 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10880 if (!isset($this->fields['ref']) && isset($this->id)) {
10881 $this->ref = (string) $this->id;
10882 }
10883 }
10884
10889 public function emtpyObjectVars()
10890 {
10891 foreach ($this->fields as $field => $arr) {
10892 $this->$field = null;
10893 }
10894 }
10895
10903 public function getFieldList($alias = '', $excludefields = array())
10904 {
10905 $keys = array_keys($this->fields);
10906 if (!empty($alias)) {
10907 $keys_with_alias = array();
10908 foreach ($keys as $fieldname) {
10909 if (!empty($excludefields)) {
10910 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10911 continue;
10912 }
10913 }
10914 $keys_with_alias[] = $this->db->sanitize($alias . '.' . $fieldname);
10915 }
10916 return implode(', ', $keys_with_alias);
10917 } else {
10918 return implode(', ', $keys);
10919 }
10920 }
10921
10929 protected function quote($value, $fieldsentry)
10930 {
10931 if (is_null($value)) {
10932 return 'NULL';
10933 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10934 return price2num((string) $value);
10935 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10936 return (int) $value;
10937 } elseif ($fieldsentry['type'] == 'boolean') {
10938 if ($value) {
10939 return 'true';
10940 } else {
10941 return 'false';
10942 }
10943 } else {
10944 return "'".$this->db->escape($value)."'";
10945 }
10946 }
10947
10948
10956 public function createCommon(User $user, $notrigger = 0)
10957 {
10958 global $conf;
10959 //global $langs; // Should be able to work with $langs loaded
10960
10961 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10962
10963 $error = 0;
10964
10965 $now = dol_now();
10966
10967 $fieldvalues = $this->setSaveQuery();
10968
10969 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10970
10971 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10972 $fieldvalues['date_creation'] = $this->db->idate($now);
10973 $this->date_creation = $this->db->idate($now);
10974 }
10975 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10976 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10977 $fieldvalues['fk_user_creat'] = $user->id;
10978 $this->fk_user_creat = $user->id;
10979 }
10980 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10981 $fieldvalues['user_creation_id'] = $user->id;
10982 $this->user_creation_id = $user->id;
10983 }
10984 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10985 // @phan-suppress-next-line PhanUndeclaredProperty
10986 $tmparray = dol_hash($this->pass, '0', 0, 1);
10987 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10988 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10989 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10990 }
10991 }
10992 if (array_key_exists('ref', $fieldvalues)) {
10993 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10994 }
10995 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
10996 $fieldvalues['tms'] = $this->db->idate($now);
10997 }
10998
10999 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
11000
11001 $sanitized_keys = array();
11002 $sanitized_values = array(); // Array to store string forged for SQL syntax
11003 foreach ($fieldvalues as $k => $v) {
11004 $sanitized_keys[$k] = $k; // Safe source @phan-suppress-current-line SqlInjection
11005 $value = $this->fields[$k];
11006 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11007 $sanitized_values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
11008 }
11009
11010 // Clean and check mandatory
11011 foreach ($sanitized_keys as $key) {
11012 if (!isset($this->fields[$key])) {
11013 continue;
11014 }
11015 $key_fields = $this->fields[$key];
11016
11017 // If field is an implicit foreign key field (so type = 'integer:...')
11018 if (preg_match('/^integer:/i', $key_fields['type']) && $sanitized_values[$key] == '-1') {
11019 $sanitized_values[$key] = '';
11020 }
11021 if (!empty($key_fields['foreignkey']) && $sanitized_values[$key] == '-1') {
11022 $sanitized_values[$key] = '';
11023 }
11024
11025 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']))) {
11026 $error++;
11027 global $langs;
11028 if (empty($langs)) {
11029 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
11030 $langs = new Translate('', $conf);
11031 $langs->setDefaultLang();
11032 $langs->load("errors");
11033 }
11034 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
11035 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
11036 }
11037
11038 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
11039 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'])) {
11040 $sanitized_values[$key] = $this->quote($key_fields['default'], $key_fields);
11041 }
11042
11043 // If field is an implicit foreign key field (so type = 'integer:...')
11044 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($sanitized_values[$key])) {
11045 if (isset($key_fields['default'])) {
11046 $sanitized_values[$key] = ((int) $key_fields['default']);
11047 } else {
11048 $sanitized_values[$key] = 'null';
11049 }
11050 }
11051 if (!empty($key_fields['foreignkey']) && empty($sanitized_values[$key])) {
11052 $sanitized_values[$key] = 'null';
11053 }
11054 }
11055
11056 if ($error) {
11057 return -1;
11058 }
11059
11060 $this->db->begin();
11061
11062 if (!$error) {
11063 $sql = "INSERT INTO ".$this->db->prefix().$this->db->sanitize($this->table_element);
11064 $sql .= " (".implode(", ", $sanitized_keys).')';
11065 $sql .= " VALUES (".implode(", ", $sanitized_values).")"; // $sanitized_values can contains 'abc' or 123
11066
11067 $res = $this->db->query($sql);
11068 if (!$res) {
11069 $error++;
11070 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
11071 $this->errors[] = "ErrorRefAlreadyExists";
11072 } else {
11073 $this->errors[] = $this->db->lasterror();
11074 }
11075 }
11076 }
11077
11078 if (!$error) {
11079 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
11080 }
11081
11082 // If we have a field ref with a default value of (PROV)
11083 if (!$error) {
11084 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
11085 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)') {
11086 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11087 $sql .= " SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
11088 $resqlupdate = $this->db->query($sql);
11089
11090 if ($resqlupdate === false) {
11091 $error++;
11092 $this->errors[] = $this->db->lasterror();
11093 } else {
11094 $this->ref = '(PROV'.$this->id.')';
11095 }
11096 }
11097 }
11098
11099 // Create extrafields
11100 if (!$error) {
11101 $result = $this->insertExtraFields();
11102 if ($result < 0) {
11103 $error++;
11104 }
11105 }
11106
11107 // Create lines
11108 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
11109 foreach ($this->lines as $line) {
11110 $keyforparent = $this->fk_element;
11111 $line->$keyforparent = $this->id;
11112
11113 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
11114 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
11115 if (!is_object($line)) {
11116 $line = (object) $line;
11117 }
11118
11119 $result = 0;
11120 if (method_exists($line, 'insert')) {
11121 $result = $line->insert($user, 1);
11122 } elseif (method_exists($line, 'create')) {
11123 $result = $line->create($user, 1);
11124 }
11125 if ($result < 0) {
11126 $this->error = $line->error;
11127 $this->db->rollback();
11128 return -1;
11129 }
11130 }
11131 }
11132
11133 // Triggers
11134 if (!$error && !$notrigger) {
11135 // Call triggers
11136 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
11137 if ($result < 0) {
11138 $error++;
11139 }
11140 // End call triggers
11141 }
11142
11143 // Commit or rollback
11144 if ($error) {
11145 $this->db->rollback();
11146 return -1;
11147 } else {
11148 $this->db->commit();
11149 return $this->id;
11150 }
11151 }
11152
11153
11163 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
11164 {
11165 if (empty($id) && empty($ref) && empty($morewhere)) {
11166 return -1;
11167 }
11168
11169 $fieldlist = $this->getFieldList('t');
11170 if (empty($fieldlist)) {
11171 return 0;
11172 }
11173
11174 $sql = "SELECT ".$this->db->sanitize($fieldlist, 0, 0, 1);
11175 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
11176
11177 if (!empty($id)) {
11178 $sql .= ' WHERE t.rowid = '.((int) $id);
11179 } elseif (!empty($ref)) {
11180 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
11181 } else {
11182 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
11183 }
11184 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
11185 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
11186 }
11187 if ($morewhere) {
11188 $sql .= $morewhere;
11189 }
11190 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
11191
11192 $res = $this->db->query($sql);
11193 if ($res) {
11194 $obj = $this->db->fetch_object($res);
11195 if ($obj) {
11196 $this->setVarsFromFetchObj($obj);
11197
11198 // Retrieve all extrafield
11199 // fetch optionals attributes and labels
11200 if (empty($noextrafields)) {
11201 $result = $this->fetch_optionals();
11202 if ($result < 0) {
11203 $this->error = $this->db->lasterror();
11204 $this->errors[] = $this->error;
11205 return -4;
11206 }
11207 }
11208
11209 return $this->id;
11210 } else {
11211 return 0;
11212 }
11213 } else {
11214 $this->error = $this->db->lasterror();
11215 $this->errors[] = $this->error;
11216 return -1;
11217 }
11218 }
11219
11227 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
11228 {
11229 $objectlineclassname = get_class($this).'Line';
11230 if (!class_exists($objectlineclassname)) {
11231 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
11232 return -1;
11233 }
11234
11235 $objectline = new $objectlineclassname($this->db);
11236 '@phan-var-force CommonObjectLine $objectline';
11237
11238 $sql = "SELECT ".$objectline->getFieldList('l');
11239 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($objectline->table_element)." as l";
11240 $sql .= " WHERE l.fk_".$this->db->sanitize($this->element)." = ".((int) $this->id);
11241 if ($morewhere) {
11242 $sql .= $morewhere;
11243 }
11244 if (isset($objectline->fields['position'])) {
11245 $sql .= $this->db->order('position', 'ASC');
11246 }
11247
11248 $resql = $this->db->query($sql);
11249 if ($resql) {
11250 $num_rows = $this->db->num_rows($resql);
11251 $i = 0;
11252 $this->lines = array();
11253 while ($i < $num_rows) {
11254 $obj = $this->db->fetch_object($resql);
11255 if ($obj) {
11256 $newline = new $objectlineclassname($this->db);
11257 '@phan-var-force CommonObjectLine $newline';
11258 $newline->setVarsFromFetchObj($obj);
11259
11260 // Load also extrafields for the line
11261 if (empty($noextrafields)) {
11262 $newline->fetch_optionals();
11263 }
11264
11265 $this->lines[] = $newline;
11266 }
11267 $i++;
11268 }
11269
11270 return 1;
11271 } else {
11272 $this->error = $this->db->lasterror();
11273 $this->errors[] = $this->error;
11274 return -1;
11275 }
11276 }
11277
11285 public function updateCommon(User $user, $notrigger = 0)
11286 {
11287 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11288
11289 $error = 0;
11290
11291 $now = dol_now();
11292
11293 // $this->oldcopy should have been set by the caller of update
11294 //if (empty($this->oldcopy)) {
11295 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11296 // $this->oldcopy = dol_clone($this, 2);
11297 //}
11298
11299 $fieldvalues = $this->setSaveQuery();
11300
11301 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11302
11303 if (array_key_exists('date_modification', $fieldvalues)) {
11304 $fieldvalues['date_modification'] = $this->db->idate($now);
11305 }
11306 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
11307 $fieldvalues['tms'] = $this->db->idate($now);
11308 }
11309 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11310 $fieldvalues['fk_user_modif'] = $user->id;
11311 }
11312 if (array_key_exists('user_modification_id', $fieldvalues)) {
11313 $fieldvalues['user_modification_id'] = $user->id;
11314 }
11315 // @phan-suppress-next-line PhanUndeclaredProperty
11316 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11317 // @phan-suppress-next-line PhanUndeclaredProperty
11318 $tmparray = dol_hash($this->pass, '0', 0, 1);
11319 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11320 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11321 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11322 }
11323 }
11324 if (array_key_exists('ref', $fieldvalues)) {
11325 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11326 }
11327
11328 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11329
11330 // Add quotes and escape on fields with type string
11331 $keys = array();
11332 $values = array();
11333 $sanitized_tmp = array();
11334 foreach ($fieldvalues as $k => $v) {
11335 $keys[$k] = $k;
11336 $value = $this->fields[$k];
11337 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11338 $values[$k] = $this->quote($v, $value);
11339 if ((!empty($value["type"]) && $value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11340 // Clean values for text with selectbox
11341 $v = preg_replace('/\s/', ',', $v);
11342 $v = preg_replace('/,+/', ',', $v);
11343 }
11344 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, SqlInjection
11345 $sanitized_tmp[] = $this->db->sanitize($k).'='.$this->quote($v, $this->fields[$k]);
11346 }
11347
11348 // Clean and check mandatory fields
11349 foreach ($keys as $key) {
11350 if (!empty($this->fields[$key]['type']) && preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11351 $values[$key] = ''; // This is an implicit foreign key field
11352 }
11353 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11354 $values[$key] = ''; // This is an explicit foreign key field
11355 }
11356
11357 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11358 /*
11359 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11360 {
11361 $error++;
11362 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11363 }*/
11364 }
11365
11366 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11367 $sql.= " SET ".implode(', ', $sanitized_tmp);
11368 $sql.= " WHERE rowid = ".((int) $this->id);
11369
11370 $this->db->begin();
11371
11372 if (!$error) {
11373 $res = $this->db->query($sql);
11374 if (!$res) {
11375 $error++;
11376 $this->errors[] = $this->db->lasterror();
11377 }
11378 }
11379
11380 // Update extrafield
11381 if (!$error) {
11382 $result = $this->insertExtraFields(); // This update extrafields
11383 if ($result < 0) {
11384 $error++;
11385 }
11386 }
11387
11388 // Triggers
11389 if (!$error && !$notrigger) {
11390 // Call triggers
11391 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11392 if ($result < 0) {
11393 $error++;
11394 } //Do also here what you must do to rollback action if trigger fail
11395 // End call triggers
11396 }
11397
11398 // Commit or rollback
11399 if ($error) {
11400 $this->db->rollback();
11401 return -1;
11402 } else {
11403 $this->db->commit();
11404 return $this->id;
11405 }
11406 }
11407
11416 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11417 {
11418 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11419
11420 $error = 0;
11421
11422 $this->db->begin();
11423
11424 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11425 foreach ($this->childtables as $table) {
11426 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($table);
11427 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11428 $resql = $this->db->query($sql);
11429 if (!$resql) {
11430 $this->error = $this->db->lasterror();
11431 $this->errors[] = $this->error;
11432 $this->db->rollback();
11433 return -1;
11434 }
11435 }
11436 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11437 $objectisused = $this->isObjectUsed($this->id);
11438 if (!empty($objectisused)) {
11439 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11440 $this->error = 'ErrorRecordHasChildren';
11441 $this->errors[] = $this->error;
11442 $this->db->rollback();
11443 return 0;
11444 }
11445 }
11446
11447 // Delete cascade first
11448 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11449 foreach ($this->childtablesoncascade as $tabletodelete) {
11450 $deleteFromObject = explode(':', $tabletodelete, 4);
11451 if (count($deleteFromObject) >= 2) {
11452 $className = str_replace('@', '', $deleteFromObject[0]);
11453 $filePath = $deleteFromObject[1];
11454 $columnName = $deleteFromObject[2];
11455 $filter = '';
11456 if (!empty($deleteFromObject[3])) {
11457 $filter = $deleteFromObject[3];
11458 }
11459
11460 if (dol_include_once($filePath)) {
11461 $childObject = new $className($this->db);
11462 if (method_exists($childObject, 'deleteByParentField')) {
11463 '@phan-var-force CommonObject $childObject';
11464 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11465 if ($result < 0) {
11466 $error++;
11467 $this->errors[] = $childObject->error;
11468 break;
11469 }
11470 } else {
11471 $error++;
11472 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11473 break;
11474 }
11475 } else {
11476 $error++;
11477 $this->errors[] = 'Cannot include child class file '.$filePath;
11478 break;
11479 }
11480 } else {
11481 // Delete record in child table
11482 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($tabletodelete)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11483
11484 $resql = $this->db->query($sql);
11485 if (!$resql) {
11486 $error++;
11487 $this->error = $this->db->lasterror();
11488 $this->errors[] = $this->error;
11489 break;
11490 }
11491 }
11492 }
11493 }
11494
11495 if (!$error) {
11496 if (!$notrigger) {
11497 // Call triggers
11498 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11499 if ($result < 0) {
11500 $error++;
11501 } // Do also here what you must do to rollback action if trigger fail
11502 // End call triggers
11503 }
11504 }
11505
11506 // Delete llx_ecm_files
11507 if (!$error) {
11508 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11509 if (!$res) {
11510 $error++;
11511 }
11512 }
11513
11514 if (!$error) {
11515 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11516 // For remove dir
11517 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11518 if (dol_is_dir($dir)) {
11519 if (!dol_delete_dir_recursive($dir)) {
11520 $this->errors[] = 'ErrorFailToDeleteDir';
11521 }
11522 }
11523 }
11524
11525 // Delete linked object
11526 $res = $this->deleteObjectLinked();
11527 if ($res < 0) {
11528 $error++;
11529 }
11530
11531 if (!$error && !empty($this->isextrafieldmanaged)) {
11532 $result = $this->deleteExtraFields();
11533 if ($result < 0) {
11534 $error++;
11535 }
11536 }
11537
11538 if (!$error) {
11539 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11540 $sql .= " WHERE rowid = ".((int) $this->id);
11541
11542 $resql = $this->db->query($sql);
11543 if (!$resql) {
11544 $error++;
11545 $this->errors[] = $this->db->lasterror();
11546 }
11547 }
11548
11549 // Commit or rollback
11550 if ($error) {
11551 $this->db->rollback();
11552 return -1;
11553 } else {
11554 $this->db->commit();
11555 return 1;
11556 }
11557 }
11558
11570 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11571 {
11572 global $user;
11573
11574 $error = 0;
11575 $deleted = 0;
11576
11577 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11578
11579 if (!empty($parentId) && !empty($parentField)) {
11580 if (empty($this->table_element)) {
11581 $this->error = 'Property table_element for object is not defined';
11582 $this->errors[] = $this->error;
11583 $error++;
11584 return -1;
11585 }
11586 if (!method_exists($this, 'fetch')) {
11587 $this->error = 'Method fetch for object is not defined';
11588 $this->errors[] = $this->error;
11589 $error++;
11590 return -1;
11591 }
11592 if (!method_exists($this, 'delete')) {
11593 $this->error = 'Method delete for object is not defined';
11594 $this->errors[] = $this->error;
11595 $error++;
11596 return -1;
11597 }
11598
11599 $this->db->begin();
11600
11601 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element);
11602 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11603
11604 // Manage filter
11605 $errormessage = '';
11606 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11607 if ($errormessage) {
11608 $this->errors[] = $errormessage;
11609 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11610 return -1;
11611 }
11612
11613 $resql = $this->db->query($sql);
11614 if (!$resql) {
11615 $this->errors[] = $this->db->lasterror();
11616 $error++;
11617 } else {
11618 while ($obj = $this->db->fetch_object($resql)) {
11619 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11620 if ($result < 0) {
11621 $error++;
11622 $this->errors[] = $this->error;
11623 } else {
11624 $result = $this->delete($user); // @phpstan-ignore-line
11625 if ($result < 0) {
11626 $error++;
11627 $this->errors[] = $this->error;
11628 } else {
11629 $deleted++;
11630 }
11631 }
11632 }
11633 }
11634
11635 if (empty($error)) {
11636 $this->db->commit();
11637 return $deleted;
11638 } else {
11639 $this->error = implode(', ', $this->errors);
11640 $this->db->rollback();
11641 return $error * -1;
11642 }
11643 }
11644
11645 return $deleted;
11646 }
11647
11656 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11657 {
11658 $error = 0;
11659
11660 $tmpforobjectclass = get_class($this);
11661 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11662
11663 $this->db->begin();
11664
11665 // Call trigger
11666 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11667 if ($result < 0) {
11668 $error++;
11669 }
11670 // End call triggers
11671
11672 if (empty($error)) {
11673 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
11674 $sql .= " WHERE rowid = ".((int) $idline);
11675
11676 $resql = $this->db->query($sql);
11677 if (!$resql) {
11678 $this->error = "Error ".$this->db->lasterror();
11679 $error++;
11680 }
11681 }
11682
11683 if (empty($error)) {
11684 // Remove extrafields
11685 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11686 '@phan-var-force CommonObjectLine $tmpobjectline';
11687 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11688 $tmpobjectline->id = $idline;
11689 $result = $tmpobjectline->deleteExtraFields();
11690 if ($result < 0) {
11691 $error++;
11692 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11693 }
11694 }
11695 }
11696
11697 if (empty($error)) {
11698 $this->db->commit();
11699 return 1;
11700 } else {
11701 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11702 $this->db->rollback();
11703 return -1;
11704 }
11705 }
11706
11707
11718 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11719 {
11720 $error = 0;
11721
11722 $this->db->begin();
11723
11724 $statusfield = 'status';
11725 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11726 $statusfield = 'fk_statut';
11727 }
11728
11729 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element);
11730 $sql .= " SET ".$this->db->sanitize($statusfield)." = ".((int) $status);
11731 $sql .= " WHERE rowid = ".((int) $this->id);
11732
11733 if ($this->db->query($sql)) {
11734 if (!$error) {
11735 $this->oldcopy = clone $this;
11736 }
11737
11738 if (!$error && !$notrigger) {
11739 // Call trigger
11740 $result = $this->call_trigger($triggercode, $user);
11741 if ($result < 0) {
11742 $error++;
11743 }
11744 }
11745
11746 if (!$error) {
11747 $this->status = $status;
11748 if (property_exists($this, 'statut')) { // For backward compatibility
11749 $this->statut = $status;
11750 }
11751 $this->db->commit();
11752 return 1;
11753 } else {
11754 $this->db->rollback();
11755 return -1;
11756 }
11757 } else {
11758 $this->error = $this->db->error();
11759 $this->db->rollback();
11760 return -1;
11761 }
11762 }
11763
11770 public function initAsSpecimenCommon()
11771 {
11772 global $user;
11773
11774 $this->id = 0;
11775 $this->specimen = 1;
11776 $fields = array(
11777 'label' => 'This is label',
11778 'ref' => 'ABCD1234',
11779 'description' => 'This is a description',
11780 'qty' => 123.12,
11781 'note_public' => 'Public note',
11782 'note_private' => 'Private note',
11783 'date_creation' => (dol_now() - 3600 * 48),
11784 'date_modification' => (dol_now() - 3600 * 24),
11785 'fk_user_creat' => $user->id,
11786 'fk_user_modif' => $user->id,
11787 'date' => dol_now(),
11788 );
11789 foreach ($fields as $key => $value) {
11790 if (array_key_exists($key, $this->fields)) {
11791 $this->{$key} = $value; // @phpstan-ignore-line
11792 }
11793 }
11794
11795 // Force values to default values when known
11796 if (property_exists($this, 'fields')) {
11797 foreach ($this->fields as $key => $value) {
11798 // If fields are already set, do nothing
11799 if (array_key_exists($key, $fields)) {
11800 continue;
11801 }
11802
11803 if (!empty($value['default'])) {
11804 $this->$key = $value['default'];
11805 }
11806 }
11807 }
11808
11809 return 1;
11810 }
11811
11812
11813 /* Part for comments */
11814
11820 public function fetchComments()
11821 {
11822 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11823
11824 $comment = new Comment($this->db);
11825 $result = $comment->fetchAllFor($this->element, $this->id);
11826 if ($result < 0) {
11827 $this->setErrorsFromObject($comment);
11828 return -1;
11829 } else {
11830 $this->comments = $comment->comments;
11831 }
11832 return count($this->comments);
11833 }
11834
11840 public function getNbComments()
11841 {
11842 return count($this->comments);
11843 }
11844
11851 public function trimParameters($parameters)
11852 {
11853 if (!is_array($parameters)) {
11854 return;
11855 }
11856 foreach ($parameters as $parameter) {
11857 if (isset($this->$parameter)) {
11858 $this->$parameter = trim($this->$parameter);
11859 }
11860 }
11861 }
11862
11863 /* Part for categories/tags */
11864
11875 public function getCategoriesCommon($type_categ)
11876 {
11877 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11878
11879 // Get current categories
11880 $c = new Categorie($this->db);
11881 $existing = $c->containing($this->id, $type_categ, 'id');
11882
11883 return $existing;
11884 }
11885
11898 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11899 {
11900 // Handle single category
11901 if (!is_array($categories)) {
11902 $categories = array($categories);
11903 }
11904
11905 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11906
11907 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11908
11909 if (empty($type_categ)) {
11910 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11911 return -1;
11912 }
11913
11914 // Get current categories
11915 $c = new Categorie($this->db);
11916 $existing = $c->containing($this->id, $type_categ, 'id');
11917 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11918 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11919 if (!is_array($existing)) {
11920 $existing = array();
11921 }
11922 if ($remove_existing) {
11923 // Diff
11924 $to_del = array_diff($existing, $categories);
11925 $to_add = array_diff($categories, $existing);
11926 } else {
11927 $to_del = array(); // Nothing to delete
11928 $to_add = array_diff($categories, $existing);
11929 }
11930
11931 $error = 0;
11932 $ok = 0;
11933
11934 // Process
11935 foreach ($to_del as $del) {
11936 if ($c->fetch($del) > 0) {
11937 $result = $c->del_type($this, $type_categ);
11938 if ($result < 0) {
11939 $error++;
11940 $this->error = $c->error;
11941 $this->errors = $c->errors;
11942 break;
11943 } else {
11944 $ok += $result;
11945 }
11946 }
11947 }
11948 foreach ($to_add as $add) {
11949 if ($c->fetch($add) > 0) {
11950 $result = $c->add_type($this, $type_categ);
11951 if ($result < 0) {
11952 $error++;
11953 $this->error = $c->error;
11954 $this->errors = $c->errors;
11955 break;
11956 } else {
11957 $ok += $result;
11958 }
11959 }
11960 }
11961
11962 return $error ? (-1 * $error) : $ok;
11963 }
11964
11973 public function cloneCategories($fromId, $toId, $type = '')
11974 {
11975 $this->db->begin();
11976
11977 if (empty($type)) {
11978 $type = $this->table_element;
11979 }
11980
11981 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11982 $categorystatic = new Categorie($this->db);
11983
11984 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11985 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11986
11987 $sql = "INSERT INTO ".$this->db->sanitize($tablename)." (fk_categorie, ".$this->db->sanitize($fkname).")";
11988 $sql .= " SELECT fk_categorie, ".((int) $toId)." FROM ".$this->db->sanitize($tablename);
11989 $sql .= " WHERE ".$this->db->sanitize($fkname)." = ".((int) $fromId);
11990
11991 if (!$this->db->query($sql)) {
11992 $this->error = $this->db->lasterror();
11993 $this->db->rollback();
11994 return -1;
11995 }
11996
11997 $this->db->commit();
11998 return 1;
11999 }
12000
12007 public function deleteEcmFiles($mode = 0)
12008 {
12009 global $conf;
12010
12011 $this->db->begin();
12012
12013 // Delete in database with mode 0
12014 if ($mode == 0) {
12015 switch ($this->element) {
12016 case 'propal':
12017 $element = 'propale';
12018 break;
12019 case 'product':
12020 $element = 'produit';
12021 break;
12022 case 'order_supplier':
12023 $element = 'fournisseur/commande';
12024 break;
12025 case 'invoice_supplier':
12026 // Special cases that need to use get_exdir to get real dir of object
12027 // In future, all object should use this to define path of documents.
12028 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
12029 break;
12030 case 'shipping':
12031 $element = 'expedition/sending';
12032 break;
12033 case 'task':
12034 case 'project_task':
12035 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
12036
12037 $project_result = $this->fetchProject();
12038 if ($project_result >= 0) {
12039 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
12040 }
12041 // no break
12042 case 'contrat':
12043 $element = 'contract';
12044 break;
12045 default:
12046 $element = $this->element;
12047 }
12048 '@phan-var-force string $element';
12049
12050 // Delete ecm_files_extrafields with mode 0 (using name)
12051 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
12052 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
12053 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
12054 $sql .= ")";
12055
12056 if (!$this->db->query($sql)) {
12057 $this->error = $this->db->lasterror();
12058 $this->db->rollback();
12059 return false;
12060 }
12061
12062 // Delete ecm_files with mode 0 (using name)
12063 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
12064 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
12065 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
12066
12067 if (!$this->db->query($sql)) {
12068 $this->error = $this->db->lasterror();
12069 $this->db->rollback();
12070 return false;
12071 }
12072 }
12073
12074 // Delete in database with mode 1
12075 if ($mode == 1) {
12076 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
12077 $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).")";
12078 $resql = $this->db->query($sql);
12079 if (!$resql) {
12080 $this->error = $this->db->lasterror();
12081 $this->db->rollback();
12082 return false;
12083 }
12084
12085 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
12086 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
12087 $resql = $this->db->query($sql);
12088 if (!$resql) {
12089 $this->error = $this->db->lasterror();
12090 $this->db->rollback();
12091 return false;
12092 }
12093 }
12094
12095 $this->db->commit();
12096 return true;
12097 }
12098
12107 public function checkActiveProductInLines($status = 'onsale')
12108 {
12109 global $langs;
12110
12111 if (isModEnabled('product') || isModEnabled('service')) {
12112 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
12113
12114 $ret = true;
12115 $tmpproduct = new Product($this->db);
12116 foreach ($this->lines as $line) {
12117 if ($line->fk_product > 0) {
12118 $tmpproduct->fetch($line->fk_product);
12119 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
12120 if (!$tmpproduct->$statustotest) {
12121 $langs->load('products');
12122 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
12123 $ret = false;
12124 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
12125 break;
12126 }
12127 }
12128 }
12129 if (!$ret) {
12130 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
12131 }
12132 return $ret;
12133 } else {
12134 return true;
12135 }
12136 }
12137
12144 public function setErrorWithoutLog($message)
12145 {
12146 $this->error = $message;
12147 $this->errors[] = $message;
12148 }
12149
12157 public function setErrorWithLog($message, $loglevel = LOG_ERR)
12158 {
12159 global $dolibarr_main_document_root;
12160
12161 $this->setErrorWithoutLog($message);
12162 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
12163 $file = str_replace($dolibarr_main_document_root, '', $trace[0]['file'] ?? 'file unknown');
12164 $line = $trace[0]['line'] ?? 'line unknown';
12165 $syslogMessage = sprintf('%s:%s %s', $file, $line, $message);
12166 dol_syslog($syslogMessage, $loglevel);
12167 }
12168}
$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.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
fetchWarehouse($force_warehouse_id=0)
Load the warehouse of object, from id $this->warehouse_id or $this->fk_warehouse, into this->warehous...
getCategoriesCommon($type_categ)
Sets object to given categories.
indexFile($destfull, $update_main_doc_field)
Index a file into the ECM database.
getFormatedSupplierRef($objref)
Return supplier ref for screen output.
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteLineCommon(User $user, $idline, $notrigger=0)
Delete a line of object in database.
isEmpty()
isEmpty We consider CommonObject isEmpty if this->id is empty
clearFieldError($fieldKey)
clear validation message result for a field
static getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Count items linked to an object id in association table.
deleteEcmFiles($mode=0)
Delete related files of object in database.
getLastMainDocLink($modulepart, $initsharekey=0, $relativelink=0)
Return the link of last main doc file for direct public download.
liste_type_contact($source='internal', $order='position', $option=0, $activeonly=0, $code='')
Return array with list of possible values for type of contacts.
getTooltipContent($params)
getTooltipContent
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
swapContactStatus($rowid)
Update status of a contact linked to object.
getFieldError($fieldKey)
get field error message
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='')
Set status of an object.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
updateObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $f_user=null, $notrigger=0)
Update object linked of a current object.
setErrorWithoutLog($message)
Set the error message for the object without logging it.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
fetchObjectFrom($table, $field, $key, $element=null)
Load object from specific field.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
static deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Function used to remove all items linked to an object id in association table.
setFieldError($fieldKey, $msg='')
set validation error message a field
validateField($fields, $fieldKey, $fieldValue)
Return validation test result for a field.
getDataToShowPhoto($modulepart, $imagesize)
Function used to get the logos or photos of an object.
setMulticurrencyCode($code)
Change the multicurrency code.
emtpyObjectVars()
Sets all object fields to null.
fetch_projet()
Load the project with id $this->fk_project into this->project.
getIdContact($source, $code, $status=0)
Return id of contacts for a source and a contact code.
setExtraField($key, $value)
Convenience method for setting the value of an extrafield without actually updating it in the databas...
setDocModel($user, $modelpdf)
Set last model used by doc generator.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
getExtraField($key)
Convenience method for retrieving the value of an extrafield without actually fetching it from the da...
updateExtraField($key, $trigger=null, $userused=null)
Update 1 extra field value for the current object.
setPaymentTerms($id, $deposit_percent=null)
Change the payments terms.
isFloat($info)
Function test if type is float.
setBankAccount($fk_account, $notrigger=0, $userused=null)
Change the bank account.
setExtraParameters()
Set extra parameters.
delete_resource($rowid, $element='', $notrigger=0)
Delete a link to resource line.
setErrorsFromObject($object)
setErrorsFromObject
setShippingMethod($shipping_method_id, $notrigger=0, $userused=null)
Change the shipping method.
update_note_public($note)
Update public note (kept for backward compatibility)
getSpecialCode($lineid)
Get special code of a line.
clearObjectLinkedCache()
Clear the cache saying that all linked object were already loaded.
update_ref_ext($ref_ext)
Update external ref of element.
fetchOneLike($ref)
Looks for an object with ref matching the wildcard provided It does only work when $this->table_ref_f...
hasProductsOrServices($predefined=-1)
Function to say how many lines object contains.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
updateRangOfLine($rowid, $rang)
Update position of line (rang)
getDefaultCreateValueFor($fieldname, $alternatevalue=null, $type='alphanohtml')
Return the default value to use for a field when showing the create form of object.
add_element_resource($resource_id, $resource_type, $busy=0, $mandatory=0, $notrigger=0)
Add resources to the current object : add entry into llx_element_resources Need $this->element & $thi...
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
createCommon(User $user, $notrigger=0)
Create object in the database.
getTotalWeightVolume()
Return into unit=0, the calculated total of weight and volume of all lines * qty Calculate by adding ...
update_note($note, $suffix='', $notrigger=0)
Update note of element.
getFullAddress($withcountry=0, $sep="\n", $withregion=0, $extralangcode='')
Return full address of contact.
fetch_project()
Load the project with id $this->fk_project into this->project.
getFieldList($alias='', $excludefields=array())
Function to concat keys of fields.
getNbComments()
Return nb comments already posted.
setVarsFromFetchObj(&$obj)
Function to load data from a SQL pointer into properties of current object $this.
printObjectLines($action, $seller, $buyer, $selected=0, $dateSelector=0, $defaulttpldir='/core/tpl')
Return HTML table for object lines TODO Move this into an output class file (htmlline....
getChildrenOfLine($id, $includealltree=0)
Get children of line.
updateCommon(User $user, $notrigger=0)
Update object into database.
updateExtraLanguages($key, $trigger=null, $userused=null)
Update an extra language value for the current object.
deleteExtraFields()
Delete all extra fields values for the current object.
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
deprecatedProperties()
Provide list of deprecated properties and replacements.
setSaveQuery()
Function to return the array of data key-value from the ->fields and all the ->properties of an objec...
setValuesForExtraLanguages($onlykey='')
Fill array_options property of object by extrafields value (using for data sent by forms) Used for ex...
insertExtraLanguages($trigger='', $userused=null)
Add/Update all extra languages values for the current object.
setTransportMode($id)
Change the transport mode methods.
isArray($info)
Function test if type is array.
isInt($info)
Function test if type is integer.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
delete_contact($rowid, $notrigger=0)
Delete a link to contact line.
updateLineUp($rowid, $rang)
Update position of line up (rang)
fetch_user($userid)
Load the user with id $userid into this->user.
errorsToString()
Method to output saved errors.
getListContactId($source='external')
Return list of id of contacts of object.
setWarehouse($warehouse_id)
Change the warehouse.
setDeliveryAddress($id)
Define delivery address.
fetchBarCode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
getTotalDiscount()
Function that returns the total amount HT of discounts applied for all lines.
initAsSpecimenCommon()
Initialise object with example values Id must be 0 if object instance is a specimen.
printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected=0, $extrafields=null, $defaulttpldir='/core/tpl')
Return HTML content of a detail line TODO Move this into an output class file (htmlline....
copy_linked_contact($objFrom, $source='internal')
Copy contact from one element to current.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
getValueFrom($table, $id, $field)
Getter generic.
trimParameters($parameters)
Trim object parameters.
fetch_product()
Load the product with id $this->fk_product into this->product.
isIndex($info)
Function test if is indexed.
quote($value, $fieldsentry)
Add quote to field value if necessary.
formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir='/core/tpl')
Show add free and predefined products/services form.
fetchComments()
Load comments linked with current task.
line_down($rowid, $fk_parent_line=true)
Update a line to have a higher rank.
setValueFrom($field, $value, $table='', $id=null, $format='', $id_field='', $fuser=null, $trigkey='', $fk_user_field='fk_user_modif')
Setter generic.
fetch_contact($contactid=null)
Load object contact with id=$this->contact_id into $this->contact.
delThumbs($file)
Delete thumbs.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $overwritetitle=0, $usesharelink=0, $cache='', $addphotorefcss='photoref')
Show photos of an object (nbmax maximum), into several columns.
fetchLinesCommon($morewhere='', $noextrafields=0)
Load object in memory from the database.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
getRangOfLine($rowid)
Get position of line (rang)
showInputField($val, $key, $value, $moreparam='', $keysuffix='', $keyprefix='', $morecss=0, $nonewbutton=0)
Return HTML string to put an input field into a page Code very similar with showInputField of extra f...
update_contact($rowid, $statut, $type_contact_id=0, $fk_socpeople=0)
Update a link to contact line.
getElementType()
Return an element type string formatted like element_element target_type and source_type.
load_previous_next_ref($filter, $fieldid, $nodbprefix=0)
Load properties id_previous and id_next by comparing $fieldid with $this->ref.
setCategoriesCommon($categories, $type_categ='', $remove_existing=true)
Sets object to given categories.
fetchValuesForExtraLanguages()
Function to get alternative languages of a data into $this->array_languages This method is NOT called...
fetchNoCompute($id)
Function to make a fetch but set environment to avoid to load computed values before.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
getJSListDependancies($type='_extra')
setProject($projectid, $notrigger=0)
Link element with a project.
isForcedToNullIfZero($info)
Function test if field is forced to null if zero or empty.
line_ajaxorder($rows)
Update position of line with ajax (rang)
printOriginLinesList($restrictlist='', $selectedLines=array())
Return HTML table table of source object lines TODO Move this and previous function into output html ...
fetch_origin()
Read linked origin object.
getIdOfLine($rang)
Get rowid of the line relative to its position.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
deleteByParentField($parentId=0, $parentField='', $filter='', $filtermode="AND")
Delete all child object from a parent ID.
static getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
Function used to get an array with all items linked to an object id in association table.
__clone()
Overwrite magic function to solve problem of cloning object that are kept as references.
setPaymentMethods($id)
Change the payments methods.
updateLineDown($rowid, $rang, $max)
Update position of line down (rang)
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
fetchCommon($id, $ref=null, $morewhere='', $noextrafields=0)
Load object in memory from the database.
deleteCommon(User $user, $notrigger=0, $forcechilddeletion=0)
Delete object in database.
getFormatedCustomerRef($objref)
Return customer ref for screen output.
getTooltipContentArray($params)
Return array of data to show into a tooltip.
isText($info)
Function test if type is text.
isDate($info)
Function test if type is date.
printOriginLine($line, $var, $restrictlist='', $defaulttpldir='/core/tpl', $selectedLines=array())
Return HTML with a line of table array of source object lines TODO Move this and previous function in...
showOptionals($extrafields, $mode='view', $params=null, $keysuffix='', $keyprefix='', $onetrtd='', $display_type='card')
Function to show lines of extrafields with output data.
getCanvas($id=0, $ref='')
Load type of canvas of an object if it exists.
line_up($rowid, $fk_parent_line=true)
Update a line to have a lower rank.
isDuration($info)
Function test if type is duration.
setErrorWithLog($message, $loglevel=LOG_ERR)
Set the error message for the object and logs it, including the file name and line number.
canBeNull($info)
Function test if field can be null.
listeTypeContacts($source='internal', $option=0, $activeonly=0, $code='', $element='', $excludeelement='')
Return array with list of possible values for type of contacts.
getRights()
Returns the rights used for this class.
static commonReplaceContact(DoliDB $dbs, $origin_id, $dest_id, array $tables, $fieldname='fk_socpeople', $ignoreerrors=0)
Function used to replace a contact id with another one.
addThumbs($file, $quality=50)
Build thumb.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
cloneCategories($fromId, $toId, $type='')
Copy related categories to another object.
fetch_barcode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
Class to manage contact/addresses.
Class to manage absolute discounts.
Class to manage a WYSIWYG editor.
Class to manage Dolibarr database access.
sanitize($stringtosanitize, $allowsimplequote=0, $allowsequals=0, $allowsspace=0, $allowschars=1)
Sanitize a string for SQL forging.
Class to manage donations.
Definition don.class.php:41
Class to manage ECM files.
Class to manage warehouses.
Class to manage standard extra fields.
static isEmptyValue($v, string $type)
Return if a value is "empty" for a mandatory vision.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage generation of HTML components Only common components must be here.
Parent class of subscription templates.
Parent class to manage intervention document templates.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_SERVICE
Service.
Class to manage projects.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
Class toolbox to validate values.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
getState($id, $withcode='0', $dbtouse=null, $withregion=0, $outputlangs=null, $entconv=1)
Return state translated from an id.
convertSecondToTime($iSecond, $format='all', $lengthOfDay=86400, $lengthOfWeek=7)
Return, in clear text, value of a number of seconds in days, hours and minutes.
Definition date.lib.php:248
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_meta_create($object)
Create a meta file with document file into same directory.
completeFileArrayWithDatabaseInfo(&$filearray, $relativedir, $object=null)
Complete $filearray with data from database.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
dol_is_dir($folder)
Test if filename is a directory.
dol_delete_preview($object)
Delete all preview files linked to object instance.
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
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.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
getPictoForType($key, $morecss='')
Return the picto for a data type.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
Definition html.lib.php:99
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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...
Definition html.lib.php:172
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
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:134
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:130
getRandomPassword($generic=false, $replaceambiguouschars=null, $length=32)
Return a generated password using default module.
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.
dolEncrypt($chain, $key='', $ciphering='', $forceseed='', $obfuscationmode='dolcrypt')
Encode a string with a symmetric encryption.