dolibarr 24.0.0-beta
commonobject.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2015 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
5 * Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
6 * Copyright (C) 2011-2022 Philippe Grand <philippe.grand@atoo-net.com>
7 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
8 * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
10 * Copyright (C) 2015-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
11 * Copyright (C) 2016 Bahfir abbes <bafbes@gmail.com>
12 * Copyright (C) 2017 ATM Consulting <support@atm-consulting.fr>
13 * Copyright (C) 2017-2026 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2017 Rui Strecht <rui.strecht@aliartalentos.com>
15 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
16 * Copyright (C) 2018 Josep Lluís Amador <joseplluis@lliuretic.cat>
17 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
18 * Copyright (C) 2021 Grégory Blémand <gregory.blemand@atm-consulting.fr>
19 * Copyright (C) 2023 Lenin Rivas <lenin.rivas777@gmail.com>
20 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
21 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
22 * Copyright (C) 2025 Alexandre Janniaux <alexandre.janniaux@gmail.com>
23 * Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
24 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
25*
26 * This program is free software; you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation; either version 3 of the License, or
29 * (at your option) any later version.
30 *
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
35 *
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <https://www.gnu.org/licenses/>.
38 */
39
46require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
47require_once DOL_DOCUMENT_ROOT.'/core/class/commontrigger.class.php';
48
54abstract class CommonObject
55{
56 use DolDeprecationHandler;
57 use CommonTrigger;
58
62 public $module;
63
67 public $db;
68
72 public $id;
73
77 public $entity;
78
83 public $error;
84
88 public $errorhidden;
89
93 public $errors = array();
94
99 public $warning;
100
104 public $warnings = array();
105
109 private $validateFieldsErrors = array();
110
114 public $element;
115
120 public $fk_element;
121
127 public $element_for_permission;
128
132 public $table_element;
133
137 public $table_rowid;
138
142 public $table_element_line = '';
143
148 public $ismultientitymanaged;
149
153 public $import_key;
154
158 public $array_options = array();
159
160
237 public $fields = array();
238
244 public $array_languages = null; // Value is array() when load already tried
245
249 public $contacts_ids;
250
254 public $contacts_ids_internal;
255
259 public $linked_objects;
260
264 public $linkedObjectsIds;
265
269 public $linkedObjects;
270
274 private $linkedObjectsFullLoaded = array();
275
279 public $oldcopy;
280
284 public $oldref;
285
289 protected $table_ref_field = '';
290
294 public $restrictiononfksoc = 0;
295
296
297 // The following vars are used by some objects only.
298 // We keep these properties in CommonObject in order to provide common methods using them.
299
303 public $context = array();
304
308 public $actionmsg;
312 public $actionmsg2;
313
317 public $canvas;
318
323 public $project;
324
329 public $fk_project;
330
336 public $fk_projet;
337
342 public $contact;
343
348 public $contact_id;
349
354 public $thirdparty;
355
360 public $user;
361
366 public $product;
367
372 public $origin_type;
373
378 public $origin_id;
379
384
390 public $origin;
391
400 private $expedition;
401
407 private $livraison;
408
414 private $commandeFournisseur;
415
416
420 public $ref;
421
425 public $ref_ext;
426
430 public $ref_previous;
431
435 public $ref_next;
436
440 public $newref;
441
448 public $statut;
449
458 public $status;
459
464 public $country;
465
470 public $country_id;
471
476 public $country_code;
477
482 public $state;
483
488 public $state_id;
489
494 public $state_code;
495
500 public $region_id;
501
506 public $region_code;
507
512 public $region;
513
514
519 public $barcode_type;
520
525 public $barcode_type_code;
526
531 public $barcode_type_label;
532
537 public $barcode_type_coder;
538
543 public $mode_reglement_id;
544
549 public $cond_reglement_id;
550
554 public $demand_reason_id;
555
560 public $transport_mode_id;
561
569 private $cond_reglement; // Private to call DolDeprecationHandler
573 protected $depr_cond_reglement; // Internal value for deprecation
574
580 public $fk_delivery_address;
581
586 public $shipping_method_id;
587
592 public $shipping_method;
593
594 // Multicurrency
598 public $fk_multicurrency;
599
604 public $multicurrency_code;
605
610 public $multicurrency_tx;
611
615 public $multicurrency_total_ht;
616
620 public $multicurrency_total_tva;
621
625 public $multicurrency_total_localtax1; // not in database
626
630 public $multicurrency_total_localtax2; // not in database
631
635 public $multicurrency_total_ttc;
636
637
642 public $model_pdf;
643
648 public $last_main_doc;
649
655 public $fk_bank;
656
661 public $fk_account;
662
667 public $note_public;
668
673 public $note_private;
674
680 public $note;
681
686 public $total_ht;
687
692 public $total_tva;
693
698 public $total_localtax1;
699
704 public $total_localtax2;
705
710 public $total_ttc;
711
712
716 public $lines;
717
721 public $actiontypecode;
722
727 public $comments = array();
728
732 public $name;
733
737 public $lastname;
738
742 public $firstname;
743
748 public $civility_id;
749
753 public $civility_code;
754
755 // Dates
759 public $date_creation;
760
764 public $date_validation;
765
769 public $date_modification;
770
776 public $tms;
777
783 private $date_update;
784
788 public $date_cloture;
789
793 public $user_creation_id;
794
798 public $user_validation_id;
799
803 public $user_closing_id;
804
808 public $user_modification_id;
809
814 public $fk_user_creat;
815
820 public $fk_user_modif;
821
822
826 public $next_prev_filter;
827
831 public $specimen = 0;
832
836 public $sendtoid;
837
838
843 public $alreadypaid;
844
849 public $totalpaid;
850
855 public $totalpaid_multicurrency;
856
857
861 public $labelStatus = array();
862
866 public $labelStatusShort = array();
867
871 public $tpl;
872
873
877 public $showphoto_on_popup;
878
882 public $nb = array();
883
887 public $nbphoto;
888
892 public $output;
893
897 public $extraparams = array();
898
902 protected $childtables = array();
903
909 protected $childtablesoncascade = array();
910
914 public $cond_reglement_supplier_id;
915
921 public $deposit_percent;
922
923
927 public $warehouse_id;
928
932 public $isextrafieldmanaged = 0;
933
934
935 // No constructor as it is an abstract class
936
942 protected function deprecatedProperties()
943 {
944 return array(
945 'alreadypaid' => 'totalpaid',
946 'cond_reglement' => 'depr_cond_reglement',
947 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
948 'commandeFournisseur' => 'origin_object',
949 'expedition' => 'origin_object',
950 'fk_project' => 'fk_project',
951 'livraison' => 'origin_object',
952 'projet' => 'project',
953 'statut' => 'status',
954 );
955 }
956
957
968 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
969 {
970 global $db, $conf;
971
972 $sql = "SELECT rowid";
973 $sql .= " FROM ".$db->prefix().$element;
974 $sql .= " WHERE entity IN (".getEntity($element).")";
975
976 if ($id > 0) {
977 $sql .= " AND rowid = ".((int) $id);
978 } elseif ($ref) {
979 $sql .= " AND ref = '".$db->escape($ref)."'";
980 } elseif ($ref_ext) {
981 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
982 } else {
983 $error = 'ErrorWrongParameters';
984 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
985 return -1;
986 }
987 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
988 $sql .= " AND entity = ".((int) $conf->entity);
989 }
990
991 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
992 $resql = $db->query($sql);
993 if ($resql) {
994 $num = $db->num_rows($resql);
995 if ($num > 0) {
996 return 1;
997 } else {
998 return 0;
999 }
1000 }
1001 return -1;
1002 }
1003
1009 public function isEmpty()
1010 {
1011 return (empty($this->id));
1012 }
1013
1021 {
1022 if (!empty($object->error)) {
1023 $this->error = $object->error;
1024 }
1025 if (!empty($object->errors)) {
1026 $this->errors = array_merge($this->errors, $object->errors);
1027 }
1028 }
1029
1037 public function getTooltipContentArray($params)
1038 {
1039 return [];
1040 }
1041
1049 public function getTooltipContent($params)
1050 {
1051 global $action, $extrafields, $langs, $hookmanager;
1052
1053 // If there is too much extrafields, we do not include them into tooltip
1054 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
1055
1056 $data = $this->getTooltipContentArray($params);
1057 $count = 0;
1058
1059 // Add extrafields
1060 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1061 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1062 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1063 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1064 continue;
1065 }
1066
1067 if (empty($extrafields->attributes[$this->table_element]['showintooltip'][$key])) {
1068 continue;
1069 }
1070
1071
1072 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1073 $data['more_extrafields'] = '<br>...';
1074 break;
1075 }
1076 $enabled = 1;
1077 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1078 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1079 }
1080 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1081 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1082 }
1083 $perms = 1;
1084 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1085 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1086 }
1087 if (empty($enabled)) {
1088 continue; // 0 = Never visible field
1089 }
1090 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1091 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1092 }
1093 if (empty($perms)) {
1094 continue; // 0 = Not visible
1095 }
1096 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1097 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1098 }
1099 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1100 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1101 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1102 } else {
1103 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1104 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1105 $count++;
1106 }
1107 }
1108 $data['closedivextra'] = '</div>';
1109 }
1110
1111 $hookmanager->initHooks(array($this->element . 'dao'));
1112 $parameters = array(
1113 'tooltipcontentarray' => &$data,
1114 'params' => $params,
1115 );
1116 // Note that $action and $object may have been modified by some hooks
1117 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1118
1119 //var_dump($data);
1120 $label = implode($data);
1121
1122 return $label;
1123 }
1124
1125
1131 public function errorsToString()
1132 {
1133 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1134 }
1135
1136
1143 public function getFormatedCustomerRef($objref)
1144 {
1145 global $hookmanager;
1146
1147 $parameters = array('objref' => $objref);
1148 $action = '';
1149 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1150 if ($reshook > 0) {
1151 return $hookmanager->resArray['objref'];
1152 }
1153 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1154 }
1155
1162 public function getFormatedSupplierRef($objref)
1163 {
1164 global $hookmanager;
1165
1166 $parameters = array('objref' => $objref);
1167 $action = '';
1168 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1169 if ($reshook > 0) {
1170 return $hookmanager->resArray['objref'];
1171 }
1172 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1173 }
1174
1184 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1185 {
1186 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1187 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1188 $tmparray = getCountry($this->country_id, 'all');
1189 $this->country_code = $tmparray['code'];
1190 $this->country = $tmparray['label'];
1191 }
1192
1193 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1194 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1195 $tmparray = getState($this->state_id, 'all', null, 1);
1196 $this->state_code = $tmparray['code'];
1197 $this->state = $tmparray['label'];
1198 $this->region_code = $tmparray['region_code'];
1199 $this->region = $tmparray['region'];
1200 }
1201
1202 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1203 }
1204
1205
1214 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1215 {
1216 global $user, $dolibarr_main_url_root;
1217
1218 if (empty($this->last_main_doc)) {
1219 return ''; // No way to known which document name to use
1220 }
1221
1222 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1223 $ecmfile = new EcmFiles($this->db);
1224 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1225 if ($result < 0) {
1226 $this->error = $ecmfile->error;
1227 $this->errors = $ecmfile->errors;
1228 return -1;
1229 }
1230
1231 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1232 // Add entry into index
1233 if ($initsharekey) {
1234 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1235
1236 // 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
1237 /*
1238 $ecmfile->filepath = $rel_dir;
1239 $ecmfile->filename = $filename;
1240 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1241 $ecmfile->fullpath_orig = '';
1242 $ecmfile->gen_or_uploaded = 'generated';
1243 $ecmfile->description = ''; // indexed content
1244 $ecmfile->keywords = ''; // keyword content
1245 $ecmfile->share = getRandomPassword(true);
1246 $result = $ecmfile->create($user);
1247 if ($result < 0)
1248 {
1249 $this->error = $ecmfile->error;
1250 $this->errors = $ecmfile->errors;
1251 }
1252 */
1253 } else {
1254 return '';
1255 }
1256 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1257 // Add entry into index
1258 if ($initsharekey) {
1259 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1260 $ecmfile->share = getRandomPassword(true);
1261 $ecmfile->update($user);
1262 } else {
1263 return '';
1264 }
1265 }
1266 // Define $urlwithroot
1267 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1268 // This is to use external domain name found into config file
1269 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1270 //else
1271 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1272 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1273
1274 $forcedownload = 0;
1275
1276 $paramlink = '';
1277 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1278 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1279 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1280 if (!empty($ecmfile->share)) {
1281 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1282 }
1283 if ($forcedownload) {
1284 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1285 }
1286
1287 if ($relativelink) {
1288 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1289 } else {
1290 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1291 }
1292
1293 // Here $ecmfile->share is defined
1294 return $linktoreturn;
1295 }
1296
1297
1298 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1308 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1309 {
1310 // phpcs:enable
1311 global $user, $langs;
1312
1313 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1314
1315 // Check parameters
1316 if ($fk_socpeople <= 0) {
1317 $langs->load("errors");
1318 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1319 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1320 return -1;
1321 }
1322
1323 // Check that the contact actually exists in database
1324 $sql_check = "SELECT rowid FROM ".$this->db->prefix().($source == 'internal' ? "user" : "socpeople")." WHERE rowid = ".((int) $fk_socpeople);
1325 $resql_check = $this->db->query($sql_check);
1326 if ($resql_check) {
1327 if (!$this->db->num_rows($resql_check)) {
1328 $langs->load("errors");
1329 $this->error = $langs->trans("ErrorRecordNotFound");
1330 dol_syslog(get_class($this)."::add_contact Contact/user id ".$fk_socpeople." does not exist", LOG_ERR);
1331 return -1;
1332 }
1333 } else {
1334 $this->error = $this->db->lasterror();
1335 return -1;
1336 }
1337
1338 if (!$type_contact) {
1339 $langs->load("errors");
1340 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1341 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1342 return -2;
1343 }
1344
1345 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && !$user->hasRight('societe', 'client', 'voir')) {
1346 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1347 $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid;
1348 $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id;
1349
1350 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1351
1352 if (!$resql_allowed_contacts) {
1353 $this->errors[] = $this->db->lasterror();
1354 return -3;
1355 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1356 if ($obj->cnt == 0) {
1357 $langs->load("companies");
1358 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1359 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1360 return -4;
1361 }
1362 }
1363 }
1364
1365 $id_type_contact = 0;
1366 if (is_numeric($type_contact)) {
1367 $id_type_contact = $type_contact;
1368 } else {
1369 // We look for id type_contact
1370 $sql = "SELECT tc.rowid";
1371 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1372 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1373 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1374 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1375 //print $sql;
1376 $resql = $this->db->query($sql);
1377 if ($resql) {
1378 $obj = $this->db->fetch_object($resql);
1379 if ($obj) {
1380 $id_type_contact = $obj->rowid;
1381 }
1382 }
1383 }
1384 if ($id_type_contact == 0) {
1385 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");
1386 return 0;
1387 }
1388
1389 $datecreate = dol_now();
1390
1391 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1392 $TListeContacts = $this->liste_contact(-1, $source);
1393 $already_added = false;
1394 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1395 foreach ($TListeContacts as $array_contact) {
1396 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1397 $already_added = true;
1398 break;
1399 }
1400 }
1401 }
1402
1403 if (!$already_added) {
1404 $this->db->begin();
1405
1406 // Insert into database
1407 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1408 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1409 $sql .= " VALUES (".$this->id.", ".((int) $fk_socpeople)." , ";
1410 $sql .= "'".$this->db->idate($datecreate)."'";
1411 $sql .= ", 4, ".((int) $id_type_contact);
1412 $sql .= ")";
1413
1414 $resql = $this->db->query($sql);
1415 if ($resql) {
1416 if (!$notrigger) {
1417 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1418 $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user);
1419 if ($result < 0) {
1420 $this->db->rollback();
1421 return -5;
1422 }
1423 }
1424
1425 $this->db->commit();
1426 return 1;
1427 } else {
1428 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1429 $this->error = $this->db->errno();
1430 $this->db->rollback();
1431 return -6;
1432 } else {
1433 $this->error = $this->db->lasterror();
1434 $this->db->rollback();
1435 return -7;
1436 }
1437 }
1438 } else {
1439 return 0;
1440 }
1441 }
1442
1443 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1451 public function copy_linked_contact($objFrom, $source = 'internal')
1452 {
1453 // phpcs:enable
1454 $contacts = $objFrom->liste_contact(-1, $source);
1455 foreach ($contacts as $contact) {
1456 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1457 return -1;
1458 }
1459 }
1460 return 1;
1461 }
1462
1463 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1473 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1474 {
1475 // phpcs:enable
1476 // Insert into database
1477 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1478 $sql .= " statut = ".((int) $statut);
1479 if ($type_contact_id) {
1480 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1481 }
1482 if ($fk_socpeople) {
1483 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1484 }
1485 $sql .= " where rowid = ".((int) $rowid);
1486
1487 $resql = $this->db->query($sql);
1488 if ($resql) {
1489 return 0;
1490 } else {
1491 $this->error = $this->db->lasterror();
1492 return -1;
1493 }
1494 }
1495
1496 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1504 public function delete_contact($rowid, $notrigger = 0)
1505 {
1506 // phpcs:enable
1507 global $user;
1508
1509 $error = 0;
1510
1511 $this->db->begin();
1512
1513 if (!$error && empty($notrigger)) {
1514 // Call trigger
1515 $this->context['contact_id'] = ((int) $rowid);
1516 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1517 $result = $this->call_trigger($triggerPrefix.'_DELETE_CONTACT', $user);
1518 if ($result < 0) {
1519 $error++;
1520 }
1521 // End call triggers
1522 }
1523
1524 if (!$error) {
1525 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1526
1527 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1528 $sql .= " WHERE rowid = ".((int) $rowid);
1529
1530 $result = $this->db->query($sql);
1531 if (!$result) {
1532 $error++;
1533 $this->errors[] = $this->db->lasterror();
1534 }
1535 }
1536
1537 if (!$error) {
1538 $this->db->commit();
1539 return 1;
1540 } else {
1541 $this->error = $this->db->lasterror();
1542 $this->db->rollback();
1543 return -1;
1544 }
1545 }
1546
1547 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1555 public function delete_linked_contact($source = '', $code = '')
1556 {
1557 // phpcs:enable
1558 $listId = '';
1559 $temp = array();
1560 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1561
1562 if (!empty($typeContact)) {
1563 foreach ($typeContact as $key => $value) {
1564 array_push($temp, $key);
1565 }
1566 $listId = implode(",", $temp);
1567 }
1568
1569 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1570 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1571 if (empty($listId)) {
1572 return 0;
1573 }
1574
1575 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1576 $sql .= " WHERE element_id = ".((int) $this->id);
1577 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1578
1579 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1580 if ($this->db->query($sql)) {
1581 return 1;
1582 } else {
1583 $this->error = $this->db->lasterror();
1584 return -1;
1585 }
1586 }
1587
1588 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1600 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1601 {
1602 // phpcs:enable
1603 global $langs;
1604
1605 $tab = array();
1606
1607 $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
1608 if ($source == 'internal') {
1609 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1610 }
1611 if ($source == 'external' || $source == 'thirdparty') {
1612 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1613 }
1614 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1615 if (empty($arrayoftcids)) {
1616 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1617 }
1618 $sql .= " FROM";
1619 if (empty($arrayoftcids)) {
1620 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1621 }
1622 $sql .= " ".$this->db->prefix()."element_contact as ec";
1623 if ($source == 'internal') { // internal contact (user)
1624 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1625 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1626 }
1627 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1628 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1629 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1630 }
1631 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1632 if (empty($arrayoftcids)) {
1633 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1634 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1635 if ($code) {
1636 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1637 }
1638 if ($source == 'internal') {
1639 $sql .= " AND tc.source = 'internal'";
1640 }
1641 if ($source == 'external' || $source == 'thirdparty') {
1642 $sql .= " AND tc.source = 'external'";
1643 }
1644 $sql .= " AND tc.active = 1";
1645 } else {
1646 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1647 }
1648 if ($status >= 0) {
1649 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1650 }
1651 if ($statusoflink >= 0) {
1652 $sql .= " AND ec.statut = ".((int) $statusoflink);
1653 }
1654 $sql .= " ORDER BY t.lastname ASC";
1655
1656 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1657 $resql = $this->db->query($sql);
1658 if ($resql) {
1659 $num = $this->db->num_rows($resql);
1660 $i = 0;
1661 while ($i < $num) {
1662 $obj = $this->db->fetch_object($resql);
1663
1664 if (!$list) {
1665 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1666 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1667 $tab[$i] = array(
1668 'parentId' => $this->id,
1669 'source' => $obj->source,
1670 'socid' => $obj->socid,
1671 'id' => $obj->id,
1672 'nom' => $obj->lastname, // For backward compatibility
1673 'civility' => $obj->civility,
1674 'lastname' => $obj->lastname,
1675 'firstname' => $obj->firstname,
1676 'email' => $obj->email,
1677 'address' => $obj->address,
1678 'zip' => $obj->zip,
1679 'town' => $obj->town,
1680 'country_id' => $obj->country_id,
1681 'country' => $obj->country,
1682 'login' => (empty($obj->login) ? '' : $obj->login),
1683 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1684 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1685 'statuscontact' => $obj->statuscontact,
1686 'rowid' => $obj->rowid,
1687 'code' => $obj->code,
1688 'libelle' => $libelle_type,
1689 'status' => (int) $obj->statuslink,
1690 'fk_c_type_contact' => $obj->fk_c_type_contact
1691 );
1692 } else {
1693 $tab[$i] = $obj->id;
1694 }
1695
1696 $i++;
1697 }
1698
1699 return $tab;
1700 } else {
1701 $this->error = $this->db->lasterror();
1702 dol_print_error($this->db);
1703 return -1;
1704 }
1705 }
1706
1707
1714 public function swapContactStatus($rowid)
1715 {
1716 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1717 $sql .= " tc.code, tc.libelle as type_label";
1718 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1719 $sql .= " WHERE ec.rowid =".((int) $rowid);
1720 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1721 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1722
1723 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1724 $resql = $this->db->query($sql);
1725 if ($resql) {
1726 $obj = $this->db->fetch_object($resql);
1727 $newstatut = ($obj->statut == 4) ? 5 : 4;
1728 $result = $this->update_contact($rowid, $newstatut);
1729 $this->db->free($resql);
1730 return $result;
1731 } else {
1732 $this->error = $this->db->error();
1733 dol_print_error($this->db);
1734 return -1;
1735 }
1736 }
1737
1738 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1749 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1750 {
1751 // phpcs:enable
1752 global $langs;
1753
1754 if (empty($order)) {
1755 $order = 'position';
1756 }
1757 if ($order == 'position') {
1758 $order .= ',code';
1759 }
1760
1761 $tab = array();
1762
1763 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1764 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1765 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1766 if ($activeonly == 1) {
1767 $sql .= " AND tc.active = 1"; // only the active types
1768 }
1769 if (!empty($source) && $source != 'all') {
1770 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1771 }
1772 if (!empty($code)) {
1773 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1774 }
1775 $sql .= $this->db->order($order, 'ASC');
1776
1777 //print "sql=".$sql;
1778 $resql = $this->db->query($sql);
1779 if (!$resql) {
1780 $this->error = $this->db->lasterror();
1781 //dol_print_error($this->db);
1782 return null;
1783 }
1784
1785 $num = $this->db->num_rows($resql);
1786 $i = 0;
1787 while ($i < $num) {
1788 $obj = $this->db->fetch_object($resql);
1789
1790 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1791 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1792 if (empty($option)) {
1793 $tab[$obj->rowid] = $libelle_type;
1794 } elseif ($option == 1) {
1795 $tab[$obj->code] = $libelle_type;
1796 } else {
1797 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1798 }
1799 $i++;
1800 }
1801
1802 return $tab;
1803 }
1804
1816 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1817 {
1818 global $langs;
1819
1820 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1821
1822 $tab = array();
1823
1824 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1825 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1826
1827 $sqlWhere = array();
1828 if (!empty($element)) {
1829 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1830 }
1831 if (!empty($excludeelement)) {
1832 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1833 }
1834
1835 if ($activeonly == 1) {
1836 $sqlWhere[] = " tc.active=1"; // only the active types
1837 }
1838
1839 if (!empty($source) && $source != 'all') {
1840 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1841 }
1842
1843 if (!empty($code)) {
1844 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1845 }
1846
1847 if (count($sqlWhere) > 0) {
1848 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1849 }
1850
1851 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1852
1853 dol_syslog(__METHOD__, LOG_DEBUG);
1854 $resql = $this->db->query($sql);
1855 if ($resql) {
1856 $num = $this->db->num_rows($resql);
1857 if ($num > 0) {
1858 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1859
1860 while ($obj = $this->db->fetch_object($resql)) {
1861 $modulename = $obj->module ?? $obj->element;
1862 if (strpos($obj->element, 'project') !== false) {
1863 $modulename = 'projet';
1864 } elseif ($obj->element == 'contrat') {
1865 $element = 'contract';
1866 } elseif ($obj->element == 'action') {
1867 $modulename = 'agenda';
1868 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1869 $modulename = 'fournisseur';
1870 }
1871 if (isModEnabled($modulename)) {
1872 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1873 $tmpelement = $obj->element;
1874 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1875 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1876 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1877 }
1878 }
1879 }
1880 return $tab;
1881 } else {
1882 $this->error = $this->db->lasterror();
1883 return null;
1884 }
1885 }
1886
1898 public function getIdContact($source, $code, $status = 0)
1899 {
1900 $result = array();
1901 $i = 0;
1902 // Particular case for shipping
1903 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1904 $id = $this->origin_id;
1905 $element = 'commande';
1906 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1907 $id = $this->origin_id;
1908 $element = 'order_supplier';
1909 } else {
1910 $id = $this->id;
1911 $element = $this->element;
1912 }
1913
1914 $sql = "SELECT ec.fk_socpeople";
1915 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1916 if ($source == 'internal') {
1917 $sql .= " ".$this->db->prefix()."user as c,";
1918 }
1919 if ($source == 'external') {
1920 $sql .= " ".$this->db->prefix()."socpeople as c,";
1921 }
1922 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1923 $sql .= " WHERE ec.element_id = ".((int) $id);
1924 $sql .= " AND ec.fk_socpeople = c.rowid";
1925 if ($source == 'internal') {
1926 $sql .= " AND c.entity IN (".getEntity('user').")";
1927 }
1928 if ($source == 'external') {
1929 $sql .= " AND c.entity IN (".getEntity('societe').")";
1930 }
1931 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1932 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1933 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1934 if ($code) {
1935 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1936 }
1937 $sql .= " AND tc.active = 1";
1938 if ($status) {
1939 $sql .= " AND ec.statut = ".((int) $status);
1940 }
1941
1942 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1943 $resql = $this->db->query($sql);
1944 if ($resql) {
1945 while ($obj = $this->db->fetch_object($resql)) {
1946 $result[$i] = $obj->fk_socpeople;
1947 $i++;
1948 }
1949 } else {
1950 $this->error = $this->db->error();
1951 return null;
1952 }
1953
1954 return $result;
1955 }
1956
1957 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1964 public function fetch_contact($contactid = null)
1965 {
1966 // phpcs:enable
1967 if (empty($contactid)) {
1968 $contactid = $this->contact_id;
1969 }
1970
1971 if (empty($contactid)) {
1972 return 0;
1973 }
1974
1975 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1976 $contact = new Contact($this->db);
1977 $result = $contact->fetch($contactid);
1978 $this->contact = $contact;
1979 return $result;
1980 }
1981
1982 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1990 public function fetch_thirdparty($force_thirdparty_id = 0)
1991 {
1992 // phpcs:enable
1993 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1994 return 0;
1995 }
1996
1997 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1998
1999 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2000 if ($force_thirdparty_id) {
2001 $idtofetch = $force_thirdparty_id;
2002 }
2003
2004 if ($idtofetch) {
2005 $thirdparty = new Societe($this->db);
2006 $result = $thirdparty->fetch($idtofetch);
2007 if ($result < 0) {
2008 $this->errors = array_merge($this->errors, $thirdparty->errors);
2009 }
2010 $this->thirdparty = $thirdparty;
2011
2012 // Use first price level if level not defined for third party
2013 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
2014 $this->thirdparty->price_level = 1;
2015 }
2016
2017 return $result;
2018 } else {
2019 return -1;
2020 }
2021 }
2022
2023
2031 public function fetchOneLike($ref)
2032 {
2033 if (!$this->table_ref_field) {
2034 return 0;
2035 }
2036
2037 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
2038 $sql .= " WHERE ".$this->db->sanitize($this->table_ref_field)." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
2039 $sql .= " LIMIT 1";
2040
2041 $query = $this->db->query($sql);
2042
2043 if (!$this->db->num_rows($query)) {
2044 return 0;
2045 }
2046
2047 $result = $this->db->fetch_object($query);
2048
2049 if (method_exists($this, 'fetch')) {
2050 return $this->fetch($result->rowid);
2051 } else {
2052 $this->error = 'Fetch method not implemented on '.get_class($this);
2053 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2054 array_push($this->errors, $this->error);
2055 return -1;
2056 }
2057 }
2058
2059 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2068 public function fetch_barcode()
2069 {
2070 // phpcs:enable
2071 return $this->fetchBarCode();
2072 }
2073
2081 public function fetchBarCode()
2082 {
2083 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2084
2085 $idtype = $this->barcode_type;
2086 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
2087 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2088 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2089 } elseif ($this->element == 'societe') {
2090 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2091 } else {
2092 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2093 }
2094 }
2095
2096 if ($idtype > 0) {
2097 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
2098 $sql = "SELECT rowid, code, libelle as label, coder";
2099 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2100 $sql .= " WHERE rowid = ".((int) $idtype);
2101
2102 $resql = $this->db->query($sql);
2103 if ($resql) {
2104 $obj = $this->db->fetch_object($resql);
2105
2106 $this->barcode_type = $obj->rowid;
2107 $this->barcode_type_code = $obj->code;
2108 $this->barcode_type_label = $obj->label;
2109 $this->barcode_type_coder = $obj->coder;
2110 return 1;
2111 } else {
2112 dol_print_error($this->db);
2113 return -1;
2114 }
2115 }
2116 }
2117 return 0;
2118 }
2119
2125 public function fetchProject()
2126 {
2127 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2128
2129 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2130 $this->fk_project = $this->fk_projet; // For backward compatibility
2131 }
2132 if (empty($this->fk_project)) {
2133 return 0;
2134 }
2135
2136 $project = new Project($this->db);
2137 $result = $project->fetch($this->fk_project);
2138
2139 $this->project = $project;
2140
2141 return $result;
2142 }
2143
2144 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2152 public function fetch_project()
2153 {
2154 // phpcs:enable
2155 return $this->fetchProject();
2156 }
2157
2158 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2166 public function fetch_projet()
2167 {
2168 // phpcs:enable
2169 return $this->fetchProject();
2170 }
2171
2172 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2178 public function fetch_product()
2179 {
2180 // phpcs:enable
2181 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2182
2183 // @phan-suppress-next-line PhanUndeclaredProperty
2184 if (empty($this->fk_product)) {
2185 return 0;
2186 }
2187
2188 $product = new Product($this->db);
2189 // @phan-suppress-next-line PhanUndeclaredProperty
2190 $result = $product->fetch($this->fk_product);
2191
2192 $this->product = $product;
2193 return $result;
2194 }
2195
2196 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2203 public function fetch_user($userid)
2204 {
2205 // phpcs:enable
2206 $user = new User($this->db);
2207 $result = $user->fetch($userid);
2208 $this->user = $user;
2209 return $result;
2210 }
2211
2212 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2219 public function fetch_origin()
2220 {
2221 // phpcs:enable
2222 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2223
2224 // Manage classes with non standard name
2225 if ($tmpclassname == 'shipping') {
2226 $tmpclassname = 'Expedition';
2227 }
2228 if ($tmpclassname == 'delivery') {
2229 $tmpclassname = 'Livraison';
2230 }
2231 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2232 $tmpclassname = 'CommandeFournisseur';
2233 }
2234
2235 $classname = ucfirst($tmpclassname);
2236
2237 $this->origin_object = new $classname($this->db);
2238 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2239 $this->origin_object->fetch($this->origin_id);
2240 }
2241
2251 public function fetchObjectFrom($table, $field, $key, $element = null)
2252 {
2253 global $conf;
2254
2255 $result = false;
2256
2257 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2258 $sql .= " WHERE ".$this->db->sanitize($field)." = '".$this->db->escape($key)."'";
2259 if (!empty($element)) {
2260 $sql .= " AND entity IN (".getEntity($element).")";
2261 } else {
2262 $sql .= " AND entity = ".((int) $conf->entity);
2263 }
2264
2265 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2266 $resql = $this->db->query($sql);
2267 if ($resql) {
2268 $obj = $this->db->fetch_object($resql);
2269 // Test for avoid error -1
2270 if ($obj) {
2271 if (method_exists($this, 'fetch')) {
2272 $result = $this->fetch($obj->rowid);
2273 } else {
2274 $this->error = 'fetch() method not implemented on '.get_class($this);
2275 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2276 array_push($this->errors, $this->error);
2277 $result = -1;
2278 }
2279 }
2280 }
2281
2282 return $result;
2283 }
2284
2293 public function getValueFrom($table, $id, $field)
2294 {
2295 $result = false;
2296 if (!empty($id) && !empty($field) && !empty($table)) {
2297 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2298 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2299 return -1;
2300 }
2301 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2302 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2303 return -1;
2304 }
2305
2306 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2307 $sql .= " WHERE rowid = ".((int) $id);
2308
2309 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2310 $resql = $this->db->query($sql);
2311 if ($resql) {
2312 $row = $this->db->fetch_row($resql);
2313 $result = $row[0];
2314 }
2315 }
2316 return $result;
2317 }
2318
2335 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2336 {
2337 global $user;
2338
2339 if (empty($table)) {
2340 $table = $this->table_element;
2341 }
2342 if (empty($id)) {
2343 $id = $this->id;
2344 }
2345 if (empty($format)) {
2346 $format = 'text';
2347 }
2348 if (empty($id_field)) {
2349 $id_field = 'rowid';
2350 }
2351
2352 $propfield = $field;
2353
2354 // Special case
2355 if ($table == 'product') {
2356 if ($field == 'note_private') {
2357 $field = 'note';
2358 $propfield = 'note_private';
2359 } elseif ($field == 'status') {
2360 $field = 'tosell';
2361 $propfield = 'status';
2362 } elseif ($field == 'status_buy') {
2363 $field = 'tobuy';
2364 $propfield = 'status_buy';
2365 } elseif ($field == 'status_batch') {
2366 $field = 'tobatch';
2367 $propfield = 'status_batch';
2368 }
2369 }
2370
2371 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2372 $fk_user_field = 'fk_user_mod';
2373 }
2374 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2375 $fk_user_field = '';
2376 }
2377
2378 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2379 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2380 return -1;
2381 }
2382 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2383 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2384 return -1;
2385 }
2386
2387 $oldvalue = null;
2388 if ($trigkey) {
2389 $sql = "SELECT " . $this->db->sanitize($field);
2390 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2391 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2392
2393 $resql = $this->db->query($sql);
2394 if ($resql) {
2395 if ($obj = $this->db->fetch_object($resql)) {
2396 if ($format == 'date') {
2397 $oldvalue = $this->db->jdate($obj->$field);
2398 } else {
2399 $oldvalue = $obj->$field;
2400 }
2401 }
2402 } else {
2403 $this->error = $this->db->lasterror();
2404 return -1;
2405 }
2406 }
2407
2408 $error = 0;
2409
2410 dol_syslog(__METHOD__, LOG_DEBUG);
2411
2412 $this->db->begin();
2413
2414 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2415
2416 if ($format == 'text') {
2417 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2418 } elseif ($format == 'int') {
2419 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2420 } elseif ($format == 'date') {
2421 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2422 } elseif ($format == 'dategmt') {
2423 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2424 }
2425
2426 if ($fk_user_field) {
2427 if (!empty($fuser) && is_object($fuser)) {
2428 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2429 } elseif (empty($fuser) || $fuser != 'none') {
2430 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2431 }
2432 }
2433
2434 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2435
2436 $resql = $this->db->query($sql);
2437 if ($resql) {
2438 if ($trigkey) {
2439 // call trigger with updated object values
2440 if (method_exists($this, 'fetch')) {
2441 $result = $this->fetch($id);
2442 } else {
2443 $result = $this->fetchCommon($id);
2444 }
2445
2446 $this->oldcopy = clone $this;
2447 if (property_exists($this->oldcopy, $field)) {
2448 $this->oldcopy->$field = $oldvalue;
2449 }
2450 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2451 $this->oldcopy->$propfield = $oldvalue;
2452 }
2453
2454 if (empty($this->context['actionmsgmore'])) {
2455 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2456 }
2457
2458 if ($result >= 0) {
2459 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2460 }
2461 if ($result < 0) {
2462 $error++;
2463 }
2464 } else {
2465 if (property_exists($this, $field)) {
2466 $this->$field = $value;
2467 }
2468 if ($propfield != $field && property_exists($this, $propfield)) {
2469 $this->$propfield = $value;
2470 }
2471 }
2472
2473 if (!$error) {
2474 $this->db->commit();
2475 return 1;
2476 } else {
2477 if (property_exists($this, $field)) {
2478 $this->$field = $oldvalue;
2479 }
2480 if ($propfield != $field && property_exists($this, $propfield)) {
2481 $this->$propfield = $oldvalue;
2482 }
2483 $this->db->rollback();
2484 return -2;
2485 }
2486 } else {
2487 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2488 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2489 } else {
2490 $this->error = $this->db->lasterror();
2491 }
2492 $this->db->rollback();
2493 return -1;
2494 }
2495 }
2496
2497 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2508 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2509 {
2510 // phpcs:enable
2511 global $conf, $user;
2512
2513 if (!$this->table_element) {
2514 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2515 return -1;
2516 }
2517 if ($fieldid == 'none') {
2518 return 1;
2519 }
2520
2521 // For backward compatibility
2522 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2523 $fieldid = 'titre';
2524 }
2525
2526 // Security on socid
2527 $socid = 0;
2528 if ($user->socid > 0) {
2529 $socid = $user->socid;
2530 }
2531
2532 // this->ismultientitymanaged contains
2533 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2534 $aliastablesociete = 's';
2535 if ($this->element == 'societe') {
2536 $aliastablesociete = 'te'; // te as table_element
2537 }
2538 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2539 $sql = "SELECT MAX(te.".$this->db->sanitize($fieldid).")";
2540 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2541 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2542 $tmparray = explode('@', $this->ismultientitymanaged);
2543 $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
2544 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2545 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2546 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2547 $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
2548 }
2549 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2550 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2551 }
2552 if ($fieldid == 'rowid') {
2553 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < ".((int) $this->id);
2554 } elseif ($fieldid == 'label') {
2555 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < '".$this->db->escape((string) $this->label)."'";
2556 } else { // Should be 'ref' or any other string field
2557 $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)
2558 }
2559 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2560 $sql .= " AND sc.fk_user = ".((int) $user->id);
2561 }
2562 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2563 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2564 }
2565
2566 $filtermax = $filter;
2567
2568 // Manage filter
2569 $errormessage = '';
2570 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2571 if ($errormessage) {
2572 if (!preg_match('/^\s*AND/i', $filtermax)) {
2573 $sql .= " AND ";
2574 }
2575 $sql .= $filtermax;
2576 } else {
2577 $sql .= $tmpsql;
2578 }
2579
2580 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2581 $tmparray = explode('@', $this->ismultientitymanaged);
2582 $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
2583 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2584 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2585 }
2586 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2587 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2588 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2589 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2590 } else {
2591 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2592 }
2593 } else {
2594 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2595 }
2596 }
2597 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2598 $tmparray = explode('@', $this->ismultientitymanaged);
2599 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2600 }
2601 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2602 $sql .= ' AND te.fk_soc = '.((int) $socid);
2603 }
2604 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2605 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2606 }
2607 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2608 $sql .= ' AND te.rowid = '.((int) $socid);
2609 }
2610 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2611
2612 $result = $this->db->query($sql);
2613 if (!$result) {
2614 $this->error = $this->db->lasterror();
2615 return -1;
2616 }
2617 $row = $this->db->fetch_row($result);
2618 $this->ref_previous = $row[0];
2619
2620 $sql = "SELECT MIN(te.".$this->db->sanitize($fieldid).")";
2621 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2622 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2623 $tmparray = explode('@', $this->ismultientitymanaged);
2624 $sql .= ", ".$this->db->prefix().$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2625 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2626 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2627 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2628 $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
2629 }
2630 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2631 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2632 }
2633 if ($fieldid == 'rowid') {
2634 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > ".((int) $this->id);
2635 } elseif ($fieldid == 'label') {
2636 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > '".$this->db->escape((string) $this->label)."'";
2637 } else { // Should be 'ref' or any other string field
2638 $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)
2639 }
2640 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2641 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2642 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2643 $userschilds = $user->getAllChildIds();
2644 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2645 }
2646 $sql .= ')';
2647 }
2648 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2649 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2650 }
2651
2652 $filtermin = $filter;
2653
2654 // Manage filter
2655 $errormessage = '';
2656 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2657 if ($errormessage) {
2658 if (!preg_match('/^\s*AND/i', $filtermin)) {
2659 $sql .= " AND ";
2660 }
2661 $sql .= $filtermin;
2662
2663 $filtermin = '';
2664 } else {
2665 $sql .= $tmpsql;
2666 }
2667
2668 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2669 $tmparray = explode('@', $this->ismultientitymanaged);
2670 $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
2671 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2672 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2673 }
2674 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2675 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2676 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2677 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2678 } else {
2679 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2680 }
2681 } else {
2682 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2683 }
2684 }
2685 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2686 $tmparray = explode('@', $this->ismultientitymanaged);
2687 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2688 }
2689 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2690 $sql .= ' AND te.fk_soc = '.((int) $socid);
2691 }
2692 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2693 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2694 }
2695 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2696 $sql .= ' AND te.rowid = '.((int) $socid);
2697 }
2698 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2699 // 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
2700
2701 $result = $this->db->query($sql);
2702 if (!$result) {
2703 $this->error = $this->db->lasterror();
2704 return -2;
2705 }
2706 $row = $this->db->fetch_row($result);
2707 $this->ref_next = $row[0];
2708
2709 return 1;
2710 }
2711
2712
2720 public function getListContactId($source = 'external')
2721 {
2722 $contactAlreadySelected = array();
2723 $tab = $this->liste_contact(-1, $source);
2724 $num = count($tab);
2725 $i = 0;
2726 while ($i < $num) {
2727 if ($source == 'thirdparty') {
2728 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2729 } else {
2730 $contactAlreadySelected[$i] = $tab[$i]['id'];
2731 }
2732 $i++;
2733 }
2734 return $contactAlreadySelected;
2735 }
2736
2737
2745 public function setProject($projectid, $notrigger = 0)
2746 {
2747 global $user;
2748 $error = 0;
2749
2750 if (!$this->table_element) {
2751 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2752 return -1;
2753 }
2754
2755 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2756 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2757 if ($projectid) {
2758 $sql .= " SET fk_project = ".((int) $projectid);
2759 } else {
2760 $sql .= " SET fk_project = NULL";
2761 }
2762 $sql .= ' WHERE id = '.((int) $this->id);
2763 // @phan-suppress-next-line PhanTypeMismatchProperty
2764 } elseif (!empty($this->fields['fk_project'])) { // Common case
2765 if ($projectid) {
2766 $sql .= " SET fk_project = ".((int) $projectid);
2767 } else {
2768 $sql .= " SET fk_project = NULL";
2769 }
2770 $sql .= ' WHERE rowid = '.((int) $this->id);
2771 } else { // Special case for old architecture objects
2772 if ($projectid) {
2773 $sql .= ' SET fk_projet = '.((int) $projectid);
2774 } else {
2775 $sql .= ' SET fk_projet = NULL';
2776 }
2777 $sql .= " WHERE rowid = ".((int) $this->id);
2778 }
2779
2780 $this->db->begin();
2781
2782 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2783 if ($this->db->query($sql)) {
2784 $this->fk_project = ((int) $projectid);
2785 } else {
2786 dol_print_error($this->db);
2787 $error++;
2788 }
2789
2790 // Triggers
2791 if (!$error && !$notrigger) {
2792 // Call triggers
2793 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2794 if ($result < 0) {
2795 $error++;
2796 } //Do also here what you must do to rollback action if trigger fail
2797 // End call triggers
2798 }
2799
2800 // Commit or rollback
2801 if ($error) {
2802 $this->db->rollback();
2803 return -1;
2804 } else {
2805 $this->db->commit();
2806 return 1;
2807 }
2808 }
2809
2816 public function setPaymentMethods($id)
2817 {
2818 global $user;
2819
2820 $error = 0;
2821 $notrigger = 0;
2822
2823 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2824
2825 if ($this->status >= 0 || $this->element == 'societe') {
2826 $this->oldcopy = dol_clone($this, 2); // @phan-suppress-current-line PhanTypeMismatchProperty
2827
2828 // TODO uniformize field name
2829 $fieldname = 'fk_mode_reglement';
2830 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2831 if ($this->element == 'societe') {
2832 $fieldname = 'mode_reglement';
2833 $triggerName = 'COMPANY';
2834 }
2835 if (get_class($this) == 'Facture') {
2836 $triggerName = 'BILL';
2837 }
2838 if (get_class($this) == 'Fournisseur') {
2839 $fieldname = 'mode_reglement_supplier';
2840 $triggerName = 'BILL_SUPPLIER';
2841 }
2842 if (get_class($this) == 'Tva') {
2843 $fieldname = 'fk_typepayment';
2844 $triggerName = 'VAT';
2845 }
2846 if (get_class($this) == 'Salary') {
2847 $fieldname = 'fk_typepayment';
2848 }
2849
2850 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2851 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2852 $sql .= ' WHERE rowid = '.((int) $this->id);
2853
2854 if ($this->db->query($sql)) {
2855 $this->mode_reglement_id = $id;
2856 // for supplier
2857 if (get_class($this) == 'Fournisseur') {
2858 $this->mode_reglement_supplier_id = $id;
2859 }
2860
2861 // Triggers
2862 if (!$error && !$notrigger) {
2863 // Call triggers
2864 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2865 if ($result < 0) {
2866 $error++;
2867 }
2868 // End call triggers
2869 }
2870 return 1;
2871 } else {
2872 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2873 $this->error = $this->db->error();
2874 return -1;
2875 }
2876 } else {
2877 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2878 $this->error = 'Status of the object is incompatible '.$this->status;
2879 return -2;
2880 }
2881 }
2882
2889 public function setMulticurrencyCode($code)
2890 {
2891 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2892 if ($this->status >= 0 || $this->element == 'societe') {
2893 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2894 $sql .= " SET multicurrency_code = '".$this->db->escape($code)."'";
2895 $sql .= ' WHERE rowid='.((int) $this->id);
2896
2897 if ($this->db->query($sql)) {
2898 $this->multicurrency_code = $code;
2899
2900 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2901 if ($rate) {
2902 $this->setMulticurrencyRate($rate, 2);
2903 }
2904
2905 return 1;
2906 } else {
2907 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2908 $this->error = $this->db->error();
2909 return -1;
2910 }
2911 } else {
2912 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2913 $this->error = 'Status of the object is incompatible '.$this->status;
2914 return -2;
2915 }
2916 }
2917
2925 public function setMulticurrencyRate($rate, $mode = 1)
2926 {
2927 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2928 if ($this->status >= 0 || $this->element == 'societe') {
2929 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2930 $sql .= " SET multicurrency_tx = ".((float) $rate);
2931 $sql .= ' WHERE rowid='.((int) $this->id);
2932
2933 if ($this->db->query($sql)) {
2934 $this->multicurrency_tx = $rate;
2935
2936 // Update line price
2937 if (!empty($this->lines)) {
2938 foreach ($this->lines as &$line) {
2939 // Amounts in company currency will be recalculated
2940 if ($mode == 1) {
2941 $line->subprice = 0;
2942 }
2943
2944 // Amounts in foreign currency will be recalculated
2945 if ($mode == 2) {
2946 $line->multicurrency_subprice = 0;
2947 }
2948
2949 switch ($this->element) {
2950 case 'propal':
2953 '@phan-var-force Propal $this';
2954 '@phan-var-force PropaleLigne $line';
2955 $this->updateline(
2956 $line->id,
2957 $line->subprice,
2958 $line->qty,
2959 $line->remise_percent,
2960 $line->tva_tx,
2961 $line->localtax1_tx,
2962 $line->localtax2_tx,
2963 ($line->description ? $line->description : $line->desc),
2964 'HT',
2965 $line->info_bits,
2966 $line->special_code,
2967 $line->fk_parent_line,
2968 $line->skip_update_total,
2969 $line->fk_fournprice,
2970 $line->pa_ht,
2971 $line->label,
2972 $line->product_type,
2973 $line->date_start,
2974 $line->date_end,
2975 $line->array_options,
2976 $line->fk_unit,
2977 $line->multicurrency_subprice
2978 );
2979 break;
2980 case 'commande':
2983 '@phan-var-force Commande $this';
2984 '@phan-var-force OrderLine $line';
2985 $this->updateline(
2986 $line->id,
2987 ($line->description ? $line->description : $line->desc),
2988 $line->subprice,
2989 $line->qty,
2990 $line->remise_percent,
2991 $line->tva_tx,
2992 $line->localtax1_tx,
2993 $line->localtax2_tx,
2994 'HT',
2995 $line->info_bits,
2996 $line->date_start,
2997 $line->date_end,
2998 $line->product_type,
2999 $line->fk_parent_line,
3000 $line->skip_update_total,
3001 $line->fk_fournprice,
3002 $line->pa_ht,
3003 $line->label,
3004 $line->special_code,
3005 $line->array_options,
3006 $line->fk_unit,
3007 $line->multicurrency_subprice
3008 );
3009 break;
3010 case 'facture':
3013 '@phan-var-force Facture $this';
3014 '@phan-var-force FactureLigne $line';
3015 $this->updateline(
3016 $line->id,
3017 ($line->description ? $line->description : $line->desc),
3018 $line->subprice,
3019 $line->qty,
3020 $line->remise_percent,
3021 $line->date_start,
3022 $line->date_end,
3023 $line->tva_tx,
3024 $line->localtax1_tx,
3025 $line->localtax2_tx,
3026 'HT',
3027 $line->info_bits,
3028 $line->product_type,
3029 $line->fk_parent_line,
3030 $line->skip_update_total,
3031 $line->fk_fournprice,
3032 $line->pa_ht,
3033 $line->label,
3034 $line->special_code,
3035 $line->array_options,
3036 $line->situation_percent,
3037 $line->fk_unit,
3038 $line->multicurrency_subprice
3039 );
3040 break;
3041 case 'facturerec':
3044 '@phan-var-force FactureRec $this';
3045 '@phan-var-force FactureLigneRec $line';
3046 $this->updateline(
3047 $line->id,
3048 ($line->description ? $line->description : $line->desc),
3049 $line->subprice,
3050 $line->qty,
3051 $line->tva_tx,
3052 $line->localtax1_tx,
3053 $line->localtax2_tx,
3054 $line->fk_product,
3055 $line->remise_percent,
3056 'HT',
3057 $line->info_bits,
3058 0,
3059 0,
3060 $line->product_type,
3061 $line->rang,
3062 $line->special_code,
3063 $line->label,
3064 $line->fk_unit,
3065 $line->multicurrency_subprice,
3066 0,
3067 $line->date_start,
3068 $line->date_end,
3069 $line->fk_fournprice,
3070 $line->pa_ht,
3071 $line->fk_parent_line
3072 );
3073 break;
3074 case 'supplier_proposal':
3077 '@phan-var-force SupplierProposal $this';
3078 '@phan-var-force SupplierProposalLine $line';
3079 $this->updateline(
3080 $line->id,
3081 $line->subprice,
3082 $line->qty,
3083 $line->remise_percent,
3084 $line->tva_tx,
3085 $line->localtax1_tx,
3086 $line->localtax2_tx,
3087 ($line->description ? $line->description : $line->desc),
3088 'HT',
3089 $line->info_bits,
3090 $line->special_code,
3091 $line->fk_parent_line,
3092 $line->skip_update_total,
3093 $line->fk_fournprice,
3094 $line->pa_ht,
3095 $line->label,
3096 $line->product_type,
3097 $line->array_options,
3098 $line->ref_fourn,
3099 (int) $line->fk_unit,
3100 $line->multicurrency_subprice
3101 );
3102 break;
3103 case 'order_supplier':
3106 '@phan-var-force CommandeFournisseur $this';
3107 '@phan-var-force CommandeFournisseurLigne $line';
3108 $this->updateline(
3109 $line->id,
3110 ($line->description ? $line->description : $line->desc),
3111 $line->subprice,
3112 $line->qty,
3113 $line->remise_percent,
3114 $line->tva_tx,
3115 $line->localtax1_tx,
3116 $line->localtax2_tx,
3117 'HT',
3118 $line->info_bits,
3119 $line->product_type,
3120 0,
3121 $line->date_start,
3122 $line->date_end,
3123 $line->array_options,
3124 $line->fk_unit,
3125 $line->multicurrency_subprice,
3126 $line->ref_supplier
3127 );
3128 break;
3129 case 'invoice_supplier':
3132 '@phan-var-force FactureFournisseur $this';
3133 '@phan-var-force SupplierInvoiceLIne $line';
3134 $this->updateline(
3135 $line->id,
3136 ($line->description ? $line->description : $line->desc),
3137 $line->subprice,
3138 $line->tva_tx,
3139 $line->localtax1_tx,
3140 $line->localtax2_tx,
3141 $line->qty,
3142 0,
3143 'HT',
3144 $line->info_bits,
3145 $line->product_type,
3146 $line->remise_percent,
3147 0,
3148 $line->date_start,
3149 $line->date_end,
3150 $line->array_options,
3151 $line->fk_unit,
3152 $line->multicurrency_subprice,
3153 $line->ref_supplier
3154 );
3155 break;
3156 default:
3157 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3158 break;
3159 }
3160 }
3161 }
3162
3163 return 1;
3164 } else {
3165 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3166 $this->error = $this->db->error();
3167 return -1;
3168 }
3169 } else {
3170 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3171 $this->error = 'Status of the object is incompatible '.$this->status;
3172 return -2;
3173 }
3174 }
3175
3183 public function setPaymentTerms($id, $deposit_percent = null)
3184 {
3185 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.formatLogObject($deposit_percent).')');
3186 if ($this->status >= 0 || $this->element == 'societe') {
3187 // TODO uniformize field name
3188 $fieldname = 'fk_cond_reglement';
3189 if ($this->element == 'societe') {
3190 $fieldname = 'cond_reglement';
3191 }
3192 if (get_class($this) == 'Fournisseur') {
3193 $fieldname = 'cond_reglement_supplier';
3194 }
3195
3196 if (empty($deposit_percent) || $deposit_percent < 0) {
3197 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3198 }
3199
3200 if ($deposit_percent > 100) {
3201 $deposit_percent = 100;
3202 }
3203
3204 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3205 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3206 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3207 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3208 }
3209 $sql .= ' WHERE rowid = '.((int) $this->id);
3210
3211 if ($this->db->query($sql)) {
3212 $this->cond_reglement_id = $id;
3213 // for supplier
3214 if (get_class($this) == 'Fournisseur') {
3215 $this->cond_reglement_supplier_id = $id;
3216 }
3217 $this->deposit_percent = $deposit_percent;
3218 return 1;
3219 } else {
3220 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3221 $this->error = $this->db->error();
3222 return -1;
3223 }
3224 } else {
3225 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3226 $this->error = 'Status of the object is incompatible '.$this->status;
3227 return -2;
3228 }
3229 }
3230
3237 public function setTransportMode($id)
3238 {
3239 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3240 if ($this->status >= 0 || $this->element == 'societe') {
3241 $fieldname = 'fk_transport_mode';
3242 if ($this->element == 'societe') {
3243 $fieldname = 'transport_mode';
3244 }
3245 if (get_class($this) == 'Fournisseur') {
3246 $fieldname = 'transport_mode_supplier';
3247 }
3248
3249 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3250 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3251 $sql .= ' WHERE rowid='.((int) $this->id);
3252
3253 if ($this->db->query($sql)) {
3254 $this->transport_mode_id = $id;
3255 // for supplier
3256 if (get_class($this) == 'Fournisseur') {
3257 $this->transport_mode_supplier_id = $id;
3258 }
3259 return 1;
3260 } else {
3261 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3262 $this->error = $this->db->error();
3263 return -1;
3264 }
3265 } else {
3266 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3267 $this->error = 'Status of the object is incompatible '.$this->status;
3268 return -2;
3269 }
3270 }
3271
3279 public function setDeliveryAddress($id)
3280 {
3281 $fieldname = 'fk_delivery_address';
3282 if ($this->element == 'delivery' || $this->element == 'shipping') {
3283 $fieldname = 'fk_address';
3284 }
3285
3286 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$this->db->sanitize($fieldname)." = ".((int) $id);
3287 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3288
3289 if ($this->db->query($sql)) {
3290 $this->fk_delivery_address = $id;
3291 return 1;
3292 } else {
3293 $this->error = $this->db->error();
3294 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3295 return -1;
3296 }
3297 }
3298
3299
3308 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3309 {
3310 global $user;
3311
3312 if (empty($userused)) {
3313 $userused = $user;
3314 }
3315
3316 $error = 0;
3317
3318 if (!$this->table_element) {
3319 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3320 return -1;
3321 }
3322
3323 $this->db->begin();
3324
3325 if ($shipping_method_id < 0) {
3326 $shipping_method_id = 'NULL';
3327 }
3328 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3329
3330 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3331 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3332 $sql .= " WHERE rowid=".((int) $this->id);
3333 $resql = $this->db->query($sql);
3334 if (!$resql) {
3335 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3336 $this->error = $this->db->lasterror();
3337 $error++;
3338 } else {
3339 if (!$notrigger) {
3340 // Call trigger
3341 $this->context = array('shippingmethodupdate' => 1);
3342 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3343 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3344 if ($result < 0) {
3345 $error++;
3346 }
3347 // End call trigger
3348 }
3349 }
3350 if ($error) {
3351 $this->db->rollback();
3352 return -1;
3353 } else {
3354 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3355 $this->db->commit();
3356 return 1;
3357 }
3358 }
3359
3360
3367 public function setWarehouse($warehouse_id)
3368 {
3369 if (!$this->table_element) {
3370 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3371 return -1;
3372 }
3373 if ($warehouse_id < 0) {
3374 $warehouse_id = 'NULL';
3375 }
3376 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3377
3378 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3379 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3380 $sql .= " WHERE rowid=".((int) $this->id);
3381
3382 if ($this->db->query($sql)) {
3383 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3384 return 1;
3385 } else {
3386 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3387 $this->error = $this->db->error();
3388 return 0;
3389 }
3390 }
3391
3392
3400 public function setDocModel($user, $modelpdf)
3401 {
3402 if (!$this->table_element) {
3403 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3404 return -1;
3405 }
3406
3407 $newmodelpdf = dol_trunc($modelpdf, 255);
3408
3409 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3410 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3411 $sql .= " WHERE rowid = ".((int) $this->id);
3412
3413 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3414 $resql = $this->db->query($sql);
3415 if ($resql) {
3416 $this->model_pdf = $modelpdf;
3417 return 1;
3418 } else {
3419 dol_print_error($this->db);
3420 return 0;
3421 }
3422 }
3423
3424
3433 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3434 {
3435 global $user;
3436
3437 if (empty($userused)) {
3438 $userused = $user;
3439 }
3440
3441 $error = 0;
3442
3443 if (!$this->table_element) {
3444 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3445 return -1;
3446 }
3447 $this->db->begin();
3448
3449 if ($fk_account < 0) {
3450 $fk_account = 'NULL';
3451 }
3452 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3453
3454 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3455 $sql .= " SET fk_account = ".((int) $fk_account);
3456 $sql .= " WHERE rowid=".((int) $this->id);
3457
3458 $resql = $this->db->query($sql);
3459 if (!$resql) {
3460 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3461 $this->error = $this->db->lasterror();
3462 $error++;
3463 } else {
3464 if (!$notrigger) {
3465 // Call trigger
3466 $this->context['bankaccountupdate'] = 1;
3467
3468 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3469 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3470 if ($result < 0) {
3471 $error++;
3472 }
3473 // End call trigger
3474 }
3475 }
3476 if ($error) {
3477 $this->db->rollback();
3478 return -1;
3479 } else {
3480 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3481 $this->db->commit();
3482 return 1;
3483 }
3484 }
3485
3486
3487 // TODO: Move line related operations to CommonObjectLine?
3488
3489 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3499 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3500 {
3501 // phpcs:enable
3502 if (!$this->table_element_line) {
3503 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3504 return -1;
3505 }
3506 if (!$this->fk_element) {
3507 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3508 return -1;
3509 }
3510
3511 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3512 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3513 $fieldposition = 'position';
3514 }
3515
3516 // Count number of lines to reorder (according to choice $renum)
3517 $nl = 0;
3518 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3519 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3520 if (!$renum) {
3521 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = 0";
3522 }
3523 if ($renum) {
3524 $sql .= " AND " . $this->db->sanitize($fieldposition) . " <> 0";
3525 }
3526
3527 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3528 $resql = $this->db->query($sql);
3529 if ($resql) {
3530 $row = $this->db->fetch_row($resql);
3531 $nl = $row[0];
3532 } else {
3533 dol_print_error($this->db);
3534 }
3535 if ($nl > 0) {
3536 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3537 $rows = array();
3538
3539 // We first search all lines that are parent lines (for multilevel details lines)
3540 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3541 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3542 if ($fk_parent_line) {
3543 $sql .= ' AND fk_parent_line IS NULL';
3544 }
3545 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC, rowid " . $this->db->sanitize($rowidorder);
3546
3547 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3548 $resql = $this->db->query($sql);
3549 if ($resql) {
3550 $i = 0;
3551 $num = $this->db->num_rows($resql);
3552 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3553 while ($i < $num) {
3554 $row = $this->db->fetch_row($resql);
3555 $rows[] = $row[0]; // Add parent line into array rows
3556 if ($fk_parent_line) {
3557 $children = $this->getChildrenOfLine($row[0], $grandchild);
3558 }
3559 if (!empty($children)) {
3560 foreach ($children as $child) {
3561 array_push($rows, $child);
3562 }
3563 }
3564 $i++;
3565 }
3566
3567 // Now we set a new number for each lines (parent and children with children included into parent tree)
3568 if (!empty($rows)) {
3569 foreach ($rows as $key => $row) {
3570 $this->updateRangOfLine($row, ($key + 1));
3571 }
3572 }
3573 } else {
3574 dol_print_error($this->db);
3575 }
3576 }
3577 return 1;
3578 }
3579
3587 public function getChildrenOfLine($id, $includealltree = 0)
3588 {
3589 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3590 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3591 $fieldposition = 'position';
3592 }
3593
3594 $rows = array();
3595
3596 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3597 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3598 $sql .= ' AND fk_parent_line = '.((int) $id);
3599 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC";
3600
3601 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3602
3603 $resql = $this->db->query($sql);
3604 if (!$resql || $this->db->num_rows($resql) <= 0) {
3605 return array();
3606 }
3607
3608 while ($row = $this->db->fetch_row($resql)) {
3609 $rows[] = $row[0];
3610 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3611 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3612 }
3613 }
3614 return $rows;
3615 }
3616
3617 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3625 public function line_up($rowid, $fk_parent_line = true)
3626 {
3627 // phpcs:enable
3628 $this->line_order(false, 'ASC', $fk_parent_line);
3629
3630 // Get rang of line
3631 $rang = $this->getRangOfLine($rowid);
3632
3633 // Update position of line
3634 $this->updateLineUp($rowid, $rang);
3635 }
3636
3637 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3645 public function line_down($rowid, $fk_parent_line = true)
3646 {
3647 // phpcs:enable
3648 $this->line_order(false, 'ASC', $fk_parent_line);
3649
3650 // Get rang of line
3651 $rang = $this->getRangOfLine($rowid);
3652
3653 // Get max value for rang
3654 $max = $this->line_max();
3655
3656 // Update position of line
3657 $this->updateLineDown($rowid, $rang, $max);
3658 }
3659
3667 public function updateRangOfLine($rowid, $rang)
3668 {
3669 global $hookmanager;
3670 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3671 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3672 $fieldposition = 'position';
3673 }
3674
3675 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3676 $sql .= ' WHERE rowid = '.((int) $rowid);
3677
3678 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3679 if (!$this->db->query($sql)) {
3680 dol_print_error($this->db);
3681 return -1;
3682 }
3683
3684 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3685 $action = '';
3686 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3687 return 1;
3688 }
3689
3690 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3697 public function line_ajaxorder($rows)
3698 {
3699 // phpcs:enable
3700 $num = count($rows);
3701 for ($i = 0; $i < $num; $i++) {
3702 $this->updateRangOfLine($rows[$i], ($i + 1));
3703 }
3704 }
3705
3713 public function updateLineUp($rowid, $rang)
3714 {
3715 if ($rang <= 1) {
3716 return -1;
3717 }
3718
3719 $fieldposition = 'rang';
3720 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3721 $fieldposition = 'position';
3722 }
3723
3724 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3725 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3726 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang - 1));
3727 if (!$this->db->query($sql)) {
3728 $this->error = $this->db->lasterror();
3729 return -1;
3730 }
3731
3732 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang - 1));
3733 $sql .= ' WHERE rowid = '.((int) $rowid);
3734 if (!$this->db->query($sql)) {
3735 $this->error = $this->db->lasterror();
3736 return -1;
3737 }
3738
3739 return 1;
3740 }
3741
3750 public function updateLineDown($rowid, $rang, $max)
3751 {
3752 if ($rang >= $max) {
3753 return -1;
3754 }
3755
3756 $fieldposition = 'rang';
3757 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3758 $fieldposition = 'position';
3759 }
3760
3761 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3762 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3763 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang + 1));
3764 if (!$this->db->query($sql)) {
3765 $this->error = $this->db->lasterror();
3766 return -1;
3767 }
3768
3769 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang + 1));
3770 $sql .= ' WHERE rowid = '.((int) $rowid);
3771 if (!$this->db->query($sql)) {
3772 $this->error = $this->db->lasterror();
3773 return -1;
3774 }
3775
3776 return 1;
3777 }
3778
3785 public function getRangOfLine($rowid)
3786 {
3787 $fieldposition = 'rang';
3788 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3789 $fieldposition = 'position';
3790 }
3791
3792 $sql = "SELECT " . $this->db->sanitize($fieldposition) . " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3793 $sql .= " WHERE rowid = ".((int) $rowid);
3794
3795 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3796 $resql = $this->db->query($sql);
3797 if (!$resql) {
3798 return 0;
3799 }
3800
3801 $row = $this->db->fetch_row($resql);
3802 return $row[0];
3803 }
3804
3811 public function getIdOfLine($rang)
3812 {
3813 $fieldposition = 'rang';
3814 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3815 $fieldposition = 'position';
3816 }
3817
3818 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3819 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3820 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = ".((int) $rang);
3821 $resql = $this->db->query($sql);
3822 if (!$resql) {
3823 return 0;
3824 }
3825
3826 $row = $this->db->fetch_row($resql);
3827 return $row[0];
3828 }
3829
3830 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3837 public function line_max($fk_parent_line = 0)
3838 {
3839 // phpcs:enable
3840 $positionfield = 'rang';
3841 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3842 $positionfield = 'position';
3843 }
3844
3845 $sql = "SELECT max(".$this->db->sanitize($positionfield).") FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3846 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3847
3848 // Search the last rang with fk_parent_line
3849 if ($fk_parent_line) {
3850 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3851 }
3852
3853 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3854 $resql = $this->db->query($sql);
3855 if ($resql) {
3856 $row = $this->db->fetch_row($resql);
3857
3858 if ($fk_parent_line) {
3859 if (!empty($row[0])) {
3860 return $row[0];
3861 } else {
3862 return $this->getRangOfLine($fk_parent_line);
3863 }
3864 } else {
3865 return $row[0];
3866 }
3867 }
3868
3869 return 0;
3870 }
3871
3872 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3879 public function update_ref_ext($ref_ext)
3880 {
3881 // phpcs:enable
3882 if (!$this->table_element) {
3883 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3884 return -1;
3885 }
3886
3887 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3888 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3889 // @phan-suppress-next-line PhanUndeclaredProperty
3890 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3891
3892 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3893 if ($this->db->query($sql)) {
3894 $this->ref_ext = $ref_ext;
3895 return 1;
3896 } else {
3897 $this->error = $this->db->error();
3898 return -1;
3899 }
3900 }
3901
3902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3911 public function update_note($note, $suffix = '', $notrigger = 0)
3912 {
3913 // phpcs:enable
3914 global $user;
3915
3916 if (!$this->table_element) {
3917 $this->error = 'update_note was called on object with property table_element not defined';
3918 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3919 return -1;
3920 }
3921 if (!in_array($suffix, array('', '_public', '_private'))) {
3922 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3923 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3924 return -2;
3925 }
3926
3927 $newsuffix = $suffix;
3928
3929 // Special case
3930 if ($this->table_element == 'product' && $newsuffix == '_private') {
3931 $newsuffix = '';
3932 }
3933 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3934 $fieldusermod = "fk_user_mod";
3935 } elseif ($this->table_element == 'ecm_files') {
3936 $fieldusermod = "fk_user_m";
3937 } else {
3938 $fieldusermod = "fk_user_modif";
3939 }
3940 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3941 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3942 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3943 $sql .= " WHERE rowid = ".((int) $this->id);
3944
3945 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3946 if ($this->db->query($sql)) {
3947 if ($suffix == '_public') {
3948 $this->note_public = $note;
3949 } elseif ($suffix == '_private') {
3950 $this->note_private = $note;
3951 } else {
3952 $this->note = $note; // deprecated
3953 $this->note_private = $note;
3954 }
3955 if (empty($notrigger)) {
3956 // TODO Use the $this->TRIGGER_PREFIX when implemented
3957 switch ($this->element) {
3958 case 'societe':
3959 $triggerName = 'COMPANY_MODIFY';
3960 break;
3961 case 'facture':
3962 $triggerName = 'BILL_MODIFY';
3963 break;
3964 case 'invoice_supplier':
3965 $triggerName = 'BILL_SUPPLIER_MODIFY';
3966 break;
3967 case 'facturerec':
3968 $triggerName = 'BILLREC_MODIFIY';
3969 break;
3970 case 'expensereport':
3971 $triggerName = 'EXPENSE_REPORT_MODIFY';
3972 break;
3973 default:
3974 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
3975 }
3976 $ret = $this->call_trigger($triggerName, $user);
3977 if ($ret < 0) {
3978 return -1;
3979 }
3980 }
3981 return 1;
3982 } else {
3983 $this->error = $this->db->lasterror();
3984 return -1;
3985 }
3986 }
3987
3988 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3997 public function update_note_public($note)
3998 {
3999 // phpcs:enable
4000 return $this->update_note($note, '_public');
4001 }
4002
4003 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4014 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4015 {
4016 // phpcs:enable
4017 global $conf, $hookmanager, $action;
4018
4019 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4020 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4021 if ($reshook > 0) {
4022 return 1; // replacement code
4023 } elseif ($reshook < 0) {
4024 return -1; // failure
4025 } // reshook = 0 => execute normal code
4026
4027 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4028 $isElementForSupplier = false;
4029 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4030 $MODULE = "";
4031 if ($this->element == 'propal') {
4032 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4033 } elseif ($this->element == 'commande' || $this->element == 'order') {
4034 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4035 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4036 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4037 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4038 $isElementForSupplier = true;
4039 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4040 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4041 $isElementForSupplier = true;
4042 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4043 } elseif ($this->element == 'supplier_proposal') {
4044 $isElementForSupplier = true;
4045 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4046 }
4047 if ($isElementForSupplier) {
4048 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4049 }
4050
4051 if (!empty($MODULE)) {
4052 if (getDolGlobalString($MODULE)) {
4053 $modsactivated = explode(',', getDolGlobalString($MODULE));
4054 foreach ($modsactivated as $mod) {
4055 if (isModEnabled($mod)) {
4056 return 1; // update was disabled by specific setup
4057 }
4058 }
4059 }
4060 }
4061
4062 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4063
4064 $forcedroundingmode = $roundingadjust;
4065 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4066 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4067 } elseif ($forcedroundingmode == 'auto') {
4068 $forcedroundingmode = '0';
4069 }
4070
4071 $error = 0;
4072
4073 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4074
4075 // Define constants to find lines to sum (field name int the table_element_line not into table_element)
4076 $fieldtva = 'total_tva';
4077 $fieldlocaltax1 = 'total_localtax1';
4078 $fieldlocaltax2 = 'total_localtax2';
4079 $fieldup = 'subprice';
4080 $base_price_type = 'HT';
4081 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4082 $fieldtva = 'tva';
4083 $fieldup = 'pu_ht';
4084 }
4085 if ($this->element == 'invoice_supplier_rec') {
4086 $fieldup = 'pu_ht';
4087 }
4088 if ($this->element == 'expensereport') {
4089 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4090 // 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
4091 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4092 $forcedroundingmode = '0';
4093 $fieldup = 'value_unit';
4094 $base_price_type = 'TTC';
4095 }
4096
4097 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4098 $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,";
4099 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4100 $sql .= ' info_bits, product_type,';
4101 if ($this->element == 'expensereport') {
4102 $sql .= ' comments as description';
4103 } else {
4104 $sql .= ' description';
4105 }
4106 if ($this->table_element_line == 'facturedet') {
4107 $sql .= ', situation_percent';
4108 }
4109 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4110 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
4111 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
4112 if ($exclspec) {
4113 $product_field = 'product_type';
4114 if ($this->table_element_line == 'contratdet') {
4115 $product_field = ''; // contratdet table has no product_type field
4116 }
4117 if ($product_field) {
4118 $sql .= " AND ".$this->db->sanitize($product_field)." <> 9";
4119 }
4120 }
4121 $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
4122
4123 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4124
4125 $resql = $this->db->query($sql);
4126 if ($resql) {
4127 $this->total_ht = 0;
4128 $this->total_tva = 0;
4129 $this->total_localtax1 = 0;
4130 $this->total_localtax2 = 0;
4131 $this->total_ttc = 0;
4132 $total_ht_by_vats = array();
4133 $total_tva_by_vats = array();
4134 $total_ttc_by_vats = array();
4135 $this->multicurrency_total_ht = 0;
4136 $this->multicurrency_total_tva = 0;
4137 $this->multicurrency_total_ttc = 0;
4138
4139 $this->db->begin();
4140
4141 $num = $this->db->num_rows($resql);
4142 $i = 0;
4143 while ($i < $num) {
4144 $obj = $this->db->fetch_object($resql);
4145
4146 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4147 $parameters = array('fk_element' => $obj->rowid);
4148 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4149
4150 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'
4151 // This part of code is to fix data. We should not call it too often.
4152 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4153 $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);
4154
4155 $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.
4156 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4157 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4158 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4159
4160 if ($diff_on_current_total) {
4161 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4162 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4163 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4164 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4165 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4166 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);
4167 $resqlfix = $this->db->query($sqlfix);
4168 if (!$resqlfix) {
4169 dol_print_error($this->db, 'Failed to update line');
4170 }
4171 $obj->total_tva = $tmpcal[1];
4172 $obj->total_ttc = $tmpcal[2];
4173 $obj->multicurrency_total_tva = $tmpcal[17];
4174 $obj->multicurrency_total_ttc = $tmpcal[18];
4175 } elseif ($diff_when_using_price_ht) {
4176 // 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
4177 // 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),
4178 // so we continue only if price HT are same.
4179 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4180 // 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
4181 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4182 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4183 // 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,
4184 // 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.
4185 // 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.
4186 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4187 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4188 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4189 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4190
4191 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);
4192
4193 $resqlfix = $this->db->query($sqlfix);
4194 if (!$resqlfix) {
4195 dol_print_error($this->db, 'Failed to update line');
4196 }
4197 $obj->total_tva = $tmpcal[1];
4198 $obj->total_ttc = $tmpcal[2];
4199 $obj->multicurrency_total_tva = $tmpcal[17];
4200 $obj->multicurrency_total_ttc = $tmpcal[18];
4201 }
4202 }
4203 }
4204 }
4205
4206 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4207 $this->total_tva += $obj->total_tva;
4208 $this->total_localtax1 += $obj->total_localtax1;
4209 $this->total_localtax2 += $obj->total_localtax2;
4210 $this->total_ttc += $obj->total_ttc;
4211 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4212 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4213 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4214
4215 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4216 $total_ht_by_vats[$obj->vatrate] = 0;
4217 }
4218 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4219 $total_tva_by_vats[$obj->vatrate] = 0;
4220 }
4221 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4222 $total_ttc_by_vats[$obj->vatrate] = 0;
4223 }
4224 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4225 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4226 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4227
4228 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4229 if ($base_price_type == 'TTC') {
4230 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4231 } else {
4232 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4233 }
4234 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4235 //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";
4236 if ($diff) {
4237 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4238 if (abs((float) $diff) > $maxdiff) {
4239 // If error is more than 10 times the accuracy of rounding. This should not happen.
4240 $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.';
4241 dol_syslog($errmsg, LOG_WARNING);
4242 $this->error = $errmsg;
4243 $error++;
4244 break;
4245 }
4246
4247 if ($base_price_type == 'TTC') {
4248 $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);
4249 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);
4250 } else {
4251 $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);
4252 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);
4253 }
4254
4255 $resqlfix = $this->db->query($sqlfix);
4256
4257 if (!$resqlfix) {
4258 dol_print_error($this->db, 'Failed to update line');
4259 }
4260
4261 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4262 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4263 if ($base_price_type == 'TTC') {
4264 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4265 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4266 } else {
4267 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4268 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4269 }
4270 }
4271 }
4272
4273 $i++;
4274 }
4275
4276 // Add revenue stamp to total
4277 // @phan-suppress-next-line PhanUndeclaredProperty
4278 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4279 // @phan-suppress-next-line PhanUndeclaredProperty
4280 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4281
4282 // Situations totals
4283 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
4284 '@phan-var-force Facture $this';
4285 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
4286 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4287 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4288 $prev_sits = $this->get_prev_sits();
4289
4290 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4291 $this->total_ht -= $sit->total_ht;
4292 $this->total_tva -= $sit->total_tva;
4293 $this->total_localtax1 -= $sit->total_localtax1;
4294 $this->total_localtax2 -= $sit->total_localtax2;
4295 $this->total_ttc -= $sit->total_ttc;
4296 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4297 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4298 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4299 }
4300 }
4301 }
4302 }
4303
4304 // Clean total
4305 $this->total_ht = (float) price2num($this->total_ht);
4306 $this->total_tva = (float) price2num($this->total_tva);
4307 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4308 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4309 $this->total_ttc = (float) price2num($this->total_ttc);
4310
4311 $this->db->free($resql);
4312
4313 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4314 $fieldht = 'total_ht';
4315 $fieldtva = 'tva';
4316 $fieldlocaltax1 = 'localtax1';
4317 $fieldlocaltax2 = 'localtax2';
4318 $fieldttc = 'total_ttc';
4319 // Specific code for backward compatibility with old field names
4320 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4321 $fieldtva = 'total_tva';
4322 }
4323
4324 if (!$error && empty($nodatabaseupdate)) {
4325 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4326 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4327 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4328 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4329 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4330 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4331 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4332 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4333 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4334 $sql .= " WHERE rowid = ".((int) $this->id);
4335
4336 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4337 $resql = $this->db->query($sql);
4338
4339 if (!$resql) {
4340 $error++;
4341 $this->error = $this->db->lasterror();
4342 $this->errors[] = $this->db->lasterror();
4343 }
4344 }
4345
4346 if (!$error) {
4347 $this->db->commit();
4348 return 1;
4349 } else {
4350 $this->db->rollback();
4351 return -1;
4352 }
4353 } else {
4354 dol_print_error($this->db, 'Bad request in update_price');
4355 return -1;
4356 }
4357 }
4358
4359 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4370 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4371 {
4372 // phpcs:enable
4373 global $user, $hookmanager, $action;
4374 dol_syslog(__METHOD__, LOG_DEBUG);
4375
4376 if (empty($this->origin_type) && !empty($this->origin)) {
4377 $this->origin_type = $this->origin;
4378 }
4379 $origin = (!empty($origin) ? $origin : $this->origin_type);
4380 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4381 $f_user = isset($f_user) ? $f_user : $user;
4382
4383 // Special case
4384 if ($origin == 'order') {
4385 $origin = 'commande';
4386 }
4387 if ($origin == 'invoice') {
4388 $origin = 'facture';
4389 }
4390 if ($origin == 'invoice_template') {
4391 $origin = 'facturerec';
4392 }
4393 if ($origin == 'supplierorder') {
4394 $origin = 'order_supplier';
4395 }
4396
4397 // Add module part to target type
4398 $targettype = $this->getElementType();
4399
4400 $parameters = array('targettype' => $targettype);
4401 // Hook for explicitly set the targettype if it must be different than $this->element
4402 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4403 if ($reshook > 0) {
4404 if (!empty($hookmanager->resArray['targettype'])) {
4405 $targettype = $hookmanager->resArray['targettype'];
4406 }
4407 }
4408
4409 $this->db->begin();
4410 $error = 0;
4411
4412 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4413 $sql .= "fk_source";
4414 $sql .= ", sourcetype";
4415 $sql .= ", fk_target";
4416 $sql .= ", targettype";
4417 $sql .= ") VALUES (";
4418 $sql .= ((int) $origin_id);
4419 $sql .= ", '" . $this->db->escape($origin) . "'";
4420 $sql .= ", " . ((int) $this->id);
4421 $sql .= ", '" . $this->db->escape($targettype) . "'";
4422 $sql .= ")";
4423
4424 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4425 if ($this->db->query($sql)) {
4426 // If we link a supplier order to a supplier invoice that already uses discounts coming from other supplier invoices
4427 // (deposit invoice / credit note used as payment), also link those source invoices to the same supplier order.
4428 // This helps keep "linked elements" consistent when totals are impacted by such discounts.
4429 if ($origin === 'order_supplier' && $targettype === 'invoice_supplier' && $this->element === 'invoice_supplier' && (int) $origin_id > 0 && (int) $this->id > 0) {
4430 $sourceinvoices = array();
4431
4432 $sqlsources = "SELECT DISTINCT rc.fk_invoice_supplier_source as rowid";
4433 $sqlsources .= " FROM ".$this->db->prefix()."societe_remise_except as rc";
4434 $sqlsources .= " WHERE rc.fk_invoice_supplier = ".((int) $this->id);
4435 $sqlsources .= " AND rc.fk_invoice_supplier_source IS NOT NULL";
4436
4437 $resqlsources = $this->db->query($sqlsources);
4438 if ($resqlsources) {
4439 while ($objsrc = $this->db->fetch_object($resqlsources)) {
4440 $srcid = (int) $objsrc->rowid;
4441 if ($srcid > 0 && $srcid !== (int) $this->id) {
4442 $sourceinvoices[$srcid] = $srcid;
4443 }
4444 }
4445 } else {
4446 dol_syslog(get_class($this)."::add_object_linked Failed to read supplier invoice discounts sources", LOG_WARNING);
4447 }
4448
4449 if (!empty($sourceinvoices)) {
4450 $existing = array();
4451 $sourcelist = implode(',', $sourceinvoices);
4452
4453 $sqlexists = "SELECT fk_target";
4454 $sqlexists .= " FROM ".$this->db->prefix()."element_element";
4455 $sqlexists .= " WHERE fk_source = ".((int) $origin_id);
4456 $sqlexists .= " AND sourcetype = '".$this->db->escape($origin)."'";
4457 $sqlexists .= " AND targettype = '".$this->db->escape($targettype)."'";
4458 $sqlexists .= " AND fk_target IN (".$this->db->sanitize($sourcelist).")";
4459
4460 $resqlexists = $this->db->query($sqlexists);
4461 if ($resqlexists) {
4462 while ($objexist = $this->db->fetch_object($resqlexists)) {
4463 $existing[(int) $objexist->fk_target] = 1;
4464 }
4465 } else {
4466 dol_syslog(get_class($this)."::add_object_linked Failed to read existing linked supplier invoices", LOG_WARNING);
4467 }
4468
4469 foreach ($sourceinvoices as $srcid) {
4470 if (!empty($existing[(int) $srcid])) {
4471 continue;
4472 }
4473
4474 $sqladd = "INSERT INTO " . $this->db->prefix() . "element_element (fk_source, sourcetype, fk_target, targettype)";
4475 $sqladd .= " VALUES (".((int) $origin_id).", '".$this->db->escape($origin)."', ".((int) $srcid).", '".$this->db->escape($targettype)."')";
4476 $this->db->query($sqladd); // Best-effort: do not fail original link action
4477 }
4478 }
4479 }
4480
4481 if (!$notrigger) {
4482 // Call trigger
4483 $this->context['link_origin'] = $origin;
4484 $this->context['link_origin_id'] = $origin_id;
4485
4486 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4487 if ($result < 0) {
4488 $error++;
4489 }
4490 // End call triggers
4491 }
4492 } else {
4493 $this->error = $this->db->lasterror();
4494 $error++;
4495 }
4496
4497 if (!$error) {
4498 $this->db->commit();
4499 return 1;
4500 } else {
4501 $this->db->rollback();
4502 return 0;
4503 }
4504 }
4505
4511 public function getElementType()
4512 {
4513 // 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.
4514 // 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).
4515 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4516 // Add module part to target type if object has $module property and isn't in core modules.
4517 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4518 }
4519
4520
4543 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4544 {
4545 global $hookmanager, $action;
4546
4547 // Important for pdf generation time reduction
4548 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4549 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4550 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4551 return 1;
4552 }
4553
4554 $this->linkedObjectsIds = array();
4555 $this->linkedObjects = array();
4556
4557 // Hook for allowing modules to completely alter the behavior of the method
4558 $parameters = array(
4559 'sourceid' => $sourceid,
4560 'sourcetype' => $sourcetype,
4561 'targetid' => $targetid,
4562 'targettype' => $targettype,
4563 'clause' => $clause,
4564 'alsosametype' => $alsosametype,
4565 'orderby' => $orderby,
4566 'loadalsoobjects' => $loadalsoobjects
4567 );
4568 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4569 if ($reshook > 0) {
4570 return $reshook;
4571 }
4572
4573 $justsource = false;
4574 $justtarget = false;
4575 $withtargettype = false;
4576 $withsourcetype = false;
4577
4578 // Hook for explicitly set the targettype if it must be differtent than $this->element
4579 if (is_object($hookmanager)) {
4580 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4581 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4582 if ($reshook > 0) {
4583 if (!empty($hookmanager->resArray['sourcetype'])) {
4584 $sourcetype = $hookmanager->resArray['sourcetype'];
4585 }
4586 if (!empty($hookmanager->resArray['sourceid'])) {
4587 $sourceid = $hookmanager->resArray['sourceid'];
4588 }
4589 if (!empty($hookmanager->resArray['targettype'])) {
4590 $targettype = $hookmanager->resArray['targettype'];
4591 }
4592 if (!empty($hookmanager->resArray['targetid'])) {
4593 $targetid = $hookmanager->resArray['targetid'];
4594 }
4595 }
4596 }
4597
4598 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4599 $justsource = true; // the source (id and type) is a search criteria
4600 if (!empty($targettype)) {
4601 $withtargettype = true;
4602 }
4603 }
4604 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4605 $justtarget = true; // the target (id and type) is a search criteria
4606 if (!empty($sourcetype)) {
4607 $withsourcetype = true;
4608 }
4609 }
4610
4611 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4612 $targetid = (!empty($targetid) ? $targetid : $this->id);
4613 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4614 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4615
4616 /*if (empty($sourceid) && empty($targetid))
4617 {
4618 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4619 return -1;
4620 }*/
4621
4622 // Links between objects are stored in table element_element
4623 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4624 $sql .= " FROM ".$this->db->prefix()."element_element";
4625 $sql .= " WHERE ";
4626 if ($justsource || $justtarget) {
4627 if ($justsource) {
4628 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4629 if ($withtargettype) {
4630 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4631 }
4632 } elseif ($justtarget) {
4633 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4634 if ($withsourcetype) {
4635 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4636 }
4637 }
4638 } else {
4639 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4640 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4641 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4642 $this->linkedObjectsFullLoaded[$this->id] = true;
4643 }
4644 }
4645 $sql .= $this->db->order($orderby);
4646
4647 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4648
4649 $resql = $this->db->query($sql);
4650 if ($resql) {
4651 $num = $this->db->num_rows($resql);
4652 $i = 0;
4653 while ($i < $num) {
4654 $obj = $this->db->fetch_object($resql);
4655 if ($justsource || $justtarget) {
4656 if ($justsource) {
4657 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4658 } elseif ($justtarget) {
4659 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4660 }
4661 } else {
4662 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4663 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4664 }
4665 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4666 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4667 }
4668 }
4669 $i++;
4670 }
4671
4672 if (!empty($this->linkedObjectsIds)) {
4673 $tmparray = $this->linkedObjectsIds;
4674 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4675 $element_properties = getElementProperties($objecttype);
4676 $element = $element_properties['element'];
4677 $classPath = $element_properties['classpath'];
4678 $classFile = $element_properties['classfile'];
4679 $className = $element_properties['classname'];
4680 $module = $element_properties['module'];
4681
4682 // Here $module, $classFile and $className are set, we can use them.
4683 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4684 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4685 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4686 if (class_exists($className)) {
4687 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4688 $object = new $className($this->db);
4689 '@phan-var-force CommonObject $object';
4690 $ret = $object->fetch($objectid);
4691 if ($ret >= 0) {
4692 $this->linkedObjects[$objecttype][$i] = $object;
4693 }
4694 }
4695 }
4696 }
4697 } else {
4698 unset($this->linkedObjectsIds[$objecttype]);
4699 }
4700 }
4701 }
4702 return 1;
4703 } else {
4704 dol_print_error($this->db);
4705 return -1;
4706 }
4707 }
4708
4715 public function clearObjectLinkedCache()
4716 {
4717 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4718 unset($this->linkedObjectsFullLoaded[$this->id]);
4719 }
4720
4721 return 1;
4722 }
4723
4736 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4737 {
4738 global $user;
4739 $updatesource = false;
4740 $updatetarget = false;
4741 $f_user = isset($f_user) ? $f_user : $user;
4742 dol_syslog(__METHOD__, LOG_DEBUG);
4743
4744 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4745 $updatesource = true;
4746 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4747 $updatetarget = true;
4748 }
4749
4750 $this->db->begin();
4751 $error = 0;
4752
4753 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4754 if ($updatesource) {
4755 $sql .= "fk_source = " . ((int) $sourceid);
4756 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4757 $sql .= " WHERE fk_target = " . ((int) $this->id);
4758 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4759 } elseif ($updatetarget) {
4760 $sql .= "fk_target = " . ((int) $targetid);
4761 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4762 $sql .= " WHERE fk_source = " . ((int) $this->id);
4763 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4764 }
4765
4766 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4767 if ($this->db->query($sql)) {
4768 if (!$notrigger) {
4769 // Call trigger
4770 $this->context['link_source_id'] = $sourceid;
4771 $this->context['link_source_type'] = $sourcetype;
4772 $this->context['link_target_id'] = $targetid;
4773 $this->context['link_target_type'] = $targettype;
4774
4775 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4776 if ($result < 0) {
4777 $error++;
4778 }
4779 // End call triggers
4780 }
4781 } else {
4782 $this->error = $this->db->lasterror();
4783 $error++;
4784 }
4785
4786 if (!$error) {
4787 $this->db->commit();
4788 return 1;
4789 } else {
4790 $this->db->rollback();
4791 return -1;
4792 }
4793 }
4794
4808 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4809 {
4810 global $user;
4811 $deletesource = false;
4812 $deletetarget = false;
4813 $f_user = isset($f_user) ? $f_user : $user;
4814
4815 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4816 $deletesource = true;
4817 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4818 $deletetarget = true;
4819 }
4820
4821 $element = $this->getElementType();
4822 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4823 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4824 $targetid = (!empty($targetid) ? $targetid : $this->id);
4825 $targettype = (!empty($targettype) ? $targettype : $element);
4826 $this->db->begin();
4827 $error = 0;
4828
4829 if (!$notrigger) {
4830 // Call trigger
4831 $this->context['link_id'] = $rowid;
4832 $this->context['link_source_id'] = $sourceid;
4833 $this->context['link_source_type'] = $sourcetype;
4834 $this->context['link_target_id'] = $targetid;
4835 $this->context['link_target_type'] = $targettype;
4836
4837 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4838 if ($result < 0) {
4839 $error++;
4840 }
4841 // End call triggers
4842 }
4843
4844 if (!$error) {
4845 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4846 $sql .= " WHERE";
4847 if ($rowid > 0) {
4848 $sql .= " rowid = " . ((int) $rowid);
4849 } else {
4850 if ($deletesource) {
4851 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4852 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4853 } elseif ($deletetarget) {
4854 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4855 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4856 } else {
4857 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4858 $sql .= " OR";
4859 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4860 }
4861 }
4862
4863 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4864 if (!$this->db->query($sql)) {
4865 $this->error = $this->db->lasterror();
4866 $this->errors[] = $this->error;
4867 $error++;
4868 }
4869 }
4870
4871 if (!$error) {
4872 $this->db->commit();
4873 return 1;
4874 } else {
4875 $this->db->rollback();
4876 return 0;
4877 }
4878 }
4879
4889 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4890 {
4891 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4892 return -1;
4893 }
4894 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4895 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4896 }
4897
4898 global $db;
4899
4900 $sql = "SELECT ".$db->sanitize($field_select)." FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4901 $resql = $db->query($sql);
4902
4903 $TRes = array();
4904 if (!empty($resql)) {
4905 while ($res = $db->fetch_object($resql)) {
4906 $TRes[] = $res->{$field_select};
4907 }
4908 }
4909
4910 return $TRes;
4911 }
4912
4921 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4922 {
4923 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4924 return -1;
4925 }
4926
4927 global $db;
4928
4929 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4930 $resql = $db->query($sql);
4931 $n = 0;
4932 if ($resql) {
4933 $res = $db->fetch_object($resql);
4934 if ($res) {
4935 $n = $res->nb;
4936 }
4937 }
4938
4939 return $n;
4940 }
4941
4950 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4951 {
4952 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4953 return -1;
4954 }
4955
4956 global $db;
4957
4958 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4959 $resql = $db->query($sql);
4960
4961 if (empty($resql)) {
4962 return 0;
4963 }
4964
4965 return 1;
4966 }
4967
4979 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
4980 {
4981 global $user;
4982
4983 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4984
4985 $elementId = (!empty($elementId) ? $elementId : $this->id);
4986 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4987
4988 $this->db->begin();
4989
4990 if (empty($fieldstatus)) {
4991 if ($elementTable == 'facture_rec') {
4992 $fieldstatus = "suspended";
4993 }
4994 if ($elementTable == 'mailing') {
4995 $fieldstatus = "statut";
4996 }
4997 if ($elementTable == 'cronjob') {
4998 $fieldstatus = "status";
4999 }
5000 if ($elementTable == 'user') {
5001 $fieldstatus = "statut";
5002 }
5003 if ($elementTable == 'expensereport') {
5004 $fieldstatus = "fk_statut";
5005 }
5006 if ($elementTable == 'receptiondet_batch') {
5007 $fieldstatus = "status";
5008 }
5009 if ($elementTable == 'prelevement_bons') {
5010 $fieldstatus = "statut";
5011 }
5012 if ($elementTable == 'bank_account') {
5013 $fieldstatus = "clos";
5014 }
5015 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
5016 $fieldstatus = 'status';
5017 }
5018 // If still empty
5019 if (empty($fieldstatus)) {
5020 $fieldstatus = 'fk_statut';
5021 }
5022 }
5023
5024 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
5025 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
5026 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
5027 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
5028 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
5029 $sql .= ", fk_user_valid = ".((int) $user->id);
5030 }
5031 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
5032 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
5033 }
5034 if ($status == 1 && in_array($elementTable, array('inventory'))) {
5035 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
5036 }
5037 $sql .= " WHERE rowid = ".((int) $elementId);
5038 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
5039
5040 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
5041 $resql = $this->db->query($sql);
5042 if ($resql) {
5043 $error = 0;
5044
5045 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
5046
5047 if ($nb_rows_affected > 0) {
5048 if (empty($trigkey)) {
5049 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5050 if ($this->element == 'supplier_proposal' && $status == 2) {
5051 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5052 }
5053 if ($this->element == 'supplier_proposal' && $status == 3) {
5054 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5055 }
5056 if ($this->element == 'supplier_proposal' && $status == 4) {
5057 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5058 }
5059 if ($this->element == 'fichinter' && $status == 3) {
5060 $trigkey = 'FICHINTER_CLASSIFY_DONE';
5061 }
5062 if ($this->element == 'fichinter' && $status == 2) {
5063 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5064 }
5065 if ($this->element == 'fichinter' && $status == 1) {
5066 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5067 }
5068 }
5069
5070 $this->context = array_merge($this->context, array('newstatus' => $status));
5071
5072 if ($trigkey && $trigkey != 'none') {
5073 $this->oldcopy = dol_clone($this);
5074
5075 // Call trigger
5076 $result = $this->call_trigger($trigkey, $user);
5077 if ($result < 0) {
5078 $error++;
5079 }
5080 // End call triggers
5081 }
5082 } else {
5083 // The status was probably already good. We do nothing more, no triggers.
5084 }
5085
5086 if (!$error) {
5087 $this->db->commit();
5088
5089 if (empty($savElementId)) {
5090 // If the element we update is $this (so $elementId was provided as null)
5091 if ($fieldstatus == 'dispute_status') {
5092 $this->dispute_status = $status;
5093 } elseif ($fieldstatus == 'tosell') {
5094 $this->status = $status;
5095 } elseif ($fieldstatus == 'tobuy') {
5096 $this->status_buy = $status; // @phpstan-ignore-line
5097 } elseif ($fieldstatus == 'tobatch') {
5098 $this->status_batch = $status; // @phpstan-ignore-line
5099 } else {
5100 $this->status = $status;
5101 }
5102 }
5103
5104 return 1;
5105 } else {
5106 $this->db->rollback();
5107 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5108 return -1;
5109 }
5110 } else {
5111 $this->error = $this->db->lasterror();
5112 $this->db->rollback();
5113 return -1;
5114 }
5115 }
5116
5117
5125 public function getCanvas($id = 0, $ref = '')
5126 {
5127 if (empty($id) && empty($ref)) {
5128 return 0;
5129 }
5130 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5131 return 0; // To increase speed. Not enabled by default.
5132 }
5133
5134 // Clean parameters
5135 $ref = trim($ref);
5136
5137 $sql = "SELECT rowid, canvas";
5138 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5139 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5140 if (!empty($id)) {
5141 $sql .= " AND rowid = ".((int) $id);
5142 }
5143 if (!empty($ref)) {
5144 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5145 }
5146
5147 $resql = $this->db->query($sql);
5148 if ($resql) {
5149 $obj = $this->db->fetch_object($resql);
5150 if ($obj) {
5151 $this->canvas = $obj->canvas;
5152 return 1;
5153 } else {
5154 return 0;
5155 }
5156 } else {
5157 dol_print_error($this->db);
5158 return -1;
5159 }
5160 }
5161
5162
5169 public function getSpecialCode($lineid)
5170 {
5171 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
5172 $sql .= " WHERE rowid = ".((int) $lineid);
5173 $resql = $this->db->query($sql);
5174 if ($resql) {
5175 $row = $this->db->fetch_row($resql);
5176 return (!empty($row[0]) ? $row[0] : 0);
5177 }
5178
5179 return 0;
5180 }
5181
5190 public function isObjectUsed($id = 0, $entity = 0)
5191 {
5192 global $langs;
5193
5194 if (empty($id)) {
5195 $id = $this->id;
5196 }
5197
5198 // Check parameters
5199 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5200 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5201 return -1;
5202 }
5203
5204 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5205 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5206 $tmparray = array_keys($this->childtables);
5207 if (is_numeric($tmparray[0])) {
5208 $arraytoscan = array_flip($this->childtables);
5209 }
5210
5211 // Test on all child tables to scan if child exists
5212 $haschild = 0;
5213 foreach ($arraytoscan as $table => $element) {
5214 //print $id.'-'.$table.'-'.$elementname.'<br>';
5215
5216 // Check if module is enabled (to avoid error if tables of module not created)
5217 if (isset($element['enabled']) && !empty($element['enabled'])) {
5218 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5219 if (empty($enabled)) {
5220 continue;
5221 }
5222 }
5223
5224 // Check if element can be deleted
5225 $sql = "SELECT COUNT(*) as nb";
5226 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($table)." as c";
5227 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5228 $sql .= ", ".$this->db->prefix().$this->db->sanitize($element['parent'])." as p";
5229 }
5230 if (!empty($element['fk_element'])) {
5231 $sql .= " WHERE c.".$this->db->sanitize($element['fk_element'])." = ".((int) $id);
5232 } else {
5233 $sql .= " WHERE c.".$this->db->sanitize($this->fk_element)." = ".((int) $id);
5234 }
5235 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5236 $sql .= " AND c.".$this->db->sanitize($element['parentkey'])." = p.rowid";
5237 }
5238 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5239 $sql .= " AND c.".$this->db->sanitize($element['parenttypefield'])." = '".$this->db->escape($element['parenttypevalue'])."'";
5240 }
5241 if (!empty($entity)) {
5242 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5243 $sql .= " AND p.entity = ".((int) $entity);
5244 } else {
5245 $sql .= " AND c.entity = ".((int) $entity);
5246 }
5247 }
5248 //var_dump($table, $element, $sql);
5249
5250 $resql = $this->db->query($sql);
5251 if ($resql) {
5252 $obj = $this->db->fetch_object($resql);
5253 if ($obj->nb > 0) {
5254 $langs->load("errors");
5255 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5256 $haschild += (int) $obj->nb;
5257 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5258 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5259 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5260 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5261 } else { // new usage: $element['name']=Translation key
5262 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5263 }
5264 break; // We found at least one, we stop here
5265 }
5266 } else {
5267 $this->errors[] = $this->db->lasterror();
5268 return -1;
5269 }
5270 }
5271
5272 if ($haschild > 0) {
5273 $this->errors[] = "ErrorRecordHasChildren";
5274 return $haschild;
5275 } else {
5276 return 0;
5277 }
5278 }
5279
5286 public function hasProductsOrServices($predefined = -1)
5287 {
5288 $nb = 0;
5289
5290 foreach ($this->lines as $key => $val) {
5291 $qualified = 0;
5292 if ($predefined == -1) {
5293 $qualified = 1;
5294 }
5295 if ($predefined == 1 && $val->fk_product > 0) {
5296 $qualified = 1;
5297 }
5298 if ($predefined == 0 && $val->fk_product <= 0) {
5299 $qualified = 1;
5300 }
5301 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5302 $qualified = 1;
5303 }
5304 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5305 $qualified = 1;
5306 }
5307 if ($qualified) {
5308 $nb++;
5309 }
5310 }
5311 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5312 return $nb;
5313 }
5314
5320 public function getTotalDiscount()
5321 {
5322 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5323 $total_discount = 0.00;
5324
5325 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5326 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5327 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
5328
5329 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5330 $resql = $this->db->query($sql);
5331 if ($resql) {
5332 $num = $this->db->num_rows($resql);
5333 $i = 0;
5334 while ($i < $num) {
5335 $obj = $this->db->fetch_object($resql);
5336
5337 $pu_ht = $obj->pu_ht;
5338 $qty = $obj->qty;
5339 $total_ht = $obj->total_ht;
5340
5341 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5342 $total_discount += $total_discount_line;
5343
5344 $i++;
5345 }
5346 }
5347
5348 //print $total_discount; exit;
5349 return (float) price2num($total_discount);
5350 }
5351
5352 return null;
5353 }
5354
5355
5362 public function getTotalWeightVolume()
5363 {
5364 $totalWeight = 0;
5365 $totalVolume = 0;
5366 // defined for shipment only
5367 $totalOrdered = 0;
5368 // defined for shipment only
5369 $totalToShip = 0;
5370
5371 if (empty($this->lines)) {
5372 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5373 }
5374
5375 foreach ($this->lines as $line) {
5376 if (isset($line->qty_asked)) {
5377 $totalOrdered += $line->qty_asked; // defined for shipment only
5378 }
5379 if (isset($line->qty_shipped)) {
5380 $totalToShip += $line->qty_shipped; // defined for shipment only
5381 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5382 if (empty($totalToShip)) {
5383 $totalToShip = 0;
5384 }
5385 $totalToShip += $line->qty; // defined for reception only
5386 }
5387
5388 // Define qty, weight, volume, weight_units, volume_units
5389 if ($this->element == 'shipping') {
5390 // for shipments
5391 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5392 } else {
5393 $qty = $line->qty ? $line->qty : 0;
5394 }
5395
5396 $weight = !empty($line->weight) ? $line->weight : 0;
5397 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5398 $volume = !empty($line->volume) ? $line->volume : 0;
5399 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5400
5401 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5402 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5403 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5404 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5405
5406 $weightUnit = 0;
5407 $volumeUnit = 0;
5408 if (!empty($weight_units)) {
5409 $weightUnit = $weight_units;
5410 }
5411 if (!empty($volume_units)) {
5412 $volumeUnit = $volume_units;
5413 }
5414
5415 if (empty($totalWeight)) {
5416 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5417 }
5418 if (empty($totalVolume)) {
5419 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5420 }
5421
5422 //var_dump($line->volume_units);
5423 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5424 $trueWeightUnit = pow(10, $weightUnit);
5425 $totalWeight += $weight * $qty * $trueWeightUnit;
5426 } else {
5427 if ($weight_units == 99) {
5428 // conversion 1 Pound = 0.45359237 KG
5429 $trueWeightUnit = 0.45359237;
5430 $totalWeight += $weight * $qty * $trueWeightUnit;
5431 } elseif ($weight_units == 98) {
5432 // conversion 1 Ounce = 0.0283495 KG
5433 $trueWeightUnit = 0.0283495;
5434 $totalWeight += $weight * $qty * $trueWeightUnit;
5435 } else {
5436 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5437 }
5438 }
5439 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5440 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5441 $trueVolumeUnit = pow(10, $volumeUnit);
5442 //print $line->volume;
5443 $totalVolume += $volume * $qty * $trueVolumeUnit;
5444 } else {
5445 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5446 }
5447 }
5448
5449 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5450 }
5451
5452
5458 public function setExtraParameters()
5459 {
5460 $this->db->begin();
5461
5462 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5463 $extraparams = dol_trunc($extraparams, 250);
5464
5465 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5466 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5467 $sql .= " WHERE rowid = ".((int) $this->id);
5468
5469 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5470 $resql = $this->db->query($sql);
5471 if (!$resql) {
5472 $this->error = $this->db->lasterror();
5473 $this->db->rollback();
5474 return -1;
5475 } else {
5476 $this->db->commit();
5477 return 1;
5478 }
5479 }
5480
5481
5482 // --------------------
5483 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5484 // --------------------
5485
5486 /* This is to show add lines */
5487
5497 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5498 {
5499 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5500
5501 // Line extrafield
5502 if (!is_object($extrafields)) {
5503 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5504 $extrafields = new ExtraFields($this->db);
5505 }
5506 $extrafields->fetch_name_optionals_label($this->table_element_line);
5507
5508 // Output template part (modules that overwrite templates must declare this into descriptor)
5509 // Use global variables + $dateSelector + $seller and $buyer
5510 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5511 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5512 foreach ($dirtpls as $module => $reldir) {
5513 if (!empty($module)) {
5514 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5515 } else {
5516 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5517 }
5518
5519 if (empty($conf->file->strict_mode)) {
5520 $res = @include $tpl;
5521 } else {
5522 $res = include $tpl; // for debug
5523 }
5524 if ($res) {
5525 break;
5526 }
5527 }
5528 }
5529
5530
5531
5532 /* This is to show array of line of details */
5533
5534
5549 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5550 {
5551 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5552 // TODO We should not use global var for this
5553 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5554
5555 // Define $usemargins (used by objectline_xxx.tpl.php files)
5556 $usemargins = 0;
5557 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5558 $usemargins = 1;
5559 }
5560
5561 $num = count($this->lines);
5562
5563 // Line extrafield
5564 if (!is_object($extrafields)) {
5565 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5566 $extrafields = new ExtraFields($this->db);
5567 }
5568 $extrafields->fetch_name_optionals_label($this->table_element_line);
5569
5570 if (method_exists($this, 'loadExpeditions')) {
5571 // 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.
5572 $this->loadExpeditions();
5573 }
5574
5575 $parameters = array();
5576 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5577 if (empty($reshook)) {
5578 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5579 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5580 if (empty($reshook)) {
5581 // Output template part (modules that overwrite templates must declare this into descriptor)
5582 // Use global variables + $dateSelector + $seller and $buyer
5583 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5584 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5585 foreach ($dirtpls as $module => $reldir) {
5586 $res = 0;
5587 if (!empty($module)) {
5588 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5589 } else {
5590 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5591 }
5592 if (file_exists($tpl)) {
5593 if (empty($conf->file->strict_mode)) {
5594 $res = @include $tpl;
5595 } else {
5596 $res = include $tpl; // for debug
5597 }
5598 }
5599 if ($res) {
5600 break;
5601 }
5602 }
5603 }
5604
5605 $i = 0;
5606
5607 print "<!-- begin printObjectLines() -->\n";
5608 foreach ($this->lines as $line) {
5609 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5610 //$line->fetch_optionals();
5611
5612 if (is_object($hookmanager)) {
5613 if (empty($line->fk_parent_line)) {
5614 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5615 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5616 } else {
5617 $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);
5618 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5619 }
5620 }
5621 if (empty($reshook)) {
5622 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5623 }
5624
5625 $i++;
5626 }
5627 print "<!-- end printObjectLines() -->\n";
5628 }
5629 }
5630
5648 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5649 {
5650 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5651 global $form;
5652 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5653
5654 // var used into tpl
5655 $text = '';
5656 $description = '';
5657
5658 // Line in view mode
5659 if ($action != 'editline' || $selected != $line->id) {
5660 // Product
5661 if (!empty($line->fk_product) && $line->fk_product > 0) {
5662 $product_static = new Product($this->db);
5663 $product_static->fetch($line->fk_product);
5664
5665 $product_static->ref = (string) $line->ref; //can change ref in hook
5666 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5667
5668 $text = $product_static->getNomUrl(1);
5669
5670 // Define output language and label
5671 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5672 // @phan-suppress-next-line PhanUndeclaredProperty
5673 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5674 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5675 return;
5676 }
5677
5678 $prod = new Product($this->db);
5679 $prod->fetch($line->fk_product);
5680
5681 $outputlangs = $langs;
5682 $newlang = '';
5683 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5684 $newlang = GETPOST('lang_id', 'aZ09');
5685 }
5686 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5687 $newlang = $this->thirdparty->default_lang; // To use language of customer
5688 }
5689 if (!empty($newlang)) {
5690 $outputlangs = new Translate("", $conf);
5691 $outputlangs->setDefaultLang($newlang);
5692 }
5693
5694 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5695 } else {
5696 $label = $line->product_label;
5697 }
5698
5699 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5700 $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.
5701 }
5702
5703 // Recalculate unit price with tax if not defined
5704 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)
5705 // So we calculate an estimated value just to show something on screen
5706 if ($line->remise_percent != 100) {
5707 $line->subprice_ttc = (float) price2num($line->total_ttc / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5708 } else {
5709 // Other method is less accurate
5710 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5711 }
5712 }
5713 $line->pu_ttc = $line->subprice_ttc; // deprecated
5714
5715 // Output template part (modules that overwrite templates must declare this into descriptor)
5716 // Use global variables + $dateSelector + $seller and $buyer
5717 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5718
5719 $qty_shipped = 0;
5720 if (isset($this->expeditions[$line->id])) {
5721 $qty_shipped = $this->expeditions[$line->id];
5722 }
5723 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5724
5725 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5726 foreach ($dirtpls as $module => $reldir) {
5727 $res = 0;
5728 if (!empty($module)) {
5729 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5730 } else {
5731 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5732 }
5733 //var_dump($tpl);
5734 if (file_exists($tpl)) {
5735 if (empty($conf->file->strict_mode)) {
5736 $res = @include $tpl;
5737 } else {
5738 $res = include $tpl; // for debug
5739 }
5740 }
5741 if ($res) {
5742 break;
5743 }
5744 }
5745 }
5746
5747 // Line in update mode
5748 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5749 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5750
5751 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5752 $line->pu_ttc = $line->subprice_ttc; // deprecated
5753
5754 // Output template part (modules that overwrite templates must declare this into descriptor)
5755 // Use global variables + $dateSelector + $seller and $buyer
5756 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5757 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5758 foreach ($dirtpls as $module => $reldir) {
5759 if (!empty($module)) {
5760 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5761 } else {
5762 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5763 }
5764
5765 if (empty($conf->file->strict_mode)) {
5766 $res = @include $tpl;
5767 } else {
5768 $res = include $tpl; // for debug
5769 }
5770 if ($res) {
5771 break;
5772 }
5773 }
5774 }
5775 }
5776
5777
5778 /* This is to show array of line of details of source object */
5779
5780
5791 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5792 {
5793 global $langs, $hookmanager, $form, $action;
5794
5795 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5796 print '<tr class="liste_titre">';
5797 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5798 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5799 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5800 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5801 if (isModEnabled("multicurrency")) {
5802 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5803 }
5804 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5805 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5806 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5807 }
5808 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5809 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5810 print '<td class="center">';
5811 print $form->showCheckAddButtons('checkforselect', 1);
5812 print '</td>';
5813 print '</tr>';
5814 $i = 0;
5815
5816 if (!empty($this->lines)) {
5817 foreach ($this->lines as $line) {
5818 $reshook = 0;
5819 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5820 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5821 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5822 if (!empty($line->fk_parent_line)) {
5823 $parameters['fk_parent_line'] = $line->fk_parent_line;
5824 }
5825 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5826 }
5827 if (empty($reshook)) {
5828 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5829 }
5830
5831 $i++;
5832 }
5833 }
5834 }
5835
5849 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5850 {
5851 global $langs, $conf;
5852
5853 //var_dump($line);
5854 if (!empty($line->date_start)) {
5855 // @phan-suppress-next-line PhanUndeclaredProperty
5856 $date_start = $line->date_start;
5857 } else {
5858 $date_start = $line->date_debut_prevue;
5859 if ($line->date_debut_reel) {
5860 $date_start = $line->date_debut_reel;
5861 }
5862 }
5863 if (!empty($line->date_end)) {
5864 // @phan-suppress-next-line PhanUndeclaredProperty
5865 $date_end = $line->date_end;
5866 } else {
5867 $date_end = $line->date_fin_prevue;
5868 if ($line->date_fin_reel) {
5869 $date_end = $line->date_fin_reel;
5870 }
5871 }
5872
5873 // Set thevalue into ->tpl[] array.
5874 $this->tpl['id'] = $line->id;
5875
5876 $this->tpl['label'] = '';
5877 if (!empty($line->fk_parent_line)) {
5878 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5879 }
5880
5881 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5882 $discount = new DiscountAbsolute($this->db);
5883 if (property_exists($this, 'socid')) {
5884 $discount->fk_soc = $this->socid;
5885 $discount->socid = $this->socid;
5886 }
5887 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5888 } elseif (!empty($line->fk_product)) {
5889 $productstatic = new Product($this->db);
5890 $productstatic->id = $line->fk_product;
5891 $productstatic->ref = (string) $line->ref;
5892 $productstatic->type = $line->fk_product_type;
5893 if (empty($productstatic->ref)) {
5894 $line->fetch_product();
5895 $productstatic = $line->product;
5896 }
5897
5898 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5899 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5900 // Dates
5901 if ($line->product_type == 1 && ($date_start || $date_end)) {
5902 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5903 }
5904 } else {
5905 $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"')));
5906 if (!empty($line->desc)) {
5907 $this->tpl['label'] .= $line->desc;
5908 } else {
5909 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5910 }
5911
5912 // Dates
5913 if ($line->product_type == 1 && ($date_start || $date_end)) {
5914 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5915 }
5916 }
5917
5918 if (!empty($line->desc)) {
5919 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5920 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5921 $discount = new DiscountAbsolute($this->db);
5922 $discount->fetch($line->fk_remise_except);
5923 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5924 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5925 $discount = new DiscountAbsolute($this->db);
5926 $discount->fetch($line->fk_remise_except);
5927 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5928 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5929 $discount = new DiscountAbsolute($this->db);
5930 $discount->fetch($line->fk_remise_except);
5931 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5932 } elseif ($line->desc == '(EXCESS PAID)') {
5933 $discount = new DiscountAbsolute($this->db);
5934 $discount->fetch($line->fk_remise_except);
5935 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5936 } else {
5937 $this->tpl['description'] = dol_trunc($line->desc, 60);
5938 }
5939 } else {
5940 $this->tpl['description'] = '&nbsp;';
5941 }
5942
5943 // VAT Rate
5944 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5945 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
5946 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5947 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5948 }
5949
5950 $this->tpl['price'] = price($line->subprice);
5951 $this->tpl['total_ht'] = price($line->total_ht);
5952 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5953 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5954 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5955 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
5956 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
5957 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
5958 }
5959 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
5960
5961 // Is the line strike or not
5962 $this->tpl['strike'] = 0;
5963 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5964 $this->tpl['strike'] = 1;
5965 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
5966 $this->tpl['strike'] = 1;
5967 }
5968
5969 // Output template part (modules that overwrite templates must declare this into descriptor)
5970 // Use global variables + $dateSelector + $seller and $buyer
5971 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5972 foreach ($dirtpls as $module => $reldir) {
5973 if (!empty($module)) {
5974 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5975 } else {
5976 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5977 }
5978
5979 if (empty($conf->file->strict_mode)) {
5980 $res = @include $tpl;
5981 } else {
5982 $res = include $tpl; // for debug
5983 }
5984 if ($res) {
5985 break;
5986 }
5987 }
5988 }
5989
5990
5991 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6003 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
6004 {
6005 // phpcs:enable
6006 global $user;
6007 $this->db->begin();
6008
6009 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
6010 $sql .= "resource_id";
6011 $sql .= ", resource_type";
6012 $sql .= ", element_id";
6013 $sql .= ", element_type";
6014 $sql .= ", busy";
6015 $sql .= ", mandatory";
6016 $sql .= ") VALUES (";
6017 $sql .= ((int) $resource_id);
6018 $sql .= ", '".$this->db->escape($resource_type)."'";
6019 $sql .= ", '".$this->db->escape((string) $this->id)."'";
6020 $sql .= ", '".$this->db->escape($this->element)."'";
6021 $sql .= ", '".$this->db->escape((string) $busy)."'";
6022 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
6023 $sql .= ")";
6024
6025 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
6026 if ($this->db->query($sql)) {
6027 if (!$notrigger) {
6028 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
6029 if ($result < 0) {
6030 $this->db->rollback();
6031 return -1;
6032 }
6033 }
6034 $this->db->commit();
6035 return 1;
6036 } else {
6037 $this->error = $this->db->lasterror();
6038 $this->db->rollback();
6039 return 0;
6040 }
6041 }
6042
6043 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6052 public function delete_resource($rowid, $element = '', $notrigger = 0)
6053 {
6054 // phpcs:enable
6055 global $user;
6056
6057 $this->db->begin();
6058
6059 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6060 $sql .= " WHERE rowid = ".((int) $rowid);
6061
6062 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6063
6064 $resql = $this->db->query($sql);
6065 if (!$resql) {
6066 $this->error = $this->db->lasterror();
6067 $this->db->rollback();
6068 return -1;
6069 } else {
6070 if (!$notrigger) {
6071 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6072 if ($result < 0) {
6073 $this->db->rollback();
6074 return -1;
6075 }
6076 }
6077 $this->db->commit();
6078 return 1;
6079 }
6080 }
6081
6082
6088 public function __clone()
6089 {
6090 // Force a copy of this->lines, otherwise it will point to same object.
6091 if (isset($this->lines) && is_array($this->lines)) {
6092 $nboflines = count($this->lines);
6093 for ($i = 0; $i < $nboflines; $i++) {
6094 if (is_object($this->lines[$i])) {
6095 $this->lines[$i] = clone $this->lines[$i];
6096 }
6097 }
6098 }
6099 }
6100
6114 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6115 {
6116 global $conf, $langs, $hookmanager, $action;
6117
6118 $srctemplatepath = '';
6119
6120 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6121 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6122
6123 if (!empty($reshook)) {
6124 return $reshook;
6125 }
6126
6127 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6128
6129 if (empty($modele)) {
6130 $this->error = 'BadValueForParameterModele';
6131 return -1;
6132 }
6133
6134 // Increase limit for PDF build
6135 $err = error_reporting();
6136 error_reporting(0);
6137 @set_time_limit(120);
6138 error_reporting($err);
6139
6140 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6141 $tmp = explode(':', $modele, 2);
6142 $saved_model = $modele;
6143 if (!empty($tmp[1])) {
6144 $modele = $tmp[0];
6145 $srctemplatepath = $tmp[1];
6146 }
6147
6148 // Search template files
6149 $file = '';
6150 $classname = '';
6151 $filefound = '';
6152 $dirmodels = array('/');
6153 if (is_array($conf->modules_parts['models'])) {
6154 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6155 }
6156 foreach ($dirmodels as $reldir) {
6157 foreach (array('doc', 'pdf') as $prefix) {
6158 if (in_array(get_class($this), array('Adherent'))) {
6159 // Member module use prefix_modele.class.php
6160 $file = $prefix."_".$modele.".class.php";
6161 } else {
6162 // Other module use prefix_modele.modules.php
6163 $file = $prefix."_".$modele.".modules.php";
6164 }
6165
6166 $file = dol_sanitizeFileName($file);
6167
6168 // We check if the file exists
6169 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6170 if (file_exists($file)) {
6171 $filefound = $file;
6172 $classname = $prefix.'_'.$modele;
6173 break;
6174 }
6175 }
6176 if ($filefound) {
6177 break;
6178 }
6179 }
6180
6181 if ($filefound === '' || $classname === '') {
6182 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6183 $this->errors[] = $this->error;
6184 dol_syslog($this->error, LOG_ERR);
6185 return -1;
6186 }
6187
6188 // Sanitize $filefound
6189 $filefound = dol_sanitizePathName($filefound);
6190
6191 // If generator was found
6192 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6193
6194 require_once $filefound;
6195
6196 $obj = new $classname($this->db);
6197
6198 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6199 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6200 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6201 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6202 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6203 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6204 // ModelePDFStock|ModelePDFStockTransfer|
6205 // ModeleDon|ModelePDFTask|
6206 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6207 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6208 '@phan-var-force ModelePDFMember $obj';
6209 // '@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';
6210
6211 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6212 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6213 $varfortemplatedir = $obj->scandir;
6214 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6215 $dirtoscan = getDolGlobalString($varfortemplatedir);
6216
6217 $listoffiles = array();
6218
6219 // Now we add first model found in directories scanned
6220 $listofdir = explode(',', $dirtoscan);
6221 foreach ($listofdir as $key => $tmpdir) {
6222 $tmpdir = trim($tmpdir);
6223 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6224 if (!$tmpdir) {
6225 unset($listofdir[$key]);
6226 continue;
6227 }
6228 if (is_dir($tmpdir)) {
6229 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6230 if (count($tmpfiles)) {
6231 $listoffiles = array_merge($listoffiles, $tmpfiles);
6232 }
6233 }
6234 }
6235
6236 if (count($listoffiles)) {
6237 foreach ($listoffiles as $record) {
6238 $srctemplatepath = $record['fullname'];
6239 break;
6240 }
6241 }
6242 }
6243
6244 if (empty($srctemplatepath)) {
6245 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6246 return -1;
6247 }
6248 }
6249
6250 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6251 if (!dol_is_file($srctemplatepath)) {
6252 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6253 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6254 return -1;
6255 }
6256 }
6257
6258 // We save charset_output to restore it because write_file can change it if needed for
6259 // output format that does not support UTF8.
6260 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6261
6262 // update model_pdf in object
6263 $this->model_pdf = $saved_model;
6264
6265 try {
6266 if ($obj instanceof ModelePDFMember) {
6267 if ($this instanceof Adherent) {
6268 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6269 } else {
6270 $resultwritefile = -1;
6271 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6272 }
6273 } elseif ($obj instanceof ModeleDon) {
6274 // Only 3 arguments
6275 if ($this instanceof Don) {
6276 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6277 } else {
6278 $resultwritefile = -1;
6279 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6280 }
6281 } else {
6282 // TODO: Try to set type above again
6283 '@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';
6284 $this->context['moreparams'] = $moreparams;
6285 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref); // @phan-suppress-current-line PhanTypeMismatchArgument
6286 }
6287 } catch (\Throwable $e) {
6288 $resultwritefile = -1;
6289 $this->error = $e->getMessage();
6290 dol_syslog("Exception in write_file() for ".get_class($this).": ".$e->getMessage(), LOG_ERR);
6291 }
6292 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6293
6294
6295 if ($resultwritefile > 0) {
6296 $outputlangs->charset_output = $sav_charset_output;
6297
6298 // We delete old preview
6299 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6300 dol_delete_preview($this);
6301
6302 // Index file in database
6303 if (!empty($obj->result['fullpath'])) {
6304 $destfull = $obj->result['fullpath'];
6305
6306 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6307 $update_main_doc_field = 0;
6308 if (!empty($obj->update_main_doc_field)) {
6309 $update_main_doc_field = 1;
6310 }
6311
6312 // Check that the file exists, before indexing it.
6313 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6314 if (dol_is_file($destfull)) {
6315 $this->indexFile($destfull, $update_main_doc_field);
6316 }
6317 } else {
6318 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);
6319 }
6320
6321 // Success in building document. We build meta file.
6322 dol_meta_create($this);
6323
6324 $this->warnings = $obj->warnings;
6325
6326 return 1;
6327 } else {
6328 $outputlangs->charset_output = $sav_charset_output;
6329 if (empty($this->error)) {
6330 $this->error = $obj->error;
6331 }
6332 if (empty($this->errors)) {
6333 $this->errors = $obj->errors;
6334 }
6335 $this->warnings = $obj->warnings;
6336 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$this->error, LOG_ERR);
6337 return -1;
6338 }
6339 }
6340
6350 public function indexFile($destfull, $update_main_doc_field)
6351 {
6352 global $conf, $user;
6353
6354 $upload_dir = dirname($destfull);
6355 $destfile = basename($destfull);
6356 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6357
6358 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6359 $filename = basename($destfile);
6360 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6361 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6362
6363 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6364 $ecmfile = new EcmFiles($this->db);
6365 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6366
6367 // Set the public "share" key
6368 $setsharekey = false;
6369 if (
6370 !empty($this->TRIGGER_PREFIX)
6371 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6372 ) {
6373 $setsharekey = true;
6374 }
6375 // TODO Remove case covered by trigger prefix
6376 if ($this->element == 'propal' || $this->element == 'proposal') {
6377 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6378 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6379 }
6380 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6381 $setsharekey = true;
6382 }
6383 }
6384 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6385 $setsharekey = true;
6386 }
6387 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6388 $setsharekey = true;
6389 }
6390 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6391 $setsharekey = true;
6392 }
6393 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6394 $setsharekey = true;
6395 }
6396 if ($this->element == 'order_supplier' && getDolGlobalInt("SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6397 $setsharekey = true;
6398 }
6399 if ($this->element == 'invoice_supplier' && getDolGlobalInt("SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6400 $setsharekey = true;
6401 }
6402 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6403 $setsharekey = true;
6404 }
6405
6406 if ($setsharekey) {
6407 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6408 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6409 $ecmfile->share = getRandomPassword(true);
6410 }
6411 }
6412
6413 if ($result > 0) {
6414 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6415 $ecmfile->fullpath_orig = '';
6416 $ecmfile->gen_or_uploaded = 'generated';
6417 $ecmfile->description = ''; // indexed content
6418 $ecmfile->keywords = ''; // keyword content
6419 $ecmfile->src_object_type = $this->table_element;
6420 $ecmfile->src_object_id = $this->id;
6421 $result = $ecmfile->update($user);
6422 if ($result < 0) {
6423 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6424 return -1;
6425 }
6426 } else {
6427 $ecmfile->entity = $conf->entity;
6428 $ecmfile->filepath = $rel_dir;
6429 $ecmfile->filename = $filename;
6430 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6431 $ecmfile->fullpath_orig = '';
6432 $ecmfile->gen_or_uploaded = 'generated';
6433 $ecmfile->description = ''; // indexed content
6434 $ecmfile->keywords = ''; // keyword content
6435 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6436 $ecmfile->src_object_id = $this->id;
6437
6438 $result = $ecmfile->create($user);
6439 if ($result < 0) {
6440 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6441 return -1;
6442 }
6443 }
6444
6445 /*$this->result['fullname']=$destfull;
6446 $this->result['filepath']=$ecmfile->filepath;
6447 $this->result['filename']=$ecmfile->filename;*/
6448 //var_dump($obj->update_main_doc_field);exit;
6449
6450 if ($update_main_doc_field && !empty($this->table_element)) {
6451 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6452 $sql .= " WHERE rowid = ".((int) $this->id);
6453
6454 $resql = $this->db->query($sql);
6455 if (!$resql) {
6456 dol_print_error($this->db);
6457 return -1;
6458 } else {
6459 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6460 }
6461 }
6462 }
6463
6464 return 1;
6465 }
6466
6475 public function addThumbs($file, $quality = 50)
6476 {
6477 $file_osencoded = dol_osencode($file);
6478
6479 if (file_exists($file_osencoded)) {
6480 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6481
6482 $tmparraysize = getDefaultImageSizes();
6483 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6484 $maxheightsmall = $tmparraysize['maxheightsmall'];
6485 $maxwidthmini = $tmparraysize['maxwidthmini'];
6486 $maxheightmini = $tmparraysize['maxheightmini'];
6487 //$quality = $tmparraysize['quality'];
6488
6489 // Create small thumbs for company (Ratio is near 16/9)
6490 // Used on logon for example
6491 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6492
6493 // Create mini thumbs for company (Ratio is near 16/9)
6494 // Used on menu or for setup page for example
6495 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6496 }
6497 }
6498
6506 public function delThumbs($file)
6507 {
6508 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6509 dol_delete_file($imgThumbName);
6510 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6511 dol_delete_file($imgThumbName);
6512 }
6513
6514
6515 /* Functions common to commonobject and commonobjectline */
6516
6517 /* For default values */
6518
6532 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6533 {
6534 /* TODO Remove this. Must use now something like:
6535 $note_private = GETPOST('note_private', 'restricthtml');
6536 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6537 $note_private = $objectsrc->note_private;
6538 }
6539 */
6540
6541 // If param here has been posted, we use this value first.
6542 if (GETPOSTISSET($fieldname)) {
6543 return GETPOST($fieldname, $type, 3);
6544 }
6545
6546 if (isset($alternatevalue)) {
6547 return $alternatevalue;
6548 }
6549
6550 $newelement = $this->element;
6551 if ($newelement == 'facture') {
6552 $newelement = 'invoice';
6553 }
6554 if ($newelement == 'commande') {
6555 $newelement = 'order';
6556 }
6557 if (empty($newelement)) {
6558 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6559 return '';
6560 }
6561
6562 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6563 //var_dump($keyforfieldname);
6564 if (getDolGlobalString($keyforfieldname)) {
6565 return getDolGlobalString($keyforfieldname);
6566 }
6567
6568 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6569 // store content into $conf->cache['overwrite_default']
6570
6571 return '';
6572 }
6573
6574
6575 /* Functions for data in other language */
6576
6577
6587 {
6588 // To avoid SQL errors. Probably not the better solution though
6589 if (!$this->element) {
6590 return 0;
6591 }
6592 if (!($this->id > 0)) {
6593 return 0;
6594 }
6595 if (is_array($this->array_languages)) {
6596 return 1;
6597 }
6598
6599 $this->array_languages = array();
6600
6601 $element = $this->element;
6602 if ($element == 'categorie') {
6603 $element = 'categories'; // For compatibility
6604 }
6605
6606 // Request to get translation values for object
6607 $sql = "SELECT rowid, property, lang , value";
6608 $sql .= " FROM ".$this->db->prefix()."object_lang";
6609 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6610 $sql .= " AND fk_object = ".((int) $this->id);
6611
6612 $resql = $this->db->query($sql);
6613 if ($resql) {
6614 $numrows = $this->db->num_rows($resql);
6615 if ($numrows) {
6616 $i = 0;
6617 while ($i < $numrows) {
6618 $obj = $this->db->fetch_object($resql);
6619 $key = $obj->property;
6620 $value = $obj->value;
6621 $codelang = $obj->lang;
6622 $type = $this->fields[$key]['type'];
6623
6624 // we can add this attribute to object
6625 if (preg_match('/date/', $type)) {
6626 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6627 } else {
6628 $this->array_languages[$key][$codelang] = $value;
6629 }
6630
6631 $i++;
6632 }
6633 }
6634
6635 $this->db->free($resql);
6636
6637 if ($numrows) {
6638 return $numrows;
6639 } else {
6640 return 0;
6641 }
6642 } else {
6643 dol_print_error($this->db);
6644 return -1;
6645 }
6646 }
6647
6655 public function setValuesForExtraLanguages($onlykey = '')
6656 {
6657 // Get extra fields
6658 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6659 $tmparray = explode('-', $postfieldkey);
6660 if ($tmparray[0] != 'field') {
6661 continue;
6662 }
6663
6664 $element = $tmparray[1];
6665 $key = $tmparray[2];
6666 $codelang = $tmparray[3];
6667 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6668
6669 if (!empty($onlykey) && $key != $onlykey) {
6670 continue;
6671 }
6672 if ($element != $this->element) {
6673 continue;
6674 }
6675
6676 $key_type = $this->fields[$key]['type'];
6677
6678 $enabled = 1;
6679 if (isset($this->fields[$key]['enabled'])) {
6680 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6681 }
6682 /*$perms = 1;
6683 if (isset($this->fields[$key]['perms']))
6684 {
6685 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6686 }*/
6687 if (empty($enabled)) {
6688 continue;
6689 }
6690 //if (empty($perms)) continue;
6691
6692 if (in_array($key_type, array('date'))) {
6693 // Clean parameters
6694 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6695 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6696 } elseif (in_array($key_type, array('datetime'))) {
6697 // Clean parameters
6698 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6699 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6700 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6701 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6702 if (!empty($value_arr)) {
6703 $value_key = implode(',', $value_arr);
6704 } else {
6705 $value_key = '';
6706 }
6707 } elseif (in_array($key_type, array('price', 'double'))) {
6708 $value_arr = GETPOST($postfieldkey, 'alpha');
6709 $value_key = price2num($value_arr);
6710 } else {
6711 $value_key = GETPOST($postfieldkey);
6712 if (in_array($key_type, array('link')) && $value_key == '-1') {
6713 $value_key = '';
6714 }
6715 }
6716
6717 $this->array_languages[$key][$codelang] = $value_key;
6718
6719 /*if ($nofillrequired) {
6720 $langs->load('errors');
6721 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6722 return -1;
6723 }*/
6724 }
6725
6726 return 1;
6727 }
6728
6729
6730 /* Functions for extrafields */
6731
6738 public function fetchNoCompute($id)
6739 {
6740 global $conf;
6741
6742 $savDisableCompute = $conf->disable_compute;
6743 $conf->disable_compute = 1;
6744
6745 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6746
6747 $conf->disable_compute = $savDisableCompute;
6748
6749 return $ret;
6750 }
6751
6752 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6762 public function fetch_optionals($rowid = null, $optionsArray = null)
6763 {
6764 // phpcs:enable
6765 global $conf, $extrafields;
6766
6767 if (empty($rowid)) {
6768 $rowid = $this->id;
6769 }
6770 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6771 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6772 }
6773
6774 // To avoid SQL errors. Probably not the better solution though
6775 if (!$this->table_element) {
6776 return 0;
6777 }
6778 $this->array_options = array();
6779
6780 if (!is_array($optionsArray)) {
6781 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6782 if (!isset($extrafields) || !is_object($extrafields)) {
6783 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6784 $extrafields = new ExtraFields($this->db);
6785 }
6786
6787 // Load array of extrafields for elementype = $this->table_element
6788 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6789 $extrafields->fetch_name_optionals_label($this->table_element);
6790 }
6791 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6792 } else {
6793 global $extrafields;
6794 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6795 }
6796
6797 $table_element = $this->table_element;
6798 if ($table_element == 'categorie') {
6799 $table_element = 'categories'; // For compatibility
6800 }
6801
6802 // Request to get complementary values
6803 if (is_array($optionsArray) && count($optionsArray) > 0) {
6804 $sql = "SELECT rowid";
6805 foreach ($optionsArray as $name => $label) {
6806 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6807 $sql .= ", ".$this->db->sanitize($name);
6808 }
6809 // use geo sql fonction to read as text
6810 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6811 // TODO Add an abstraction method in the database driver
6812 $sql .= ", ST_AsWKT(".$name.") as ".$this->db->sanitize($name);
6813 }
6814 }
6815 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6816 $sql .= " WHERE fk_object = ".((int) $rowid);
6817
6818 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6819 $resql = $this->db->query($sql);
6820 if ($resql) {
6821 $numrows = $this->db->num_rows($resql);
6822 if ($numrows) {
6823 $tab = $this->db->fetch_array($resql);
6824 foreach ($tab as $key => $value) {
6825 // 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)
6826 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6827 // we can add this attribute to object
6828 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6829 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6830 $this->array_options["options_".$key] = $this->db->jdate($value);
6831 } else {
6832 $this->array_options["options_".$key] = $value;
6833 }
6834
6835 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6836 }
6837 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6838 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6839 $this->array_options["options_".$key] = dolDecrypt($value);
6840 }
6841 }
6842 }
6843 } else {
6848 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6849 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6850 $this->array_options['options_' . $key] = null;
6851 }
6852 }
6853 }
6854
6855 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6856 // in the element's extrafields table)
6857 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6858 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6859 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6860 //var_dump($conf->disable_compute);
6861 if (empty($conf->disable_compute)) {
6862 // We set a global variable to $objectoffield so we can use it inside computed formula
6863 $objectoffield = dol_clone($this, 2);
6864 global $objectoffield;
6865 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6866 }
6867 }
6868 }
6869 }
6870
6871 $this->db->free($resql);
6872
6873 if ($numrows) {
6874 return $numrows;
6875 } else {
6876 return 0;
6877 }
6878 } else {
6879 $this->errors[] = $this->db->lasterror();
6880 return -1;
6881 }
6882 }
6883 return 0;
6884 }
6885
6892 public function deleteExtraFields()
6893 {
6894 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6895 return 0;
6896 }
6897
6898 $this->db->begin();
6899
6900 $table_element = $this->table_element;
6901 if ($table_element == 'categorie') {
6902 $table_element = 'categories'; // For compatibility
6903 }
6904
6905 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6906
6907 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6908
6909 $resql = $this->db->query($sql_del);
6910 if (!$resql) {
6911 $this->error = $this->db->lasterror();
6912 $this->db->rollback();
6913 return -1;
6914 } else {
6915 $this->db->commit();
6916 return 1;
6917 }
6918 }
6919
6930 public function insertExtraFields($trigger = '', $userused = null)
6931 {
6932 global $langs, $extrafields, $user;
6933
6934 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6935 return 0;
6936 }
6937
6938 if (empty($userused)) {
6939 $userused = $user;
6940 }
6941
6942 $error = 0;
6943
6944 // Check parameters
6945 $langs->load('admin');
6946 if (!isset($extrafields) || !is_object($extrafields)) {
6947 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6948 $extrafields = new ExtraFields($this->db);
6949 }
6950 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6951
6952 // Eliminate copied source object extra fields that do not exist in target object
6953 $new_array_options = array();
6954 foreach ($this->array_options as $key => $value) {
6955 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6956 $new_array_options[$key] = $value;
6957 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6958 $new_array_options['options_'.$key] = $value;
6959 }
6960 }
6961
6962 foreach ($new_array_options as $key => $value) {
6963 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6964 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6965 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6966 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6967 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6968 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6969 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6970 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
6971 /*
6972 $attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
6973 $enabled = 0;
6974 if (!empty($extrafields->attributes[$this->table_element]['enabled'][$key])) {
6975 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
6976 }
6977 */
6978
6979 // If we clone, we have to clean unique extrafields to prevent duplicates.
6980 // If we clone, we have to clean extrafields having "empty on clone" option on.
6981 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6982 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
6983 $new_array_options[$key] = null;
6984 continue;
6985 }
6986
6987 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6988 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6989 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6990 $new_array_options[$key] = null;
6991 continue;
6992 }
6993
6994 // Similar code than into insertExtraFields
6995 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'.
6996 $v = $this->array_options[$key];
6997 if (ExtraFields::isEmptyValue($v, $attributeType)) {
6998 $langs->load("errors");
6999 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
7000 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7001 return -1;
7002 }
7003 }
7004
7005 if (!empty($attrfieldcomputed)) {
7006 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7007 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7008 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7009 $new_array_options[$key] = $value;
7010 } else {
7011 $new_array_options[$key] = null;
7012 }
7013 continue;
7014 }
7015
7016 switch ($attributeType) {
7017 case 'int':
7018 case 'duration':
7019 case 'stars':
7020 if (!is_numeric($value) && $value != '') {
7021 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7022 return -1;
7023 } elseif ($value === '') {
7024 $new_array_options[$key] = null;
7025 }
7026 break;
7027 case 'boolean':
7028 if ($value === '' || $value === false || $value === null) {
7029 $new_array_options[$key] = null;
7030 }
7031 break;
7032 case 'price':
7033 case 'double':
7034 $value = price2num($value);
7035 if (!is_numeric($value) && $value != '') {
7036 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7037 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7038 return -1;
7039 } elseif ($value == '') {
7040 $value = null;
7041 }
7042 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7043 $new_array_options[$key] = $value;
7044 break;
7045 /*case 'select': // Not required, we chose value='0' for undefined values
7046 if ($value=='-1')
7047 {
7048 $this->array_options[$key] = null;
7049 }
7050 break;*/
7051 case 'password':
7052 $algo = '';
7053 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7054 // If there is an encryption choice, we use it to encrypt data before insert
7055 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7056 $algo = reset($tmparrays);
7057 if ($algo != '') {
7058 //global $action; // $action may be 'create', 'update', 'update_extras'...
7059 //var_dump($action);
7060 //var_dump($this->oldcopy);exit;
7061 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
7062 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7063 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7064 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7065 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7066 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7067 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7068 } else {
7069 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7070 }
7071 } else {
7072 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7073 }
7074 } else {
7075 // If value has changed
7076 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7077 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7078 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7079 } else {
7080 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7081 }
7082 } else {
7083 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7084 }
7085 }
7086 } else {
7087 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7088 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7089 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7090 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7091 } else {
7092 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7093 }
7094 }
7095 } else {
7096 // No encryption
7097 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7098 }
7099 } else { // Common usage
7100 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7101 }
7102 break;
7103 case 'date':
7104 case 'datetime':
7105 // If data is a string instead of a timestamp, we convert it
7106 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7107 $this->array_options[$key] = strtotime($this->array_options[$key]);
7108 }
7109 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7110 break;
7111 case 'datetimegmt':
7112 // If data is a string instead of a timestamp, we convert it
7113 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7114 $this->array_options[$key] = strtotime($this->array_options[$key]);
7115 }
7116 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7117 break;
7118 case 'link':
7119 $param_list = array_keys($attributeParam['options']);
7120 // 0 : ObjectName
7121 // 1 : classPath
7122 $InfoFieldList = explode(":", $param_list[0]);
7123 dol_include_once($InfoFieldList[1]);
7124 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7125 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7126 $new_array_options[$key] = '';
7127 } elseif ($value) {
7128 $object = new $InfoFieldList[0]($this->db);
7129 '@phan-var-force CommonObject $object';
7130
7131 $objectId = 0;
7132
7133 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7134 if (is_numeric($value)) {
7135 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7136 } else {
7137 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7138 }
7139
7140 $obj = $this->db->getRow($sqlFetchObject);
7141
7142 if ($obj !== false) {
7143 $objectId = $obj->rowid;
7144 $res = 1;
7145 } else {
7146 $res = -1;
7147 }
7148
7149 if ($res > 0) {
7150 $new_array_options[$key] = $objectId;
7151 } else {
7152 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7153 return -1;
7154 }
7155 }
7156 } else {
7157 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7158 }
7159 break;
7160 case 'checkbox':
7161 case 'chkbxlst':
7162 if (is_array($this->array_options[$key])) {
7163 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7164 } else {
7165 $new_array_options[$key] = $this->array_options[$key];
7166 }
7167 break;
7168 }
7169 }
7170
7171 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7172 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7173
7174 foreach ($new_array_options as $key => $newValue) {
7175 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7176 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7177 if ($attributeType === 'separate') {
7178 // separator extrafields don't have data per object so they don't have a comlumn in the database
7179 continue;
7180 }
7181 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7182 // Add field of attribute
7183 if (! $geoDataType) {
7184 // not a geodata type
7185 if ($newValue != '') {
7186 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7187 } else {
7188 $sqlColumnValues[$attributeKey] = 'null';
7189 }
7190 continue;
7191 }
7192 if (empty($newValue)) {
7193 $sqlColumnValues[$attributeKey] = 'null';
7194 continue;
7195 }
7196 if (preg_match('/error/i', $newValue)) {
7197 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7198 $sqlColumnValues[$attributeKey] = 'null';
7199 continue;
7200 }
7201
7202 // Geodata type: Text must be a WKT string. Examples:
7203 // - point => "POINT(15 20)"
7204 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7205 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7206 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7207 $sqlColumnValues[$attributeKey] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')";
7208 }
7209
7210 $this->db->begin();
7211
7212 $table_element = $this->table_element;
7213 if ($table_element == 'categorie') {
7214 $table_element = 'categories'; // For compatibility
7215 }
7216 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7217
7218 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7219
7220 $linealreadyfound = 0;
7221 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7222 $resql = $this->db->query($sql);
7223 if ($resql) {
7224 $tmpobj = $this->db->fetch_object($resql);
7225 if ($tmpobj) {
7226 $linealreadyfound = $tmpobj->nb;
7227 }
7228 }
7229
7230 // if the extrafields row already exists for the object, we update it
7231 if ($linealreadyfound) {
7232 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7233 $sqlColumnValueString = implode(
7234 ',',
7239 array_map(function ($key) use ($sqlColumnValues) {
7240 return "{$key} = {$sqlColumnValues[$key]}";
7241 }, array_keys($sqlColumnValues))
7242 );
7243 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7244 } else {
7245 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7246 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7247 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7248 if (isset($sqlColumnValues[$key])) {
7249 // extrafield value already provided: no need to add it
7250 continue;
7251 }
7252 // default value: empty string, except for 'int', 'double' and 'price'
7253 $sqlColumnValues[$key] = "''";
7254 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7255 $sqlColumnValues[$key] = 0;
7256 }
7257 }
7258 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7259 $sqlValues = implode(',', array_values($sqlColumnValues));
7260 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7261 }
7262
7263 $resql = $this->db->query($sql);
7264 if (!$resql) {
7265 $this->error = $this->db->lasterror();
7266 $error++;
7267 }
7268
7269 // Update also the user of last modification in parent table
7270 if (!$error && !empty($this->fields['fk_user_modif'])) { // @phan-suppress-current-line PhanTypeMismatchProperty
7271 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7272 $sql .= " SET fk_user_modif = ".(int) $user->id;
7273 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7274 $this->db->query($sql);
7275 }
7276
7277 if (!$error && $trigger) {
7278 // Call trigger
7279 $this->context = array('extrafieldaddupdate' => 1);
7280 $result = $this->call_trigger($trigger, $userused);
7281 if ($result < 0) {
7282 $error++;
7283 }
7284 // End call trigger
7285 }
7286
7287 if ($error) {
7288 $this->db->rollback();
7289 return -1;
7290 } else {
7291 $this->db->commit();
7292 return 1;
7293 }
7294 }
7295
7307 public function insertExtraLanguages($trigger = '', $userused = null)
7308 {
7309 global $langs, $user;
7310
7311 if (empty($userused)) {
7312 $userused = $user;
7313 }
7314
7315 $error = 0;
7316
7317 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7318 return 0; // For avoid conflicts if trigger used
7319 }
7320
7321 if (is_array($this->array_languages)) {
7322 $new_array_languages = $this->array_languages;
7323
7324 foreach ($new_array_languages as $key => $value) {
7325 $attributeKey = $key;
7326 $attributeType = $this->fields[$attributeKey]['type'];
7327 $attributeLabel = $this->fields[$attributeKey]['label'];
7328
7329 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7330 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7331
7332 switch ($attributeType) {
7333 case 'int':
7334 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7335 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7336 return -1;
7337 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7338 $new_array_languages[$key] = null;
7339 }
7340 break;
7341 case 'double':
7342 $value = price2num((string) $value);
7343 if (!is_numeric($value) && $value != '') {
7344 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7345 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7346 return -1;
7347 } elseif ($value == '') {
7348 $new_array_languages[$key] = null;
7349 } else {
7350 $new_array_languages[$key] = $value;
7351 }
7352 break;
7353 /*case 'select': // Not required, we chose value='0' for undefined values
7354 if ($value=='-1')
7355 {
7356 $this->array_options[$key] = null;
7357 }
7358 break;*/
7359 }
7360 }
7361
7362 $this->db->begin();
7363
7364 $table_element = $this->table_element;
7365 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7366 $table_element = 'categories'; // For compatibility
7367 }
7368
7369 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7370
7371 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7372 foreach ($langcodearray as $langcode => $value) {
7373 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7374 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7375 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7376 $this->db->query($sql_del);
7377
7378 if ($value !== '') {
7379 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7380 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7381 $sql .= ")";
7382
7383 $resql = $this->db->query($sql);
7384 if (!$resql) {
7385 $this->error = $this->db->lasterror();
7386 $error++;
7387 break;
7388 }
7389 }
7390 }
7391 }
7392
7393 if (!$error && $trigger) {
7394 // Call trigger
7395 $this->context = array('extralanguagesaddupdate' => 1);
7396 $result = $this->call_trigger($trigger, $userused);
7397 if ($result < 0) {
7398 $error++;
7399 }
7400 // End call trigger
7401 }
7402
7403 if ($error) {
7404 $this->db->rollback();
7405 return -1;
7406 } else {
7407 $this->db->commit();
7408 return 1;
7409 }
7410 } else {
7411 return 0;
7412 }
7413 }
7414
7425 public function updateExtraField($key, $trigger = null, $userused = null)
7426 {
7427 global $langs, $user, $extrafields, $hookmanager;
7428
7429 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7430 return 0;
7431 }
7432
7433 if (empty($userused)) {
7434 $userused = $user;
7435 }
7436
7437 $error = 0;
7438
7439 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7440 // Check parameters
7441 $langs->load('admin');
7442 if (!isset($extrafields) || !is_object($extrafields)) {
7443 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7444 $extrafields = new ExtraFields($this->db);
7445 }
7446 $extrafields->fetch_name_optionals_label($this->table_element);
7447
7448 $value = $this->array_options["options_".$key];
7449
7450 $attributeKey = $key;
7451 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7452 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7453 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7454 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7455 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7456 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7457 //$attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7458
7459 // Similar code than into insertExtraFields
7460 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'.
7461 $mandatorypb = false;
7462 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7463 $mandatorypb = true;
7464 }
7465 if ($this->array_options["options_".$key] === '') {
7466 $mandatorypb = true;
7467 }
7468 if ($mandatorypb) {
7469 $langs->load("errors");
7470 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7471 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7472 return -1;
7473 }
7474 }
7475
7476 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7477 $new_array_options = $this->array_options;
7478
7479 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7480 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7481 if (!empty($attrfieldcomputed)) {
7482 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7483 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7484 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7485
7486 $new_array_options["options_".$key] = $value;
7487
7488 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7489 } else {
7490 $new_array_options["options_".$key] = null;
7491
7492 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7493 }
7494 }
7495
7496 switch ($attributeType) {
7497 case 'int':
7498 if (!is_numeric($value) && $value != '') {
7499 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7500 return -1;
7501 } elseif ($value === '') {
7502 $new_array_options["options_".$key] = null;
7503
7504 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7505 }
7506 break;
7507 case 'price':
7508 case 'double':
7509 $value = price2num($value);
7510 if (!is_numeric($value) && $value != '') {
7511 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7512 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7513 return -1;
7514 } elseif ($value === '') {
7515 $value = null;
7516 }
7517 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7518 $new_array_options["options_".$key] = $value;
7519
7520 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7521 break;
7522 /*case 'select': // Not required, we chose value='0' for undefined values
7523 if ($value=='-1')
7524 {
7525 $new_array_options["options_".$key] = $value;
7526
7527 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7528 }
7529 break;*/
7530 case 'password':
7531 $algo = '';
7532 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7533 // If there is an encryption choice, we use it to encrypt data before insert
7534 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7535 $algo = reset($tmparrays);
7536 if ($algo != '') {
7537 //global $action; // $action may be 'create', 'update', 'update_extras'...
7538 //var_dump($action);
7539 //var_dump($this->oldcopy);exit;
7540 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7541 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
7542 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7543 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.
7544 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7545 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7546 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7547 } else {
7548 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7549 }
7550 } else {
7551 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7552 }
7553 } else {
7554 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7555 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7556 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7557 } else {
7558 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7559 }
7560 } else {
7561 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7562 }
7563 }
7564 } else {
7565 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7566 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7567 } else {
7568 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7569 }
7570 }
7571 } else {
7572 // No encryption
7573 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7574 }
7575 } else { // Common usage
7576 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7577 }
7578
7579 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7580 break;
7581 case 'date':
7582 case 'datetime':
7583 if (empty($this->array_options["options_".$key])) {
7584 $new_array_options["options_".$key] = null;
7585
7586 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7587 } else {
7588 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7589 }
7590 break;
7591 case 'datetimegmt':
7592 if (empty($this->array_options["options_".$key])) {
7593 $new_array_options["options_".$key] = null;
7594
7595 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7596 } else {
7597 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7598 }
7599 break;
7600 case 'boolean':
7601 if (empty($this->array_options["options_".$key])) {
7602 $new_array_options["options_".$key] = null;
7603
7604 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7605 }
7606 break;
7607 case 'link':
7608 if ($this->array_options["options_".$key] === '') {
7609 $new_array_options["options_".$key] = null;
7610
7611 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7612 }
7613 break;
7614 /*
7615 case 'link':
7616 $param_list = array_keys($attributeParam['options']);
7617 // 0 : ObjectName
7618 // 1 : classPath
7619 $InfoFieldList = explode(":", $param_list[0]);
7620 dol_include_once($InfoFieldList[1]);
7621 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7622 {
7623 if ($value == '-1') // -1 is key for no defined in combo list of objects
7624 {
7625 $new_array_options[$key] = '';
7626 } elseif ($value) {
7627 $object = new $InfoFieldList[0]($this->db);
7628 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7629 else $res = $object->fetch(0, $value); // For compatibility
7630
7631 if ($res > 0) $new_array_options[$key] = $object->id;
7632 else {
7633 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7634 $this->db->rollback();
7635 return -1;
7636 }
7637 }
7638 } else {
7639 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7640 }
7641 break;
7642 */
7643 case 'checkbox':
7644 case 'chkbxlst':
7645 $new_array_options = array();
7646 if (is_array($this->array_options["options_".$key])) {
7647 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7648 } else {
7649 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7650 }
7651
7652 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7653 break;
7654 }
7655
7656 $this->db->begin();
7657
7658 $linealreadyfound = 0;
7659
7660 // 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)
7661 $table_element = $this->table_element;
7662 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7663 $table_element = 'categories'; // For compatibility
7664 }
7665
7666 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7667 $resql = $this->db->query($sql);
7668 if ($resql) {
7669 $tmpobj = $this->db->fetch_object($resql);
7670 if ($tmpobj) {
7671 $linealreadyfound = $tmpobj->nb;
7672 }
7673 }
7674
7675 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7676 if ($linealreadyfound) {
7677 $sanitizedGeoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7678 if ($this->array_options["options_".$key] === null) {
7679 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7680 $sql .= " SET ".$this->db->sanitize($key)." = null";
7681 } elseif (!empty($sanitizedGeoDataType['ST_Function'])) {
7682 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7683 $sql .= " SET ".$this->db->sanitize($key)." = ".$this->db->sanitize($sanitizedGeoDataType['ST_Function'])."('".$this->db->escape($this->array_options["options_".$key])."')";
7684 } else {
7685 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7686 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7687 $sql .= " SET ".$this->db->sanitize($key)." = '".$this->db->escape($new_array_options["options_".$key])."'";
7688 }
7689 $sql .= " WHERE fk_object = ".((int) $this->id);
7690
7691 $resql = $this->db->query($sql);
7692 if (!$resql) {
7693 $error++;
7694 $this->error = $this->db->lasterror();
7695 }
7696 } else {
7697 $result = $this->insertExtraFields('', $user);
7698 if ($result < 0) {
7699 $error++;
7700 }
7701 }
7702
7703 // Update also the user of last modification in parent table
7704 if (!$error && !empty($this->fields['fk_user_modif'])) {
7705 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7706 $sql .= " SET fk_user_modif = ".(int) $user->id;
7707 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7708 $this->db->query($sql);
7709 }
7710
7711 if (!$error) {
7712 $parameters = array('key' => $key);
7713 global $action;
7714 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7715 if ($reshook < 0) {
7716 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7717 }
7718 }
7719
7720 if (!$error && $trigger) {
7721 // Call trigger
7722 $this->context = array('extrafieldupdate' => 1);
7723 $result = $this->call_trigger($trigger, $userused);
7724 if ($result < 0) {
7725 $error++;
7726 }
7727 // End call trigger
7728 }
7729
7730 if ($error) {
7731 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7732 $this->db->rollback();
7733 return -1;
7734 } else {
7735 $this->db->commit();
7736 return 1;
7737 }
7738 } else {
7739 return 0;
7740 }
7741 }
7742
7749 public function getExtraField($key)
7750 {
7751 return $this->array_options['options_'.$key] ?? null;
7752 }
7753
7761 public function setExtraField($key, $value)
7762 {
7763 $this->array_options['options_'.$key] = $value;
7764 }
7765
7776 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7777 {
7778 global $user;
7779
7780 if (empty($userused)) {
7781 $userused = $user;
7782 }
7783
7784 //$error = 0;
7785
7786 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7787 return 0; // For avoid conflicts if trigger used
7788 }
7789
7790 return 0;
7791 }
7792
7793
7808 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7809 {
7810 global $conf, $langs, $form;
7811
7812
7813 // 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...)
7814
7815 if (!is_object($form)) {
7816 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7817 $form = new Form($this->db);
7818 }
7819
7820 if (!empty($this->fields)) {
7821 $val = $this->fields[$key];
7822 }
7823
7824 // Validation tests and output
7825 $fieldValidationErrorMsg = '';
7826 $validationClass = '';
7827 $fieldValidationErrorMsg = $this->getFieldError($key);
7828 if (!empty($fieldValidationErrorMsg)) {
7829 $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
7830 } else {
7831 $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
7832 }
7833
7834 //$valuemultiselectinput = array();
7835 $out = '';
7836 $type = '';
7837 $isDependList = 0;
7838 $param = array();
7839 $param['options'] = array();
7840 $reg = array();
7841 // @phan-suppress-next-line PhanTypeMismatchProperty
7842 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7843 // Because we work on extrafields
7844 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7845 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7846 $type = 'link';
7847 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7848 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7849 $type = 'link';
7850 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7851 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7852 $type = 'link';
7853 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7854 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7855 $type = 'sellist';
7856 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7857 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7858 $type = 'sellist';
7859 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7860 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7861 $type = 'sellist';
7862 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7863 $param['options'] = array($reg[1] => 'N');
7864 $type = 'chkbxlst';
7865 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7866 $param['options'] = array();
7867 $type = 'varchar';
7868 $size = $reg[1];
7869 } elseif (preg_match('/varchar/', $val['type'])) {
7870 $param['options'] = array();
7871 $type = 'varchar';
7872 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7873 $param['options'] = array();
7874 $type = 'stars';
7875 $size = $reg[1];
7876 } else {
7877 $param['options'] = array();
7878 $type = $this->fields[$key]['type'];
7879 }
7880 //var_dump($type); var_dump($param['options']);
7881
7882 // Special case that force options and type ($type can be integer, varchar, ...)
7883 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7884 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7885 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7886 if (empty($this->fields[$key]['multiinput'])) {
7887 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7888 }
7889 }
7890
7891 $label = $this->fields[$key]['label'];
7892 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7893 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7894 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7895 // @phan-suppress-next-line PhanTypeMismatchProperty
7896 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7897 // @phan-suppress-next-line PhanTypeMismatchProperty
7898 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7899 // @phan-suppress-next-line PhanTypeMismatchProperty
7900 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7901 // @phan-suppress-next-line PhanTypeMismatchProperty
7902 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7903 // @phan-suppress-next-line PhanTypeMismatchProperty
7904 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7905
7906 // @phan-suppress-next-line PhanTypeMismatchProperty
7907 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7908 // @phan-suppress-next-line PhanTypeMismatchProperty
7909 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7910 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7911
7912 $objectid = $this->id;
7913
7914 if ($computed) {
7915 if (!preg_match('/^search_/', $keyprefix)) {
7916 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7917 } else {
7918 return '';
7919 }
7920 }
7921
7922 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7923 if (empty($morecss) && !empty($val['css'])) {
7924 $morecss = $val['css'];
7925 } elseif (empty($morecss)) {
7926 if ($type == 'date') {
7927 $morecss = 'minwidth100imp';
7928 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7929 $morecss = 'minwidth200imp';
7930 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7931 $morecss = 'maxwidth75';
7932 } elseif ($type == 'url') {
7933 $morecss = 'minwidth400';
7934 } elseif ($type == 'boolean') {
7935 $morecss = '';
7936 } else {
7937 if (is_numeric($size) && round((float) $size) < 12) {
7938 $morecss = 'minwidth100';
7939 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7940 $morecss = 'minwidth200';
7941 } else {
7942 $morecss = 'minwidth400';
7943 }
7944 }
7945 }
7946
7947 // Add validation state class
7948 if (!empty($validationClass)) {
7949 $morecss .= $validationClass;
7950 }
7951
7952 if (in_array($type, array('date'))) {
7953 $tmp = explode(',', $size);
7954 $newsize = $tmp[0];
7955 $showtime = 0;
7956
7957 // Do not show current date when field not required (see selectDate() method)
7958 if (!$required && $value == '') {
7959 $value = '-1';
7960 }
7961
7962 // TODO Must also support $moreparam
7963 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7964 } elseif (in_array($type, array('datetime'))) {
7965 $tmp = explode(',', $size);
7966 $newsize = $tmp[0];
7967 $showtime = 1;
7968
7969 // Do not show current date when field not required (see selectDate() method)
7970 if (!$required && $value == '') {
7971 $value = '-1';
7972 }
7973
7974 // TODO Must also support $moreparam
7975 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7976 } elseif (in_array($type, array('duration'))) {
7977 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7978 } elseif (in_array($type, array('int', 'integer'))) {
7979 $tmp = explode(',', $size);
7980 $newsize = $tmp[0];
7981 $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' : '').'>';
7982 } elseif (in_array($type, array('real'))) {
7983 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7984 } elseif (preg_match('/varchar/', (string) $type)) {
7985 $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' : '').'>';
7986 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7987 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7988 } elseif (preg_match('/^text/', (string) $type)) {
7989 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7990 if (!empty($param['options'])) {
7991 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7992 //var_dump($param['options']);
7993 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7994 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7995 $out .= "<script>";
7996 $out .= '
7997 function handlemultiinputdisabling(htmlname){
7998 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
7999 multiinput = $("#"+htmlname+"_multiinput");
8000 multiinput.find("option").each(function(){
8001 tmpval = $("#"+htmlname).val();
8002 tmpvalarray = tmpval.split("\n");
8003 valtotest = $(this).val();
8004 if(tmpvalarray.includes(valtotest)){
8005 $(this).prop("disabled",true);
8006 } else {
8007 if($(this).prop("disabled") == true){
8008 console.log(valtotest)
8009 $(this).prop("disabled", false);
8010 }
8011 }
8012 });
8013 }
8014
8015 $(document).ready(function () {
8016 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
8017 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
8018 tmpvalarray = tmpval.split(",");
8019 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
8020 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8021 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
8022 if(tmpval == ""){
8023 tmpval = valtotest;
8024 } else {
8025 tmpval = tmpval + "\n" + valtotest;
8026 }
8027 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
8028 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8029 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
8030 } else {
8031 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
8032 }
8033 });
8034 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
8035 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8036 });
8037 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8038 })';
8039 $out .= "</script>";
8040 $value = str_replace(',', "\n", $value);
8041 }
8042 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8043 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8044 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
8045 } else {
8046 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8047 }
8048 } elseif (preg_match('/^html/', (string) $type)) {
8049 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8050 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8051 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8052 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8053 } else {
8054 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8055 }
8056 } elseif ($type == 'boolean') {
8057 $checked = '';
8058 if (!empty($value)) {
8059 $checked = ' checked value="1" ';
8060 } else {
8061 $checked = ' value="1" ';
8062 }
8063 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
8064 } elseif ($type == 'price') {
8065 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8066 $value = price($value);
8067 }
8068 $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>';
8069 } elseif ($type == 'stars') {
8070 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8071 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8072 $i = 1;
8073 while ($i <= $size) {
8074 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8075 $i++;
8076 }
8077 $out .= '</div>';
8078 $out .= '<script>
8079 jQuery(function($) { /* commonobject.class.php 1 */
8080 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8081 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8082 container.find(".star").each(function() {
8083 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8084 });
8085 container.find(".star").on("mouseover", function() {
8086 let selectedStar = $(this).data("value");
8087 container.find(".star").each(function() {
8088 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8089 });
8090 });
8091 container.on("mouseout", function() {
8092 container.find(".star").each(function() {
8093 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8094 });
8095 });
8096 container.find(".star").off("click").on("click", function() {
8097 selectedStars = $(this).data("value");
8098 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8099 selectedStars = 0;
8100 }
8101 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8102 container.find(".star").each(function() {
8103 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8104 });
8105 });
8106 });
8107 </script>';
8108 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8109 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8110 $value = price($value);
8111 }
8112 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8113 } elseif ($type == 'select') { // combo list
8114 $out = '';
8115 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8116 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8117 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8118 }
8119
8120 $tmpselect = '';
8121 $nbchoice = 0;
8122
8123 foreach ($param['options'] as $keyb => $valb) {
8124 if ((string) $keyb == '') {
8125 continue;
8126 }
8127 if (strpos($valb, "|") !== false) {
8128 list($valb, $parent) = explode('|', $valb);
8129 }
8130 $nbchoice++;
8131 $tmpselect .= '<option value="'.$keyb.'"';
8132 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8133 if (!empty($parent)) {
8134 $isDependList = 1;
8135 }
8136 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8137 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8138 }
8139
8140 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8141 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8142 $out .= '<option value="0">&nbsp;</option>';
8143 }
8144 $out .= $tmpselect;
8145 $out .= '</select>';
8146 } elseif ($type == 'sellist') {
8147 $out = '';
8148 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8149 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8150 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8151 }
8152
8153 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8154 if (is_array($param['options'])) {
8155 $tmpparamoptions = array_keys($param['options']);
8156 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8157
8158 $InfoFieldList = explode(":", $paramoptions[0], 5);
8159 // 0 : tableName
8160 // 1 : label field name
8161 // 2 : key fields name (if different of rowid)
8162 // optional parameters...
8163 // 3 : key field parent (for dependent lists). How this is used ?
8164 // 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".
8165 // 5 : string category type. This replace the filter.
8166 // 6 : ids categories list separated by comma for category root. This replace the filter.
8167 // 7 : sort field
8168
8169 // If there is filter
8170 if (! empty($InfoFieldList[4])) {
8171 $pos = 0;
8172 $parenthesisopen = 0;
8173 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8174 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8175 $parenthesisopen++;
8176 }
8177 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8178 $parenthesisopen--;
8179 }
8180 $pos++;
8181 }
8182 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8183 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8184 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8185 $InfoFieldList[4] = $tmpbefore;
8186 if ($tmpafter !== '') {
8187 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8188 }
8189 //var_dump($InfoFieldList);
8190
8191 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8192 $reg = array();
8193 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8194 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8195 }
8196
8197 //var_dump($InfoFieldList);
8198 }
8199
8200 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8201
8202 $parentName = '';
8203 $parentField = '';
8204
8205 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8206
8207 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8208 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8209 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8210 } else {
8211 $keyList = $InfoFieldList[2].' as rowid';
8212 }
8213 }
8214 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8215 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8216 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8217 $keyList .= ', main.'.$parentField;
8218 } else {
8219 $keyList .= ', '.$parentField;
8220 }
8221 }
8222
8223 $filter_categorie = false;
8224 if (count($InfoFieldList) > 5) {
8225 if ($InfoFieldList[0] == 'categorie') {
8226 $filter_categorie = true;
8227 }
8228 }
8229
8230 if (!$filter_categorie) {
8231 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8232 if (!empty($fields_label)) {
8233 $keyList .= ', ';
8234 $keyList .= implode(', ', $fields_label);
8235 }
8236
8237 $sqlwhere = '';
8238 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8239 $sql .= " FROM " . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8240
8241 if (!empty($InfoFieldList[4])) {
8242 // can use SELECT request
8243 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8244 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8245 }
8246
8247 // current object id can be use into filter
8248 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8249 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8250 } else {
8251 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8252 }
8253
8254 // We have to join on extrafield table
8255 $errstr = '';
8256 if (strpos($InfoFieldList[4], 'extra') !== false) {
8257 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8258 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8259 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8260 } else {
8261 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8262 }
8263 } else {
8264 $sqlwhere .= ' WHERE 1=1';
8265 }
8266
8267 // Add Usf filter on second line
8268 /*
8269 if ($Usf) {
8270 $errorstr = '';
8271 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8272 if (!$errorstr) {
8273 $sqlwhere .= $sqlusf;
8274 } else {
8275 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8276 }
8277 }
8278 */
8279
8280 // Some tables may have field, some other not. For the moment we disable it.
8281 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8282 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8283 }
8284 $sql .= $sqlwhere;
8285
8286 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8287 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8288 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8289 } else {
8290 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8291 }
8292 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8293 // print $sql;
8294
8295 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8296 $resql = $this->db->query($sql);
8297 if ($resql) {
8298 $out .= '<option value="0">&nbsp;</option>';
8299 $num = $this->db->num_rows($resql);
8300 $i = 0;
8301 while ($i < $num) {
8302 $labeltoshow = '';
8303 $obj = $this->db->fetch_object($resql);
8304
8305 // Several field into label (eq table:code|libelle:rowid)
8306 $notrans = false;
8307 $fields_label = explode('|', $InfoFieldList[1]);
8308 if (count($fields_label) > 1) {
8309 $notrans = true;
8310 foreach ($fields_label as $field_toshow) {
8311 $labeltoshow .= $obj->$field_toshow . ' ';
8312 }
8313 } else {
8314 $labeltoshow = $obj->{$InfoFieldList[1]};
8315 }
8316 $labeltoshow = dol_trunc($labeltoshow, 45);
8317
8318 if ($value == $obj->rowid) {
8319 foreach ($fields_label as $field_toshow) {
8320 $translabel = $langs->trans($obj->$field_toshow);
8321 if ($translabel != $obj->$field_toshow) {
8322 $labeltoshow = dol_trunc($translabel) . ' ';
8323 } else {
8324 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8325 }
8326 }
8327 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8328 } else {
8329 if (!$notrans) {
8330 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8331 if ($translabel != $obj->{$InfoFieldList[1]}) {
8332 $labeltoshow = dol_trunc($translabel, 18);
8333 } else {
8334 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8335 }
8336 }
8337 if (empty($labeltoshow)) {
8338 $labeltoshow = '(not defined)';
8339 }
8340 if ($value == $obj->rowid) {
8341 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8342 }
8343
8344 if (!empty($InfoFieldList[3]) && $parentField) {
8345 $parent = $parentName . ':' . $obj->{$parentField};
8346 $isDependList = 1;
8347 }
8348
8349 $out .= '<option value="' . $obj->rowid . '"';
8350 $out .= ($value == $obj->rowid ? ' selected' : '');
8351 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8352 $out .= '>' . $labeltoshow . '</option>';
8353 }
8354
8355 $i++;
8356 }
8357 $this->db->free($resql);
8358 } else {
8359 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8360 }
8361 } else {
8362 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8363 $categcode = $InfoFieldList[5];
8364 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8365 $tmpcategory = new Categorie($this->db);
8366 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8367 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8368 }
8369
8370 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8371 $out .= '<option value="0">&nbsp;</option>';
8372 foreach ($data as $data_key => $data_value) {
8373 $out .= '<option value="' . $data_key . '"';
8374 $out .= ($value == $data_key ? ' selected' : '');
8375 $out .= '>' . $data_value . '</option>';
8376 }
8377 }
8378 }
8379 $out .= '</select>';
8380 } elseif ($type == 'checkbox') {
8381 $value_arr = explode(',', $value);
8382 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8383 } elseif ($type == 'radio') {
8384 $out = '';
8385 foreach ($param['options'] as $keyopt => $valopt) {
8386 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8387 $out .= ' value="'.$keyopt.'"';
8388 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8389 $out .= ($value == $keyopt ? 'checked' : '');
8390 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8391 }
8392 } elseif ($type == 'chkbxlst') {
8393 if (is_array($value)) {
8394 $value_arr = $value;
8395 } else {
8396 $value_arr = explode(',', $value);
8397 }
8398
8399 if (is_array($param['options'])) {
8400 $tmpparamoptions = array_keys($param['options']);
8401 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8402
8403 $InfoFieldList = explode(":", $paramoptions[0], 5);
8404 // 0 : tableName
8405 // 1 : label field name
8406 // 2 : key fields name (if different of rowid)
8407 // optional parameters...
8408 // 3 : key field parent (for dependent lists). How this is used ?
8409 // 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".
8410 // 5 : string category type. This replace the filter.
8411 // 6 : ids categories list separated by comma for category root. This replace the filter.
8412 // 7 : sort field
8413
8414 // If there is a filter
8415 if (! empty($InfoFieldList[4])) {
8416 $pos = 0;
8417 $parenthesisopen = 0;
8418 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8419 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8420 $parenthesisopen++;
8421 }
8422 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8423 $parenthesisopen--;
8424 }
8425 $pos++;
8426 }
8427 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8428 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8429 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8430 $InfoFieldList[4] = $tmpbefore;
8431 if ($tmpafter !== '') {
8432 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8433 }
8434
8435 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8436 $reg = array();
8437 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8438 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8439 }
8440
8441 //var_dump($InfoFieldList);
8442 }
8443
8444 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8445
8446 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8447
8448 $parentName = '';
8449 $parentField = '';
8450
8451 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8452
8453 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8454 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8455 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8456 } else {
8457 $keyList = $InfoFieldList[2].' as rowid';
8458 }
8459 }
8460 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8461 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8462 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8463 $keyList .= ', main.'.$parentField;
8464 } else {
8465 $keyList .= ', '.$parentField;
8466 }
8467 }
8468
8469 $filter_categorie = false;
8470 if (count($InfoFieldList) > 5) {
8471 if ($InfoFieldList[0] == 'categorie') {
8472 $filter_categorie = true;
8473 }
8474 }
8475
8476 // Common filter
8477 if (!$filter_categorie) {
8478 $fields_label = explode('|', $InfoFieldList[1]);
8479 if (is_array($fields_label)) {
8480 $keyList .= ', ';
8481 $keyList .= implode(', ', $fields_label);
8482 }
8483
8484 $sqlwhere = '';
8485 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8486 $sql .= ' FROM ' . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8487
8488 if (!empty($InfoFieldList[4])) {
8489 // can use SELECT request
8490 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8491 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8492 }
8493
8494 // current object id can be use into filter
8495 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8496 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8497 } else {
8498 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8499 }
8500
8501 // We have to join on extrafield table
8502 $errstr = '';
8503 if (strpos($InfoFieldList[4], 'extra') !== false) {
8504 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8505 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8506 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8507 } else {
8508 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8509 }
8510 } else {
8511 $sqlwhere .= ' WHERE 1=1';
8512 }
8513
8514 // Add Usf filter on second line
8515 /*
8516 if ($Usf) {
8517 $errorstr = '';
8518 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8519 if (!$errorstr) {
8520 $sqlwhere .= $sqlusf;
8521 } else {
8522 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8523 }
8524 }
8525 */
8526
8527 // Some tables may have field, some other not. For the moment we disable it.
8528 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8529 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8530 }
8531 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8532 // print $sql;
8533
8534 $sql .= $sqlwhere;
8535
8536 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8537
8538 $resql = $this->db->query($sql);
8539 if ($resql) {
8540 $num = $this->db->num_rows($resql);
8541 $i = 0;
8542
8543 $data = array();
8544
8545 while ($i < $num) {
8546 $labeltoshow = '';
8547 $obj = $this->db->fetch_object($resql);
8548
8549 $notrans = false;
8550 // Several field into label (eq table:code|libelle:rowid)
8551 $fields_label = explode('|', $InfoFieldList[1]);
8552 if (count($fields_label) > 1) {
8553 $notrans = true;
8554 foreach ($fields_label as $field_toshow) {
8555 $labeltoshow .= $obj->$field_toshow . ' ';
8556 }
8557 } else {
8558 $labeltoshow = $obj->{$InfoFieldList[1]};
8559 }
8560 $labeltoshow = dol_trunc($labeltoshow, 45);
8561
8562 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8563 foreach ($fields_label as $field_toshow) {
8564 $translabel = $langs->trans($obj->$field_toshow);
8565 if ($translabel != $obj->$field_toshow) {
8566 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8567 } else {
8568 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8569 }
8570 }
8571
8572 $data[$obj->rowid] = $labeltoshow;
8573 } else {
8574 if (!$notrans) {
8575 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8576 if ($translabel != $obj->{$InfoFieldList[1]}) {
8577 $labeltoshow = dol_trunc($translabel, 18);
8578 } else {
8579 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8580 }
8581 }
8582 if (empty($labeltoshow)) {
8583 $labeltoshow = '(not defined)';
8584 }
8585
8586 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8587 $data[$obj->rowid] = $labeltoshow;
8588 }
8589
8590 if (!empty($InfoFieldList[3]) && $parentField) {
8591 $parent = $parentName . ':' . $obj->{$parentField};
8592 $isDependList = 1;
8593 }
8594
8595 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8596 }
8597
8598 $i++;
8599 }
8600 $this->db->free($resql);
8601
8602 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8603 } else {
8604 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8605 }
8606 } else {
8607 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8608 $categcode = $InfoFieldList[5];
8609 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8610 $tmpcategory = new Categorie($this->db);
8611 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8612 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8613 }
8614
8615 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8616 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8617 }
8618 }
8619 } elseif ($type == 'link') {
8620 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8621 // Filter can contains some ':' inside.
8622 $param_list = array_keys($param['options']);
8623 $param_list_array = explode(':', $param_list[0], 4);
8624
8625 $showempty = (($required && $default != '') ? 0 : 1);
8626
8627 if (!preg_match('/search_/', $keyprefix)) {
8628 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8629 // @phan-suppress-next-line PhanTypeMismatchProperty
8630 if (!empty($this->fields[$key]['picto'])) {
8631 $morecss .= ' widthcentpercentminusxx';
8632 } else {
8633 $morecss .= ' widthcentpercentminusx';
8634 }
8635 } else {
8636 // @phan-suppress-next-line PhanTypeMismatchProperty
8637 if (!empty($this->fields[$key]['picto'])) {
8638 $morecss .= ' widthcentpercentminusx';
8639 }
8640 }
8641 }
8642
8643 // $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.
8644
8645 // $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.
8646
8647 $objectfield = $val;
8648
8649 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8650 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8651
8652 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8653 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8654 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8655 list($class, $classfile) = explode(':', $param_list[0]);
8656 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8657 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8658 } else {
8659 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8660 }
8661 $paramforthenewlink = '';
8662 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8663 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8664 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8665 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8666 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8667 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8668 $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>';
8669 }
8670 }
8671 } elseif ($type == 'password') {
8672 // If prefix is 'search_', field is used as a filter, we use a common text field.
8673 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8674 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8675 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8676 } else {
8677 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8678 }
8679 } elseif ($type == 'array') {
8680 $newval = $val;
8681 $newval['type'] = 'varchar(256)';
8682
8683 $out = '';
8684 if (!empty($value)) {
8685 foreach ($value as $option) {
8686 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8687 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8688 }
8689 }
8690 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8691
8692 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8693 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8694
8695 if (!empty($conf->use_javascript_ajax)) {
8696 $out .= '
8697 <script nonce="'.getNonce().'">
8698 $(document).ready(function() {
8699 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8700 $("'.dol_escape_js($newInput).'").insertBefore(this);
8701 });
8702
8703 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8704 $(this).parent().remove();
8705 });
8706 });
8707 </script>';
8708 }
8709 }
8710 if (!empty($hidden)) {
8711 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8712 }
8713
8714 if ($isDependList == 1) {
8715 $out .= $this->getJSListDependancies('_common');
8716 }
8717 /* Add comments
8718 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8719 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8720 */
8721
8722 // Display error message for field
8723 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8724 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8725 }
8726
8727 return $out;
8728 }
8729
8743 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8744 {
8745 global $conf, $langs, $form;
8746
8747 // 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...)
8748
8749 if (!is_object($form)) {
8750 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8751 $form = new Form($this->db);
8752 }
8753
8754 //$label = empty($val['label']) ? '' : $val['label'];
8755 $type = empty($val['type']) ? '' : $val['type'];
8756 $size = empty($val['css']) ? '' : $val['css'];
8757
8758 $reg = array();
8759
8760 // Convert var to be able to share same code than showOutputField of extrafields
8761 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8762 $type = 'varchar'; // convert varchar(xx) int varchar
8763 $size = $reg[1];
8764 } elseif (preg_match('/varchar/', $type)) {
8765 $type = 'varchar'; // convert varchar(xx) int varchar
8766 }
8767 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8768 // @phan-suppress-next-line PhanTypeMismatchProperty
8769 if (empty($this->fields[$key]['multiinput'])) {
8770 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8771 }
8772 }
8773 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8774 $type = 'link';
8775 }
8776
8777 $default = empty($val['default']) ? '' : $val['default'];
8778 $computed = empty($val['computed']) ? '' : $val['computed'];
8779 $unique = empty($val['unique']) ? '' : $val['unique'];
8780 $required = empty($val['required']) ? '' : $val['required'];
8781 $param = array();
8782 $param['options'] = array();
8783
8784 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8785 $param['options'] = $val['arrayofkeyval'];
8786 }
8787 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8788 $type = 'link';
8789 $stringforoptions = $reg[1].':'.$reg[2];
8790 // Special case: Force addition of getnomurlparam1 to -1 for users
8791 if ($reg[1] == 'User') {
8792 $stringforoptions .= ':#getnomurlparam1=-1';
8793 }
8794 $param['options'] = array($stringforoptions => $stringforoptions);
8795 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8796 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8797 $type = 'sellist';
8798 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8799 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8800 $type = 'sellist';
8801 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8802 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8803 $type = 'sellist';
8804 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8805 $param['options'] = array($reg[1] => 'N');
8806 $type = 'chkbxlst';
8807 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8808 $param['options'] = array();
8809 $type = 'stars';
8810 $size = $reg[1];
8811 }
8812
8813 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8814 $list = (empty($val['list']) ? '' : $val['list']);
8815 $help = (empty($val['help']) ? '' : $val['help']);
8816 $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)
8817
8818 if ($hidden) {
8819 return '';
8820 }
8821
8822 // If field is a computed field, value must become result of compute
8823 if ($computed) {
8824 // Make the eval of compute string
8825 //var_dump($computed);
8826 $value = dol_eval((string) $computed, 1, 0, '2');
8827 }
8828
8829 if (empty($morecss)) {
8830 if ($type == 'date') {
8831 $morecss = 'minwidth100imp';
8832 } elseif ($type == 'datetime' || $type == 'timestamp') {
8833 $morecss = 'minwidth200imp';
8834 } elseif (in_array($type, array('int', 'double', 'price'))) {
8835 $morecss = 'maxwidth75';
8836 } elseif ($type == 'url') {
8837 $morecss = 'minwidth400';
8838 } elseif ($type == 'boolean') {
8839 $morecss = '';
8840 } else {
8841 if (is_numeric($size) && round((float) $size) < 12) {
8842 $morecss = 'minwidth100';
8843 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8844 $morecss = 'minwidth200';
8845 } else {
8846 $morecss = 'minwidth400';
8847 }
8848 }
8849 }
8850
8851 // Format output value differently according to properties of field
8852 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8853 // @phan-suppress-next-line PhanTypeMismatchProperty
8854 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.
8855 $value = $this->getNomUrl(1, '', 0, '', 1);
8856 }
8857 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8858 $value = $this->getLibStatut(3);
8859 } elseif ($type == 'date') {
8860 if (!empty($value)) {
8861 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8862 } else {
8863 $value = '';
8864 }
8865 } elseif ($type == 'datetime' || $type == 'timestamp') {
8866 if (!empty($value)) {
8867 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8868 } else {
8869 $value = '';
8870 }
8871 } elseif ($type == 'duration') {
8872 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8873 if (!is_null($value) && $value !== '') {
8874 $value = convertSecondToTime((int) $value, 'allhourmin');
8875 }
8876 } elseif ($type == 'double' || $type == 'real') {
8877 if (!is_null($value) && $value !== '') {
8878 $value = price($value);
8879 }
8880 } elseif ($type == 'boolean') {
8881 $checked = '';
8882 if (!empty($value)) {
8883 $checked = ' checked ';
8884 }
8885 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8886 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8887 } else {
8888 $value = yn($value ? 1 : 0);
8889 }
8890 } elseif ($type == 'mail' || $type == 'email') {
8891 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8892 } elseif ($type == 'url') {
8893 $value = dol_print_url((string) $value, '_blank', 32, 1);
8894 } elseif ($type == 'phone') {
8895 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8896 } elseif ($type == 'ip') {
8897 $value = dol_print_ip((string) $value, 0);
8898 } elseif ($type == 'stars') {
8899 $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 : '').'>';
8900 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8901 $i = 1;
8902 while ($i <= $size) {
8903 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8904 $i++;
8905 }
8906 $value .= '</div>';
8907 $value .= '<script>
8908 $(document).ready(function() { /* commonobject.class.php 2 */
8909 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8910 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8911 container.find(".star").each(function() {
8912 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8913 });
8914 container.find(".star").on("mouseover", function() {
8915 let selectedStar = $(this).data("value");
8916 container.find(".star").each(function() {
8917 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8918 });
8919 });
8920 container.on("mouseout", function() {
8921 container.find(".star").each(function() {
8922 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8923 });
8924 });
8925 container.find(".star").off("click").on("click", function() {
8926 selectedStars = $(this).data("value");
8927 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8928 selectedStars = 0;
8929 }
8930 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8931 container.find(".star").each(function() {
8932 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8933 });
8934 $.ajax({
8935 url: "ajax/'.$this->element.'.php",
8936 method: "POST",
8937 data: {
8938 objectId: "'.$this->id.'",
8939 field: "'.$keyprefix.$key.$keysuffix.'",
8940 value: selectedStars,
8941 token: "'.newToken().'"
8942 },
8943 success: function(response) {
8944 var res = JSON.parse(response);
8945 console[res.status === "success" ? "log" : "error"](res.message);
8946 },
8947 error: function(xhr, status, error) {
8948 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8949 }
8950 });
8951 });
8952 });
8953 </script>';
8954 } elseif ($type == 'price') {
8955 if (!is_null($value) && $value !== '') {
8956 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8957 }
8958 } elseif ($type == 'select') {
8959 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8960 if (strpos($value, "|") !== false) {
8961 $value = $langs->trans(explode('|', $value)[0]);
8962 } elseif (! is_numeric($value)) {
8963 $value = $langs->trans($value);
8964 }
8965 } elseif ($type == 'sellist') {
8966 $param_list = array_keys($param['options']);
8967 $InfoFieldList = explode(":", $param_list[0]);
8968
8969 $selectkey = "rowid";
8970 $keyList = 'rowid';
8971
8972 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8973 $selectkey = $InfoFieldList[2];
8974 $keyList = $InfoFieldList[2].' as rowid';
8975 }
8976
8977 $fields_label = explode('|', $InfoFieldList[1]);
8978 if (is_array($fields_label)) {
8979 $keyList .= ', ';
8980 $keyList .= implode(', ', $fields_label);
8981 }
8982
8983 $filter_categorie = false;
8984 if (count($InfoFieldList) > 5) {
8985 if ($InfoFieldList[0] == 'categorie') {
8986 $filter_categorie = true;
8987 }
8988 }
8989
8990 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
8991 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
8992 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8993 $sql .= ' as main';
8994 }
8995 if ($selectkey == 'rowid' && empty($value)) {
8996 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = 0";
8997 } elseif ($selectkey == 'rowid') {
8998 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = ".((int) $value);
8999 } else {
9000 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = '".$this->db->escape((string) $value)."'";
9001 }
9002
9003 //$sql.= ' AND entity = '.$conf->entity;
9004
9005 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
9006 $resql = $this->db->query($sql);
9007 if ($resql) {
9008 if (!$filter_categorie) {
9009 $value = ''; // value was used, so now we reset it to use it to build final output
9010 $numrows = $this->db->num_rows($resql);
9011 if ($numrows) {
9012 $obj = $this->db->fetch_object($resql);
9013
9014 // Several field into label (eq table:code|libelle:rowid)
9015 $fields_label = explode('|', $InfoFieldList[1]);
9016
9017 if (is_array($fields_label) && count($fields_label) > 1) {
9018 foreach ($fields_label as $field_toshow) {
9019 $translabel = '';
9020 if (!empty($obj->$field_toshow)) {
9021 $translabel = $langs->trans($obj->$field_toshow);
9022 }
9023 if ($translabel != $field_toshow) {
9024 $value .= dol_trunc($translabel, 18) . ' ';
9025 } else {
9026 $value .= $obj->$field_toshow . ' ';
9027 }
9028 }
9029 } else {
9030 $translabel = '';
9031 if (!empty($obj->{$InfoFieldList[1]})) {
9032 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9033 }
9034 if ($translabel != $obj->{$InfoFieldList[1]}) {
9035 $value = dol_trunc($translabel, 18);
9036 } else {
9037 $value = $obj->{$InfoFieldList[1]};
9038 }
9039 }
9040 }
9041 } else {
9042 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9043
9044 $toprint = array();
9045 $obj = $this->db->fetch_object($resql);
9046 $c = new Categorie($this->db);
9047 $c->fetch($obj->rowid);
9048 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9049 foreach ($ways as $way) {
9050 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9051 }
9052 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9053 }
9054 } else {
9055 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9056 }
9057 } elseif ($type == 'radio') {
9058 $value = $param['options'][(string) $value];
9059 } elseif ($type == 'checkbox') {
9060 $value_arr = explode(',', (string) $value);
9061 $value = '';
9062 if (is_array($value_arr) && count($value_arr) > 0) {
9063 $toprint = array();
9064 foreach ($value_arr as $keyval => $valueval) {
9065 if (!empty($valueval)) {
9066 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9067 }
9068 }
9069 if (!empty($toprint)) {
9070 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9071 }
9072 }
9073 } elseif ($type == 'chkbxlst') {
9074 $value_arr = (isset($value) ? explode(',', $value) : array());
9075
9076 $param_list = array_keys($param['options']);
9077 $InfoFieldList = explode(":", $param_list[0]);
9078
9079 $selectkey = "rowid";
9080 $keyList = 'rowid';
9081
9082 if (count($InfoFieldList) >= 3) {
9083 $selectkey = $InfoFieldList[2];
9084 $keyList = $InfoFieldList[2].' as rowid';
9085 }
9086
9087 $fields_label = explode('|', $InfoFieldList[1]);
9088 if (is_array($fields_label)) {
9089 $keyList .= ', ';
9090 $keyList .= implode(', ', $fields_label);
9091 }
9092
9093 $filter_categorie = false;
9094 if (count($InfoFieldList) > 5) {
9095 if ($InfoFieldList[0] == 'categorie') {
9096 $filter_categorie = true;
9097 }
9098 }
9099
9100 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9101 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9102 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9103 $sql .= ' as main';
9104 }
9105 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9106 // $sql.= ' AND entity = '.$conf->entity;
9107
9108 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9109 $resql = $this->db->query($sql);
9110 if ($resql) {
9111 if (!$filter_categorie) {
9112 $value = ''; // value was used, so now we reset it to use it to build final output
9113 $toprint = array();
9114 while ($obj = $this->db->fetch_object($resql)) {
9115 // Several field into label (eq table:code|libelle:rowid)
9116 $fields_label = explode('|', $InfoFieldList[1]);
9117 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9118 if (is_array($fields_label) && count($fields_label) > 1) {
9119 foreach ($fields_label as $field_toshow) {
9120 $translabel = '';
9121 if (!empty($obj->$field_toshow)) {
9122 $translabel = $langs->trans($obj->$field_toshow);
9123 }
9124 if ($translabel != $field_toshow) {
9125 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9126 } else {
9127 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9128 }
9129 }
9130 } else {
9131 $translabel = '';
9132 if (!empty($obj->{$InfoFieldList[1]})) {
9133 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9134 }
9135 if ($translabel != $obj->{$InfoFieldList[1]}) {
9136 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9137 } else {
9138 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9139 }
9140 }
9141 }
9142 }
9143 } else {
9144 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9145
9146 $toprint = array();
9147 while ($obj = $this->db->fetch_object($resql)) {
9148 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9149 $c = new Categorie($this->db);
9150 $c->fetch($obj->rowid);
9151 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9152 foreach ($ways as $way) {
9153 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9154 }
9155 }
9156 }
9157 }
9158 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9159 } else {
9160 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9161 }
9162 } elseif ($type == 'link') {
9163 $out = '';
9164
9165 // only if something to display (perf)
9166 if ($value) {
9167 $param_list = array_keys($param['options']);
9168 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9169 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9170
9171 $InfoFieldList = explode(":", $param_list[0]);
9172
9173 $classname = $InfoFieldList[0];
9174 $classpath = $InfoFieldList[1];
9175
9176 // Set $getnomurlparam1 et getnomurlparam2
9177 $getnomurlparam = 3;
9178 $getnomurlparam2 = '';
9179 $regtmp = array();
9180 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9181 $getnomurlparam = $regtmp[1];
9182 }
9183 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9184 $getnomurlparam2 = $regtmp[1];
9185 }
9186
9187 if (!empty($classpath)) {
9188 dol_include_once($InfoFieldList[1]);
9189
9190 if ($classname && !class_exists($classname)) {
9191 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9192 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9193 $element_prop = getElementProperties($classname);
9194 if ($element_prop) {
9195 $classname = $element_prop['classname'];
9196 }
9197 }
9198
9199
9200 if ($classname && class_exists($classname)) {
9201 $object = new $classname($this->db);
9202 '@phan-var-force CommonObject $object';
9203 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9204 '@phan-var-force Product $object';
9205 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9206 } else {
9207 $result = $object->fetch($value);
9208 }
9209 if ($result > 0) {
9210 if ($object->element === 'product') {
9211 '@phan-var-force Product $object';
9212 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9213 if (isset($val['get_name_url_params'])) {
9214 $get_name_url_params = explode(':', $val['get_name_url_params']);
9215 if (!empty($get_name_url_params)) {
9216 $param_num_max = count($get_name_url_param_arr) - 1;
9217 foreach ($get_name_url_params as $param_num => $param_value) {
9218 if ($param_num > $param_num_max) {
9219 break;
9220 }
9221 $get_name_url_param_arr[$param_num] = $param_value;
9222 }
9223 }
9224 }
9225
9229 $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]);
9230 } else {
9231 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9232 }
9233 } else {
9234 $value = '';
9235 }
9236 }
9237 } else {
9238 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9239 return 'Error bad setup of extrafield';
9240 }
9241 } else {
9242 $value = '';
9243 }
9244 } elseif ($type == 'password') {
9245 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9246 //$value = preg_replace('/./i', '*', $value);
9247 } elseif ($type == 'array') {
9248 if (is_array($value)) {
9249 $value = implode('<br>', $value);
9250 } else {
9251 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9252 return 'Error unexpected result from code evaluation';
9253 }
9254 } else { // text|html|varchar
9255 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9256 $value = str_replace(',', "\n", $value);
9257 }
9258 $value = dol_htmlentitiesbr((string) $value);
9259 }
9260
9261 //print $type.'-'.$size.'-'.$value;
9262 $out = $value;
9263
9264 return is_null($out) ? '' : $out;
9265 }
9266
9273 public function clearFieldError($fieldKey)
9274 {
9275 $this->error = '';
9276 unset($this->validateFieldsErrors[$fieldKey]);
9277 }
9278
9286 public function setFieldError($fieldKey, $msg = '')
9287 {
9288 global $langs;
9289 if (empty($msg)) {
9290 $msg = $langs->trans("UnknownError");
9291 }
9292
9293 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9294 }
9295
9302 public function getFieldError($fieldKey)
9303 {
9304 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9305 return $this->validateFieldsErrors[$fieldKey];
9306 }
9307 return '';
9308 }
9309
9318 public function validateField($fields, $fieldKey, $fieldValue)
9319 {
9320 global $langs;
9321
9322 if (!class_exists('Validate')) {
9323 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9324 }
9325
9326 $this->clearFieldError($fieldKey);
9327
9328 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9329 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9330 return false;
9331 }
9332
9333 $val = $fields[$fieldKey];
9334
9335 $param = array();
9336 $param['options'] = array();
9337 $type = $val['type'];
9338
9339 $required = false;
9340 if (isset($val['notnull']) && $val['notnull'] === 1) {
9341 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9342 $required = true;
9343 }
9344
9345 $maxSize = 0;
9346 $minSize = 0;
9347
9348 //
9349 // PREPARE Elements
9350 //
9351 $reg = array();
9352
9353 // Convert var to be able to share same code than showOutputField of extrafields
9354 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9355 $type = 'varchar'; // convert varchar(xx) int varchar
9356 $maxSize = (int) $reg[1];
9357 } elseif (preg_match('/varchar/', $type)) {
9358 $type = 'varchar'; // convert varchar(xx) int varchar
9359 }
9360
9361 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9362 $type = 'select';
9363 }
9364
9365 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9366 $type = 'link';
9367 }
9368
9369 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9370 $param['options'] = $val['arrayofkeyval'];
9371 }
9372
9373 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9374 $type = 'link';
9375 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9376 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9377 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9378 $type = 'sellist';
9379 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9380 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9381 $type = 'sellist';
9382 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9383 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9384 $type = 'sellist';
9385 }
9386
9387 //
9388 // TEST Value
9389 //
9390
9391 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9392 $validate = new Validate($this->db, $langs);
9393
9394
9395 // little trick : to perform tests with good performances sort tests by quick to low
9396
9397 //
9398 // COMMON TESTS
9399 //
9400
9401 // Required test and empty value
9402 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9403 $this->setFieldError($fieldKey, $validate->error);
9404 return false;
9405 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9406 // if no value sent and the field is not mandatory, no need to perform tests
9407 return true;
9408 }
9409
9410 // MAX Size test
9411 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9412 $this->setFieldError($fieldKey, $validate->error);
9413 return false;
9414 }
9415
9416 // MIN Size test
9417 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9418 $this->setFieldError($fieldKey, $validate->error);
9419 return false;
9420 }
9421
9422 //
9423 // TESTS for TYPE
9424 //
9425
9426 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9427 if (!$validate->isTimestamp($fieldValue)) {
9428 $this->setFieldError($fieldKey, $validate->error);
9429 return false;
9430 } else {
9431 return true;
9432 }
9433 } elseif ($type == 'duration') {
9434 if (!$validate->isDuration($fieldValue)) {
9435 $this->setFieldError($fieldKey, $validate->error);
9436 return false;
9437 } else {
9438 return true;
9439 }
9440 } elseif (in_array($type, array('double', 'real', 'price'))) {
9441 // is numeric
9442 if (!$validate->isNumeric($fieldValue)) {
9443 $this->setFieldError($fieldKey, $validate->error);
9444 return false;
9445 } else {
9446 return true;
9447 }
9448 } elseif ($type == 'boolean') {
9449 if (!$validate->isBool($fieldValue)) {
9450 $this->setFieldError($fieldKey, $validate->error);
9451 return false;
9452 } else {
9453 return true;
9454 }
9455 } elseif ($type == 'mail' || $type == 'email') {
9456 if (!$validate->isEmail($fieldValue)) {
9457 $this->setFieldError($fieldKey, $validate->error);
9458 return false;
9459 }
9460 } elseif ($type == 'url') {
9461 if (!$validate->isUrl($fieldValue)) {
9462 $this->setFieldError($fieldKey, $validate->error);
9463 return false;
9464 } else {
9465 return true;
9466 }
9467 } elseif ($type == 'phone') {
9468 if (!$validate->isPhone($fieldValue)) {
9469 $this->setFieldError($fieldKey, $validate->error);
9470 return false;
9471 } else {
9472 return true;
9473 }
9474 } elseif ($type == 'select' || $type == 'radio') {
9475 if (!isset($param['options'][$fieldValue])) {
9476 $this->error = $langs->trans('RequireValidValue');
9477 return false;
9478 } else {
9479 return true;
9480 }
9481 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9482 $param_list = array_keys($param['options']);
9483 $InfoFieldList = explode(":", $param_list[0]);
9484 $value_arr = explode(',', $fieldValue);
9485
9486 $selectkey = "rowid";
9487 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9488 $selectkey = $InfoFieldList[2];
9489 }
9490
9491 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9492 $this->setFieldError($fieldKey, $validate->error);
9493 return false;
9494 } else {
9495 return true;
9496 }
9497 } elseif ($type == 'link') {
9498 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9499 $InfoFieldList = explode(":", $param_list[0]);
9500 $classname = $InfoFieldList[0];
9501 $classpath = $InfoFieldList[1];
9502 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9503 $lastIsFetchableError = $validate->error;
9504
9505 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9506 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9507 return true;
9508 }
9509
9510 $this->setFieldError($fieldKey, $lastIsFetchableError);
9511 return false;
9512 } else {
9513 return true;
9514 }
9515 }
9516
9517 // if no test failed all is ok
9518 return true;
9519 }
9520
9534 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9535 {
9536 global $db, $conf, $langs, $action, $form, $hookmanager;
9537 global $objectoffield;
9538
9539 if (!is_object($form)) {
9540 $form = new Form($db);
9541 }
9542 if (!is_object($extrafields)) {
9543 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9544 return 'Bad parameter extrafields for showOptionals';
9545 }
9546 if (!is_array($extrafields->attributes[$this->table_element])) {
9547 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9548 }
9549
9550 // Ensure $objectoffield is available for dol_eval visibility formulas.
9551 $objectoffield = $this;
9552
9553 $out = '';
9554
9555 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9556 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9557 $hookResPrint = $hookmanager->resPrint;
9558
9559 if (empty($reshook)) {
9560 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) {
9561 $out .= "\n";
9562 $out .= '<!-- commonobject:showOptionals --> ';
9563 $out .= "\n";
9564
9565 $nbofextrafieldsshown = 0;
9566 $e = 0; // var to manage the modulo (odd/even)
9567
9568 $lastseparatorkeyfound = '';
9569 $extrafields_collapse_num = '';
9570 $extrafields_collapse_num_old = '';
9571 $i = 0;
9572
9573 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9574 $i++;
9575
9576 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9577 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9578 continue;
9579 }
9580
9581 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9582 $enabled = 1;
9583 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9584 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9585 }
9586 if (empty($enabled)) {
9587 continue;
9588 }
9589
9590 $visibility = 1;
9591 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9592 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9593 }
9594
9595 $perms = 1;
9596 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9597 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9598 }
9599
9600 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9601 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9602 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9603 // We need to make sure, that the values of hidden extrafields are also part of $_POST. Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9604 $ef_name = 'options_' . $key;
9605 $ef_value = $this->array_options[$ef_name] ?? '';
9606 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . $ef_value . '" />' . "\n";
9607 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9608 } elseif ($mode == 'view' && empty($visibility)) {
9609 continue;
9610 }
9611 if (empty($perms)) {
9612 continue;
9613 }
9614
9615 // Load language if required
9616 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9617 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9618 }
9619
9620 $colspan = 0;
9621 $value = null;
9622 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9623 if (array_key_exists('cols', $params)) {
9624 $colspan = $params['cols'];
9625 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9626 $reg = array();
9627 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9628 $colspan = $reg[1];
9629 } else {
9630 $colspan = $params['colspan'];
9631 }
9632 }
9633 }
9634 $colspan = intval($colspan);
9635
9636 switch ($mode) {
9637 case "view":
9638 $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
9639 break;
9640 case "create":
9641 case "edit":
9642 // We get the value of property found with GETPOST so it takes into account:
9643 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9644 $check = 'alphanohtml';
9645 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9646 $check = 'restricthtml';
9647 }
9648 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9649 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9650 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9651 if (is_array($getposttemp)) {
9652 // $getposttemp is an array but following code expects a comma separated string
9653 $value = implode(",", $getposttemp);
9654 } else {
9655 $value = $getposttemp;
9656 }
9657 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9658 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9659 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('varchar', 'char'))) {
9660 $value = (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] !== '') ? $this->array_options["options_".$key] : '';
9661 } else {
9662 $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.
9663 }
9664 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9665 break;
9666 }
9667
9668 $nbofextrafieldsshown++;
9669
9670 // Output value of the current field
9671 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9672 $extrafields_collapse_num = $key;
9673 /*
9674 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9675 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9676 $extrafield_param_list = array_keys($extrafield_param['options']);
9677
9678 if (count($extrafield_param_list) > 0) {
9679 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9680
9681 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9682 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9683 $extrafields_collapse_num = $key;
9684 }
9685 }
9686 }
9687 */
9688
9689 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9690 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9691
9692 $lastseparatorkeyfound = $key;
9693 } else {
9694 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9695
9696 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9697 $csstyle = '';
9698 if (is_array($params) && count($params) > 0) {
9699 if (array_key_exists('class', $params)) {
9700 $class .= $params['class'].' ';
9701 }
9702 if (array_key_exists('style', $params)) {
9703 $csstyle = $params['style'];
9704 }
9705 }
9706
9707 // add html5 elements
9708 $domData = ' data-element="extrafield"';
9709 $domData .= ' data-targetelement="'.$this->element.'"';
9710 $domData .= ' data-targetid="'.$this->id.'"';
9711
9712 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9713 if ($display_type == 'card') {
9714 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9715 $colspan = 0;
9716 }
9717
9718 if ($action == 'selectlines') {
9719 $colspan++;
9720 }
9721 }
9722
9723 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9724 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9725 $force_values_on_change_company = (
9726 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9727 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9728 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9729 );
9730 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9731 $value = $this->array_options['options_'.$key] ?? $value;
9732 }
9733
9734 // Convert date into timestamp format (value in memory must be a timestamp)
9735 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9736 $datenotinstring = null;
9737 if (array_key_exists('options_'.$key, $this->array_options)) {
9738 $datenotinstring = $this->array_options['options_'.$key];
9739 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9740 $datenotinstring = $this->db->jdate($datenotinstring);
9741 }
9742 }
9743 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9744 $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;
9745 }
9746 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9747 $datenotinstring = null;
9748 if (array_key_exists('options_'.$key, $this->array_options)) {
9749 $datenotinstring = $this->array_options['options_'.$key];
9750 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9751 $datenotinstring = $this->db->jdate($datenotinstring);
9752 }
9753 }
9754 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9755 $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;
9756 }
9757 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9758 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9759 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9760 $value = price2num($value);
9761 } elseif (isset($this->array_options['options_'.$key])) {
9762 $value = $this->array_options['options_'.$key];
9763 }
9764 }
9765
9766 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9767 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9768 if ($action == 'create' || $mode == 'create') {
9769 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9770 }
9771 }
9772
9773 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9774 if ($action == 'create') {
9775 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9776 }
9777 }
9778
9779
9780 $labeltoshow = $langs->trans($label);
9781 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9782 if ($display_type == 'card') {
9783 $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.' >';
9784 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9785 $out .= '<td></td>';
9786 }
9787 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9788 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9789 $out .= ' tdtop';
9790 }
9791 } elseif ($display_type == 'line') {
9792 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9793 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9794 }
9795 //$out .= "titlefield";
9796 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9797 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9798 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9799 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9800 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9801 $out .= ' fieldrequired';
9802 }
9803 }
9804 $out .= '">';
9805 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9806 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9807 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9808 } else {
9809 $out .= $labeltoshow;
9810 }
9811 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9812 $out .= '&nbsp;<span style="color: red">*</span>';
9813 }
9814 } else {
9815 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9816 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9817 } else {
9818 $out .= $labeltoshow;
9819 }
9820 }
9821
9822 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9823
9824 // Second column
9825 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9826 if ($display_type == 'card') {
9827 // 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
9828 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9829 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9830 $out .= '>';
9831 } elseif ($display_type == 'line') {
9832 $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].'">';
9833 }
9834
9835 switch ($mode) {
9836 case "view":
9837 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9838 break;
9839 case "create":
9840 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9841 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9842 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9843 }
9844 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9845 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9846 break;
9847 case "edit":
9848 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9849 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9850 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9851 }
9852 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9853 break;
9854 }
9855
9856 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9857 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9858 $e++;
9859 }
9860 }
9861 $out .= "\n";
9862 // Add code to manage list depending on others
9863 if (!empty($conf->use_javascript_ajax)) {
9864 $out .= $this->getJSListDependancies();
9865 }
9866
9867 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9868
9869 if (empty($nbofextrafieldsshown)) {
9870 $out = '';
9871 }
9872 }
9873 }
9874
9875 $out .= $hookResPrint;
9876
9877 return $out;
9878 }
9879
9884 public function getJSListDependancies($type = '_extra')
9885 {
9886 $out = '
9887 <script nonce="'.getNonce().'">
9888 jQuery(document).ready(function() {
9889 function showOptions'.$type.'(child_list, parent_list, orig_select)
9890 {
9891 var val = $("select[name=\""+parent_list+"\"]").val();
9892 var parentVal = parent_list + ":" + val;
9893 if(typeof val == "string"){
9894 if(val != "") {
9895 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9896 $("select[name=\""+child_list+"\"] option[parent]").remove();
9897 $("select[name=\""+child_list+"\"]").append(options);
9898 } else {
9899 var options = orig_select.find("option[parent]").clone();
9900 $("select[name=\""+child_list+"\"] option[parent]").remove();
9901 $("select[name=\""+child_list+"\"]").append(options);
9902 }
9903 } else if(val > 0) {
9904 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9905 $("select[name=\""+child_list+"\"] option[parent]").remove();
9906 $("select[name=\""+child_list+"\"]").append(options);
9907 } else {
9908 var options = orig_select.find("option[parent]").clone();
9909 $("select[name=\""+child_list+"\"] option[parent]").remove();
9910 $("select[name=\""+child_list+"\"]").append(options);
9911 }
9912 }
9913 function setListDependencies'.$type.'() {
9914 jQuery("select option[parent]").parent().each(function() {
9915 var orig_select = {};
9916 var child_list = $(this).attr("name");
9917 orig_select[child_list] = $(this).clone();
9918 var parent = $(this).find("option[parent]:first").attr("parent");
9919 var infos = parent.split(":");
9920 var parent_list = infos[0];
9921
9922 //Hide daughters lists
9923 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9924 $("#"+child_list).hide();
9925 //Show mother lists
9926 } else if ($("#"+parent_list).val() != 0){
9927 $("#"+parent_list).show();
9928 }
9929 //Show the child list if the parent list value is selected
9930 $("select[name=\""+parent_list+"\"]").click(function() {
9931 if ($(this).val() != 0){
9932 $("#"+child_list).show()
9933 }
9934 });
9935
9936 //When we change parent list
9937 $("select[name=\""+parent_list+"\"]").change(function() {
9938 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9939 //Select the value 0 on child list after a change on the parent list
9940 $("#"+child_list).val(0).trigger("change");
9941 //Hide child lists if the parent value is set to 0
9942 if ($(this).val() == 0){
9943 $("#"+child_list).hide();
9944 }
9945 });
9946 });
9947 }
9948
9949 setListDependencies'.$type.'();
9950 });
9951 </script>'."\n";
9952 return $out;
9953 }
9954
9960 public function getRights()
9961 {
9962 global $user;
9963
9964 $module = empty($this->module) ? '' : $this->module;
9965 $element = $this->element;
9966
9967 if ($element == 'facturerec') {
9968 $element = 'facture';
9969 } elseif ($element == 'invoice_supplier_rec') {
9970 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9971 } elseif ($module && $user->hasRight($module, $element)) {
9972 // for modules built with ModuleBuilder
9973 return $user->hasRight($module, $element);
9974 }
9975
9976 return $user->rights->$element;
9977 }
9978
9991 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9992 {
9993 global $hookmanager;
9994
9995 $parameters = array(
9996 'origin_id' => $origin_id,
9997 'dest_id' => $dest_id,
9998 'tables' => $tables,
9999 );
10000 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
10001 if ($reshook) {
10002 return true; // replacement code
10003 } elseif ($reshook < 0) {
10004 return $ignoreerrors === 1; // failure
10005 } // reshook = 0 => execute normal code
10006
10007 foreach ($tables as $table) {
10008 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
10009
10010 if (!$dbs->query($sql)) {
10011 if ($ignoreerrors) {
10012 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.
10013 }
10014 //$this->errors = $db->lasterror();
10015 return false;
10016 }
10017 }
10018
10019 return true;
10020 }
10021
10034 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10035 {
10036 foreach ($tables as $table) {
10037 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
10038
10039 if (!$dbs->query($sql)) {
10040 if ($ignoreerrors) {
10041 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.
10042 }
10043 //$this->errors = $db->lasterror();
10044 return false;
10045 }
10046 }
10047
10048 return true;
10049 }
10050
10063 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
10064 {
10065 global $conf;
10066
10067 $buyPrice = 0;
10068
10069 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10070 // When ForceBuyingPriceIfNull is set
10071 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10072 } else {
10073 // Get cost price for margin calculation
10074 if (!empty($fk_product) && $fk_product > 0) {
10075 $result = 0;
10076 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10077 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10078 $product = new Product($this->db);
10079 $result = $product->fetch($fk_product);
10080 if ($result <= 0) {
10081 $this->errors[] = 'ErrorProductIdDoesNotExists';
10082 return -1;
10083 }
10084 if ($product->cost_price > 0) {
10085 $buyPrice = $product->cost_price;
10086 } elseif ($product->pmp > 0) {
10087 $buyPrice = $product->pmp;
10088 }
10089 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10090 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10091 $product = new Product($this->db);
10092 $result = $product->fetch($fk_product);
10093 if ($result <= 0) {
10094 $this->errors[] = 'ErrorProductIdDoesNotExists';
10095 return -1;
10096 }
10097 if ($product->pmp > 0) {
10098 $buyPrice = $product->pmp;
10099 }
10100 }
10101
10102 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10103 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10104 $productFournisseur = new ProductFournisseur($this->db);
10105 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
10106 $buyPrice = $productFournisseur->fourn_unitprice;
10107 } elseif ($result < 0) {
10108 $this->errors[] = $productFournisseur->error;
10109 return -2;
10110 }
10111 }
10112 }
10113 }
10114
10115 return (float) $buyPrice;
10116 }
10117
10125 public function getDataToShowPhoto($modulepart, $imagesize)
10126 {
10127 // See getDataToShowPhoto() implemented by Product for example.
10128 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10129 }
10130
10131
10132 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10152 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')
10153 {
10154 // phpcs:enable
10155 global $user, $langs;
10156
10157 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10158 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10159 include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
10160
10161 $sortfield = 'position_name';
10162 $sortorder = 'asc';
10163
10164 $dir = $sdir.'/';
10165 $pdir = '/';
10166
10167 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10168 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10169
10170 // For backward compatibility
10171 if ($modulepart == 'product') {
10172 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10173 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10174 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10175 }
10176 }
10177 if ($modulepart == 'category') {
10178 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10179 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10180 }
10181
10182 // Defined relative dir to DOL_DATA_ROOT
10183 $relativedir = '';
10184 if ($dir) {
10185 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10186 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10187 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10188 }
10189
10190 $dirthumb = $dir.'thumbs/';
10191 $pdirthumb = $pdir.'thumbs/';
10192
10193 $return = '<!-- Photo -->'."\n";
10194 $nbphoto = 0;
10195
10196 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10197
10198 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10199 {
10200 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10201 $filearray=array_merge($filearray, $filearrayold);
10202 }*/
10203
10204 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10205 '@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';
10206
10207 $useLinkPathPhoto = false;
10208
10209 if (getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO')) {
10210 $link = new Link($this->db);
10211 $links = array();
10212 $link->fetchAll($links, 'product', $this->id);
10213
10214 if (count($links) > 0) {
10215 $useLinkPathPhoto = true;
10216 }
10217
10218 if ($useLinkPathPhoto) {
10219 // Start table or div based on nbbyrow
10220 if ($nbbyrow > 0) {
10221 $return .= '<table class="valigntop center centpercent" style="border:0; padding:2px; border-spacing:2px; border-collapse: separate;">';
10222 }
10223
10224 $i = 0;
10225 foreach ($links as $link) {
10226 if (!empty($link->url) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $link->url)) {
10227 $url = dol_escape_htmltag($link->url);
10228 $i++;
10229 $nbphoto++;
10230
10231 if ($nbbyrow > 0 && ($i % $nbbyrow == 1)) {
10232 $return .= '<tr class="center valignmiddle">';
10233 }
10234
10235 if ($nbbyrow > 0) {
10236 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%" class="photo">';
10237 } else {
10238 $return .= '<div class="inline-block">';
10239 }
10240
10241 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.$url.'" title="External image">';
10242
10243 if ($nbbyrow > 0) {
10244 $return .= '</td>';
10245 if ($i % $nbbyrow == 0) {
10246 $return .= '</tr>';
10247 }
10248 } else {
10249 $return .= '</div>';
10250 }
10251
10252 if ($nbmax && $nbphoto >= $nbmax) {
10253 break;
10254 }
10255 }
10256 }
10257
10258 if ($nbbyrow > 0) {
10259 while ($i % $nbbyrow) {
10260 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10261 $i++;
10262 }
10263 if ($nbphoto) {
10264 $return .= '</table>';
10265 }
10266 }
10267 }
10268 }
10269
10270 if (count($filearray)) {
10271 if ($sortfield && $sortorder) {
10272 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10273 }
10274
10275 foreach ($filearray as $key => $val) {
10276 $photo = '';
10277 $file = $val['name'];
10278
10279 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10280 if (image_format_supported($file) >= 0) {
10281 $nbphoto++;
10282 $photo = $file;
10283 $viewfilename = $file;
10284
10285 if ($size == 1 || $size == 'small') { // Format vignette
10286 // Find name of thumb file
10287 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10288 if (!dol_is_file($dirthumb.$photo_vignette)) {
10289 // The thumb does not exists, so we will use the original file
10290 $dirthumb = $dir;
10291 $pdirthumb = $pdir;
10292 $photo_vignette = basename($file);
10293 }
10294
10295 // Get filesize of original file
10296 $imgarray = dol_getImageSize($dir.$photo);
10297
10298 if ($nbbyrow > 0) {
10299 if ($nbphoto == 1) {
10300 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10301 }
10302
10303 if ($nbphoto % $nbbyrow == 1) {
10304 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10305 }
10306 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10307 } elseif ($nbbyrow < 0) {
10308 $return .= '<div class="inline-block">'."\n";
10309 }
10310
10311 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10312 if (empty($nolink)) {
10313 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10314 if ($urladvanced) {
10315 $return .= '<a href="'.$urladvanced.'">';
10316 } else {
10317 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10318 }
10319 }
10320
10321 // Show image (width height=$maxHeight)
10322 // If thumb file available and image source is too large, we use the thumb, otherwise we use the original photo
10323 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10324 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10325 if ($overwritetitle) {
10326 if (is_numeric($overwritetitle)) {
10327 $alt = '';
10328 } else {
10329 $alt = $overwritetitle;
10330 }
10331 }
10332 if (empty($cache) && !empty($val['label'])) {
10333 // label is md5 of file
10334 // use it in url to say we want to cache this version of the file
10335 $cache = $val['label'];
10336 }
10337 if ($usesharelink) {
10338 if (array_key_exists('share', $val) && $val['share']) {
10339 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10340 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10341 $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).'">';
10342 } else {
10343 $return .= '<!-- Show original file -->';
10344 $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).'">';
10345 }
10346 } else {
10347 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10348 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10349 }
10350 } else {
10351 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10352 $return .= '<!-- Show thumb -->';
10353 $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).'">';
10354 } else {
10355 $return .= '<!-- Show original file -->';
10356 $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).'">';
10357 }
10358 }
10359
10360 if (empty($nolink)) {
10361 $return .= '</a>';
10362 }
10363
10364 if ($showfilename) {
10365 $return .= '<br>'.$viewfilename;
10366 }
10367 if ($showaction) {
10368 $return .= '<br>';
10369 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10370 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10371 $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>';
10372 }
10373 // Special case for product
10374 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10375 // Link to resize
10376 $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; ';
10377
10378 // Link to delete
10379 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10380 $return .= img_delete().'</a>';
10381 }
10382 }
10383 $return .= "\n";
10384
10385 if ($nbbyrow > 0) {
10386 $return .= '</td>';
10387 if (($nbphoto % $nbbyrow) == 0) {
10388 $return .= '</tr>';
10389 }
10390 } elseif ($nbbyrow < 0) {
10391 $return .= '</div>'."\n";
10392 }
10393 }
10394
10395 if (empty($size)) { // Format origine
10396 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10397
10398 if ($showfilename) {
10399 $return .= '<br>'.$viewfilename;
10400 }
10401 if ($showaction) {
10402 // Special case for product
10403 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10404 // Link to resize
10405 $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; ';
10406
10407 // Link to delete
10408 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10409 $return .= img_delete().'</a>';
10410 }
10411 }
10412 }
10413
10414 // On continue ou on arrete de boucler ?
10415 if ($nbmax && $nbphoto >= $nbmax) {
10416 break;
10417 }
10418 }
10419 }
10420
10421 if ($size == 1 || $size == 'small') {
10422 if ($nbbyrow > 0) {
10423 // Ferme tableau
10424 while ($nbphoto % $nbbyrow) {
10425 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10426 $nbphoto++;
10427 }
10428
10429 if ($nbphoto) {
10430 $return .= '</table>';
10431 }
10432 }
10433 }
10434 }
10435
10436 $this->nbphoto = $nbphoto;
10437
10438 return $return;
10439 }
10440
10441
10448 protected function isArray($info)
10449 {
10450 if (is_array($info)) {
10451 if (isset($info['type']) && $info['type'] == 'array') {
10452 return true;
10453 } else {
10454 return false;
10455 }
10456 }
10457 return false;
10458 }
10459
10466 public function isDate($info)
10467 {
10468 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10469 return true;
10470 }
10471 return false;
10472 }
10473
10480 public function isDuration($info)
10481 {
10482 if (is_array($info)) {
10483 if (isset($info['type']) && ($info['type'] == 'duration')) {
10484 return true;
10485 } else {
10486 return false;
10487 }
10488 } else {
10489 return false;
10490 }
10491 }
10492
10499 public function isInt($info)
10500 {
10501 if (is_array($info)) {
10502 if (isset($info['type']) && (preg_match('/(^int|int$)/i', $info['type']))) {
10503 return true;
10504 } else {
10505 return false;
10506 }
10507 } else {
10508 return false;
10509 }
10510 }
10511
10518 public function isFloat($info)
10519 {
10520 if (is_array($info)) {
10521 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10522 return true;
10523 } else {
10524 return false;
10525 }
10526 }
10527 return false;
10528 }
10529
10536 public function isText($info)
10537 {
10538 if (is_array($info)) {
10539 if (isset($info['type']) && $info['type'] == 'text') {
10540 return true;
10541 } else {
10542 return false;
10543 }
10544 }
10545 return false;
10546 }
10547
10554 protected function canBeNull($info)
10555 {
10556 if (is_array($info)) {
10557 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10558 return true;
10559 } else {
10560 return false;
10561 }
10562 }
10563 return true;
10564 }
10565
10572 protected function isForcedToNullIfZero($info)
10573 {
10574 if (is_array($info)) {
10575 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10576 return true;
10577 } else {
10578 return false;
10579 }
10580 }
10581 return false;
10582 }
10583
10590 protected function isIndex($info)
10591 {
10592 if (is_array($info)) {
10593 if (array_key_exists('index', $info) && $info['index'] == true) {
10594 return true;
10595 } else {
10596 return false;
10597 }
10598 }
10599 return false;
10600 }
10601
10602
10611 protected function setSaveQuery()
10612 {
10613 global $conf;
10614
10615 $queryarray = array();
10616 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10617 // Depending on field type ('datetime', ...)
10618 if ($this->isDate($info)) {
10619 if (empty($this->{$field})) {
10620 $queryarray[$field] = null;
10621 } else {
10622 $queryarray[$field] = $this->db->idate($this->{$field});
10623 }
10624 } elseif ($this->isDuration($info)) {
10625 // $this->{$field} may be null, '', 0, '0', 123, '123'
10626 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10627 if (!isset($this->{$field})) {
10628 if (!empty($info['default'])) {
10629 $queryarray[$field] = $info['default'];
10630 } else {
10631 $queryarray[$field] = 0;
10632 }
10633 } else {
10634 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10635 }
10636 } else {
10637 $queryarray[$field] = null;
10638 }
10639 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10640 if ($field == 'entity' && is_null($this->{$field})) {
10641 $queryarray[$field] = ((int) $conf->entity);
10642 } else {
10643 // $this->{$field} may be null, '', 0, '0', 123, '123'
10644 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10645 if (!isset($this->{$field})) {
10646 $queryarray[$field] = 0;
10647 } elseif ($this->isInt($info)) {
10648 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10649 } elseif ($this->isFloat($info)) {
10650 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10651 }
10652 } else {
10653 $queryarray[$field] = null;
10654 }
10655 }
10656 } else {
10657 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10658 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10659 $queryarray[$field] = $this->{$field};
10660 }
10661
10662 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10663 unset($queryarray[$field]);
10664 }
10665 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10666 $queryarray[$field] = null; // May force 0 to null
10667 }
10668 }
10669
10670 return $queryarray;
10671 }
10672
10679 public function setVarsFromFetchObj(&$obj)
10680 {
10681 global $db;
10682
10683 foreach ($this->fields as $field => $info) {
10684 if ($this->isDate($info)) {
10685 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') {
10686 $this->$field = '';
10687 } else {
10688 $this->$field = $db->jdate($obj->$field);
10689 }
10690 } elseif ($this->isInt($info)) {
10691 if ($field == 'rowid') {
10692 $this->id = (int) $obj->$field;
10693 } else {
10694 if ($this->isForcedToNullIfZero($info)) {
10695 if (empty($obj->$field)) {
10696 $this->$field = null;
10697 } else {
10698 $this->$field = (int) $obj->$field;
10699 }
10700 } else {
10701 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10702 $this->$field = (int) $obj->$field;
10703 } else {
10704 $this->$field = null;
10705 }
10706 }
10707 }
10708 } elseif ($this->isFloat($info)) {
10709 if ($this->isForcedToNullIfZero($info)) {
10710 if (empty($obj->$field)) {
10711 $this->$field = null;
10712 } else {
10713 $this->$field = (float) $obj->$field;
10714 }
10715 } else {
10716 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10717 $this->$field = (float) $obj->$field;
10718 } else {
10719 $this->$field = null;
10720 }
10721 }
10722 } else {
10723 $this->$field = isset($obj->$field) ? $obj->$field : null;
10724 }
10725 }
10726
10727 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10728 if (!isset($this->fields['ref']) && isset($this->id)) {
10729 $this->ref = (string) $this->id;
10730 }
10731 }
10732
10737 public function emtpyObjectVars()
10738 {
10739 foreach ($this->fields as $field => $arr) {
10740 $this->$field = null;
10741 }
10742 }
10743
10751 public function getFieldList($alias = '', $excludefields = array())
10752 {
10753 $keys = array_keys($this->fields);
10754 if (!empty($alias)) {
10755 $keys_with_alias = array();
10756 foreach ($keys as $fieldname) {
10757 if (!empty($excludefields)) {
10758 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10759 continue;
10760 }
10761 }
10762 $keys_with_alias[] = $this->db->sanitize($alias . '.' . $fieldname);
10763 }
10764 return implode(', ', $keys_with_alias);
10765 } else {
10766 return implode(', ', $keys);
10767 }
10768 }
10769
10777 protected function quote($value, $fieldsentry)
10778 {
10779 if (is_null($value)) {
10780 return 'NULL';
10781 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10782 return price2num((string) $value);
10783 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10784 return (int) $value;
10785 } elseif ($fieldsentry['type'] == 'boolean') {
10786 if ($value) {
10787 return 'true';
10788 } else {
10789 return 'false';
10790 }
10791 } else {
10792 return "'".$this->db->escape($value)."'";
10793 }
10794 }
10795
10796
10804 public function createCommon(User $user, $notrigger = 0)
10805 {
10806 global $conf;
10807 //global $langs; // Should be able to work with $langs loaded
10808
10809 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10810
10811 $error = 0;
10812
10813 $now = dol_now();
10814
10815 $fieldvalues = $this->setSaveQuery();
10816
10817 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10818
10819 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10820 $fieldvalues['date_creation'] = $this->db->idate($now);
10821 $this->date_creation = $this->db->idate($now);
10822 }
10823 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10824 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10825 $fieldvalues['fk_user_creat'] = $user->id;
10826 $this->fk_user_creat = $user->id;
10827 }
10828 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10829 $fieldvalues['user_creation_id'] = $user->id;
10830 $this->user_creation_id = $user->id;
10831 }
10832 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10833 // @phan-suppress-next-line PhanUndeclaredProperty
10834 $tmparray = dol_hash($this->pass, '0', 0, 1);
10835 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10836 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10837 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10838 }
10839 }
10840 if (array_key_exists('ref', $fieldvalues)) {
10841 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10842 }
10843 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
10844 $fieldvalues['tms'] = $this->db->idate($now);
10845 }
10846
10847 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10848
10849 $keys = array();
10850 $values = array(); // Array to store string forged for SQL syntax
10851 foreach ($fieldvalues as $k => $v) {
10852 $keys[$k] = $k;
10853 $value = $this->fields[$k];
10854 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10855 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10856 }
10857
10858 // Clean and check mandatory
10859 foreach ($keys as $key) {
10860 if (!isset($this->fields[$key])) {
10861 continue;
10862 }
10863 $key_fields = $this->fields[$key];
10864
10865 // If field is an implicit foreign key field (so type = 'integer:...')
10866 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10867 $values[$key] = '';
10868 }
10869 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10870 $values[$key] = '';
10871 }
10872
10873 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10874 $error++;
10875 global $langs;
10876 if (empty($langs)) {
10877 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10878 $langs = new Translate('', $conf);
10879 $langs->setDefaultLang();
10880 $langs->load("errors");
10881 }
10882 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10883 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10884 }
10885
10886 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10887 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && array_key_exists('default', $key_fields) && !is_null($key_fields['default'])) {
10888 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10889 }
10890
10891 // If field is an implicit foreign key field (so type = 'integer:...')
10892 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10893 if (isset($key_fields['default'])) {
10894 $values[$key] = ((int) $key_fields['default']);
10895 } else {
10896 $values[$key] = 'null';
10897 }
10898 }
10899 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10900 $values[$key] = 'null';
10901 }
10902 }
10903
10904 if ($error) {
10905 return -1;
10906 }
10907
10908 $this->db->begin();
10909
10910 if (!$error) {
10911 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10912 $sql .= " (".implode(", ", $keys).')';
10913 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10914
10915 $res = $this->db->query($sql);
10916 if (!$res) {
10917 $error++;
10918 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10919 $this->errors[] = "ErrorRefAlreadyExists";
10920 } else {
10921 $this->errors[] = $this->db->lasterror();
10922 }
10923 }
10924 }
10925
10926 if (!$error) {
10927 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10928 }
10929
10930 // If we have a field ref with a default value of (PROV)
10931 if (!$error) {
10932 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10933 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)') {
10934 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10935 $resqlupdate = $this->db->query($sql);
10936
10937 if ($resqlupdate === false) {
10938 $error++;
10939 $this->errors[] = $this->db->lasterror();
10940 } else {
10941 $this->ref = '(PROV'.$this->id.')';
10942 }
10943 }
10944 }
10945
10946 // Create extrafields
10947 if (!$error) {
10948 $result = $this->insertExtraFields();
10949 if ($result < 0) {
10950 $error++;
10951 }
10952 }
10953
10954 // Create lines
10955 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10956 foreach ($this->lines as $line) {
10957 $keyforparent = $this->fk_element;
10958 $line->$keyforparent = $this->id;
10959
10960 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10961 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10962 if (!is_object($line)) {
10963 $line = (object) $line;
10964 }
10965
10966 $result = 0;
10967 if (method_exists($line, 'insert')) {
10968 $result = $line->insert($user, 1);
10969 } elseif (method_exists($line, 'create')) {
10970 $result = $line->create($user, 1);
10971 }
10972 if ($result < 0) {
10973 $this->error = $line->error;
10974 $this->db->rollback();
10975 return -1;
10976 }
10977 }
10978 }
10979
10980 // Triggers
10981 if (!$error && !$notrigger) {
10982 // Call triggers
10983 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10984 if ($result < 0) {
10985 $error++;
10986 }
10987 // End call triggers
10988 }
10989
10990 // Commit or rollback
10991 if ($error) {
10992 $this->db->rollback();
10993 return -1;
10994 } else {
10995 $this->db->commit();
10996 return $this->id;
10997 }
10998 }
10999
11000
11010 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
11011 {
11012 if (empty($id) && empty($ref) && empty($morewhere)) {
11013 return -1;
11014 }
11015
11016 $fieldlist = $this->getFieldList('t');
11017 if (empty($fieldlist)) {
11018 return 0;
11019 }
11020
11021 $sql = "SELECT ".$this->db->sanitize($fieldlist, 0, 0, 1);
11022 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
11023
11024 if (!empty($id)) {
11025 $sql .= ' WHERE t.rowid = '.((int) $id);
11026 } elseif (!empty($ref)) {
11027 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
11028 } else {
11029 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
11030 }
11031 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
11032 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
11033 }
11034 if ($morewhere) {
11035 $sql .= $morewhere;
11036 }
11037 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
11038
11039 $res = $this->db->query($sql);
11040 if ($res) {
11041 $obj = $this->db->fetch_object($res);
11042 if ($obj) {
11043 $this->setVarsFromFetchObj($obj);
11044
11045 // Retrieve all extrafield
11046 // fetch optionals attributes and labels
11047 if (empty($noextrafields)) {
11048 $result = $this->fetch_optionals();
11049 if ($result < 0) {
11050 $this->error = $this->db->lasterror();
11051 $this->errors[] = $this->error;
11052 return -4;
11053 }
11054 }
11055
11056 return $this->id;
11057 } else {
11058 return 0;
11059 }
11060 } else {
11061 $this->error = $this->db->lasterror();
11062 $this->errors[] = $this->error;
11063 return -1;
11064 }
11065 }
11066
11074 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
11075 {
11076 $objectlineclassname = get_class($this).'Line';
11077 if (!class_exists($objectlineclassname)) {
11078 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
11079 return -1;
11080 }
11081
11082 $objectline = new $objectlineclassname($this->db);
11083 '@phan-var-force CommonObjectLine $objectline';
11084
11085 $sql = "SELECT ".$objectline->getFieldList('l');
11086 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
11087 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
11088 if ($morewhere) {
11089 $sql .= $morewhere;
11090 }
11091 if (isset($objectline->fields['position'])) {
11092 $sql .= $this->db->order('position', 'ASC');
11093 }
11094
11095 $resql = $this->db->query($sql);
11096 if ($resql) {
11097 $num_rows = $this->db->num_rows($resql);
11098 $i = 0;
11099 $this->lines = array();
11100 while ($i < $num_rows) {
11101 $obj = $this->db->fetch_object($resql);
11102 if ($obj) {
11103 $newline = new $objectlineclassname($this->db);
11104 '@phan-var-force CommonObjectLine $newline';
11105 $newline->setVarsFromFetchObj($obj);
11106
11107 // Load also extrafields for the line
11108 if (empty($noextrafields)) {
11109 $newline->fetch_optionals();
11110 }
11111
11112 $this->lines[] = $newline;
11113 }
11114 $i++;
11115 }
11116
11117 return 1;
11118 } else {
11119 $this->error = $this->db->lasterror();
11120 $this->errors[] = $this->error;
11121 return -1;
11122 }
11123 }
11124
11132 public function updateCommon(User $user, $notrigger = 0)
11133 {
11134 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11135
11136 $error = 0;
11137
11138 $now = dol_now();
11139
11140 // $this->oldcopy should have been set by the caller of update
11141 //if (empty($this->oldcopy)) {
11142 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11143 // $this->oldcopy = dol_clone($this, 2);
11144 //}
11145
11146 $fieldvalues = $this->setSaveQuery();
11147
11148 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11149
11150 if (array_key_exists('date_modification', $fieldvalues)) {
11151 $fieldvalues['date_modification'] = $this->db->idate($now);
11152 }
11153 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
11154 $fieldvalues['tms'] = $this->db->idate($now);
11155 }
11156 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11157 $fieldvalues['fk_user_modif'] = $user->id;
11158 }
11159 if (array_key_exists('user_modification_id', $fieldvalues)) {
11160 $fieldvalues['user_modification_id'] = $user->id;
11161 }
11162 // @phan-suppress-next-line PhanUndeclaredProperty
11163 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11164 // @phan-suppress-next-line PhanUndeclaredProperty
11165 $tmparray = dol_hash($this->pass, '0', 0, 1);
11166 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11167 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11168 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11169 }
11170 }
11171 if (array_key_exists('ref', $fieldvalues)) {
11172 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11173 }
11174
11175 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11176
11177 // Add quotes and escape on fields with type string
11178 $keys = array();
11179 $values = array();
11180 $tmp = array();
11181 foreach ($fieldvalues as $k => $v) {
11182 $keys[$k] = $k;
11183 $value = $this->fields[$k];
11184 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11185 $values[$k] = $this->quote($v, $value);
11186 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11187 // Clean values for text with selectbox
11188 $v = preg_replace('/\s/', ',', $v);
11189 $v = preg_replace('/,+/', ',', $v);
11190 }
11191 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11192 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
11193 }
11194
11195 // Clean and check mandatory fields
11196 foreach ($keys as $key) {
11197 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11198 $values[$key] = ''; // This is an implicit foreign key field
11199 }
11200 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11201 $values[$key] = ''; // This is an explicit foreign key field
11202 }
11203
11204 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11205 /*
11206 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11207 {
11208 $error++;
11209 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11210 }*/
11211 }
11212
11213 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
11214
11215 $this->db->begin();
11216
11217 if (!$error) {
11218 $res = $this->db->query($sql);
11219 if (!$res) {
11220 $error++;
11221 $this->errors[] = $this->db->lasterror();
11222 }
11223 }
11224
11225 // Update extrafield
11226 if (!$error) {
11227 $result = $this->insertExtraFields(); // This update extrafields
11228 if ($result < 0) {
11229 $error++;
11230 }
11231 }
11232
11233 // Triggers
11234 if (!$error && !$notrigger) {
11235 // Call triggers
11236 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11237 if ($result < 0) {
11238 $error++;
11239 } //Do also here what you must do to rollback action if trigger fail
11240 // End call triggers
11241 }
11242
11243 // Commit or rollback
11244 if ($error) {
11245 $this->db->rollback();
11246 return -1;
11247 } else {
11248 $this->db->commit();
11249 return $this->id;
11250 }
11251 }
11252
11261 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11262 {
11263 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11264
11265 $error = 0;
11266
11267 $this->db->begin();
11268
11269 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11270 foreach ($this->childtables as $table) {
11271 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($table)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11272 $resql = $this->db->query($sql);
11273 if (!$resql) {
11274 $this->error = $this->db->lasterror();
11275 $this->errors[] = $this->error;
11276 $this->db->rollback();
11277 return -1;
11278 }
11279 }
11280 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11281 $objectisused = $this->isObjectUsed($this->id);
11282 if (!empty($objectisused)) {
11283 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11284 $this->error = 'ErrorRecordHasChildren';
11285 $this->errors[] = $this->error;
11286 $this->db->rollback();
11287 return 0;
11288 }
11289 }
11290
11291 // Delete cascade first
11292 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11293 foreach ($this->childtablesoncascade as $tabletodelete) {
11294 $deleteFromObject = explode(':', $tabletodelete, 4);
11295 if (count($deleteFromObject) >= 2) {
11296 $className = str_replace('@', '', $deleteFromObject[0]);
11297 $filePath = $deleteFromObject[1];
11298 $columnName = $deleteFromObject[2];
11299 $filter = '';
11300 if (!empty($deleteFromObject[3])) {
11301 $filter = $deleteFromObject[3];
11302 }
11303
11304 if (dol_include_once($filePath)) {
11305 $childObject = new $className($this->db);
11306 if (method_exists($childObject, 'deleteByParentField')) {
11307 '@phan-var-force CommonObject $childObject';
11308 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11309 if ($result < 0) {
11310 $error++;
11311 $this->errors[] = $childObject->error;
11312 break;
11313 }
11314 } else {
11315 $error++;
11316 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11317 break;
11318 }
11319 } else {
11320 $error++;
11321 $this->errors[] = 'Cannot include child class file '.$filePath;
11322 break;
11323 }
11324 } else {
11325 // Delete record in child table
11326 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($tabletodelete)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11327
11328 $resql = $this->db->query($sql);
11329 if (!$resql) {
11330 $error++;
11331 $this->error = $this->db->lasterror();
11332 $this->errors[] = $this->error;
11333 break;
11334 }
11335 }
11336 }
11337 }
11338
11339 if (!$error) {
11340 if (!$notrigger) {
11341 // Call triggers
11342 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11343 if ($result < 0) {
11344 $error++;
11345 } // Do also here what you must do to rollback action if trigger fail
11346 // End call triggers
11347 }
11348 }
11349
11350 // Delete llx_ecm_files
11351 if (!$error) {
11352 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11353 if (!$res) {
11354 $error++;
11355 }
11356 }
11357
11358 if (!$error) {
11359 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11360 // For remove dir
11361 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11362 if (dol_is_dir($dir)) {
11363 if (!dol_delete_dir_recursive($dir)) {
11364 $this->errors[] = 'ErrorFailToDeleteDir';
11365 }
11366 }
11367 }
11368
11369 // Delete linked object
11370 $res = $this->deleteObjectLinked();
11371 if ($res < 0) {
11372 $error++;
11373 }
11374
11375 if (!$error && !empty($this->isextrafieldmanaged)) {
11376 $result = $this->deleteExtraFields();
11377 if ($result < 0) {
11378 $error++;
11379 }
11380 }
11381
11382 if (!$error) {
11383 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11384
11385 $resql = $this->db->query($sql);
11386 if (!$resql) {
11387 $error++;
11388 $this->errors[] = $this->db->lasterror();
11389 }
11390 }
11391
11392 // Commit or rollback
11393 if ($error) {
11394 $this->db->rollback();
11395 return -1;
11396 } else {
11397 $this->db->commit();
11398 return 1;
11399 }
11400 }
11401
11413 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11414 {
11415 global $user;
11416
11417 $error = 0;
11418 $deleted = 0;
11419
11420 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11421
11422 if (!empty($parentId) && !empty($parentField)) {
11423 if (empty($this->table_element)) {
11424 $this->error = 'Property table_element for object is not defined';
11425 $this->errors[] = $this->error;
11426 $error++;
11427 return -1;
11428 }
11429 if (!method_exists($this, 'fetch')) {
11430 $this->error = 'Method fetch for object is not defined';
11431 $this->errors[] = $this->error;
11432 $error++;
11433 return -1;
11434 }
11435 if (!method_exists($this, 'delete')) {
11436 $this->error = 'Method delete for object is not defined';
11437 $this->errors[] = $this->error;
11438 $error++;
11439 return -1;
11440 }
11441
11442 $this->db->begin();
11443
11444 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11445 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11446
11447 // Manage filter
11448 $errormessage = '';
11449 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11450 if ($errormessage) {
11451 $this->errors[] = $errormessage;
11452 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11453 return -1;
11454 }
11455
11456 $resql = $this->db->query($sql);
11457 if (!$resql) {
11458 $this->errors[] = $this->db->lasterror();
11459 $error++;
11460 } else {
11461 while ($obj = $this->db->fetch_object($resql)) {
11462 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11463 if ($result < 0) {
11464 $error++;
11465 $this->errors[] = $this->error;
11466 } else {
11467 $result = $this->delete($user); // @phpstan-ignore-line
11468 if ($result < 0) {
11469 $error++;
11470 $this->errors[] = $this->error;
11471 } else {
11472 $deleted++;
11473 }
11474 }
11475 }
11476 }
11477
11478 if (empty($error)) {
11479 $this->db->commit();
11480 return $deleted;
11481 } else {
11482 $this->error = implode(', ', $this->errors);
11483 $this->db->rollback();
11484 return $error * -1;
11485 }
11486 }
11487
11488 return $deleted;
11489 }
11490
11499 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11500 {
11501 $error = 0;
11502
11503 $tmpforobjectclass = get_class($this);
11504 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11505
11506 $this->db->begin();
11507
11508 // Call trigger
11509 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11510 if ($result < 0) {
11511 $error++;
11512 }
11513 // End call triggers
11514
11515 if (empty($error)) {
11516 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11517 $sql .= " WHERE rowid = ".((int) $idline);
11518
11519 $resql = $this->db->query($sql);
11520 if (!$resql) {
11521 $this->error = "Error ".$this->db->lasterror();
11522 $error++;
11523 }
11524 }
11525
11526 if (empty($error)) {
11527 // Remove extrafields
11528 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11529 '@phan-var-force CommonObjectLine $tmpobjectline';
11530 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11531 $tmpobjectline->id = $idline;
11532 $result = $tmpobjectline->deleteExtraFields();
11533 if ($result < 0) {
11534 $error++;
11535 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11536 }
11537 }
11538 }
11539
11540 if (empty($error)) {
11541 $this->db->commit();
11542 return 1;
11543 } else {
11544 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11545 $this->db->rollback();
11546 return -1;
11547 }
11548 }
11549
11550
11561 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11562 {
11563 $error = 0;
11564
11565 $this->db->begin();
11566
11567 $statusfield = 'status';
11568 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11569 $statusfield = 'fk_statut';
11570 }
11571
11572 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11573 $sql .= " SET ".$this->db->sanitize($statusfield)." = ".((int) $status);
11574 $sql .= " WHERE rowid = ".((int) $this->id);
11575
11576 if ($this->db->query($sql)) {
11577 if (!$error) {
11578 $this->oldcopy = clone $this;
11579 }
11580
11581 if (!$error && !$notrigger) {
11582 // Call trigger
11583 $result = $this->call_trigger($triggercode, $user);
11584 if ($result < 0) {
11585 $error++;
11586 }
11587 }
11588
11589 if (!$error) {
11590 $this->status = $status;
11591 if (property_exists($this, 'statut')) { // For backward compatibility
11592 $this->statut = $status;
11593 }
11594 $this->db->commit();
11595 return 1;
11596 } else {
11597 $this->db->rollback();
11598 return -1;
11599 }
11600 } else {
11601 $this->error = $this->db->error();
11602 $this->db->rollback();
11603 return -1;
11604 }
11605 }
11606
11613 public function initAsSpecimenCommon()
11614 {
11615 global $user;
11616
11617 $this->id = 0;
11618 $this->specimen = 1;
11619 $fields = array(
11620 'label' => 'This is label',
11621 'ref' => 'ABCD1234',
11622 'description' => 'This is a description',
11623 'qty' => 123.12,
11624 'note_public' => 'Public note',
11625 'note_private' => 'Private note',
11626 'date_creation' => (dol_now() - 3600 * 48),
11627 'date_modification' => (dol_now() - 3600 * 24),
11628 'fk_user_creat' => $user->id,
11629 'fk_user_modif' => $user->id,
11630 'date' => dol_now(),
11631 );
11632 foreach ($fields as $key => $value) {
11633 if (array_key_exists($key, $this->fields)) {
11634 $this->{$key} = $value; // @phpstan-ignore-line
11635 }
11636 }
11637
11638 // Force values to default values when known
11639 if (property_exists($this, 'fields')) {
11640 foreach ($this->fields as $key => $value) {
11641 // If fields are already set, do nothing
11642 if (array_key_exists($key, $fields)) {
11643 continue;
11644 }
11645
11646 if (!empty($value['default'])) {
11647 $this->$key = $value['default'];
11648 }
11649 }
11650 }
11651
11652 return 1;
11653 }
11654
11655
11656 /* Part for comments */
11657
11663 public function fetchComments()
11664 {
11665 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11666
11667 $comment = new Comment($this->db);
11668 $result = $comment->fetchAllFor($this->element, $this->id);
11669 if ($result < 0) {
11670 $this->setErrorsFromObject($comment);
11671 return -1;
11672 } else {
11673 $this->comments = $comment->comments;
11674 }
11675 return count($this->comments);
11676 }
11677
11683 public function getNbComments()
11684 {
11685 return count($this->comments);
11686 }
11687
11694 public function trimParameters($parameters)
11695 {
11696 if (!is_array($parameters)) {
11697 return;
11698 }
11699 foreach ($parameters as $parameter) {
11700 if (isset($this->$parameter)) {
11701 $this->$parameter = trim($this->$parameter);
11702 }
11703 }
11704 }
11705
11706 /* Part for categories/tags */
11707
11718 public function getCategoriesCommon($type_categ)
11719 {
11720 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11721
11722 // Get current categories
11723 $c = new Categorie($this->db);
11724 $existing = $c->containing($this->id, $type_categ, 'id');
11725
11726 return $existing;
11727 }
11728
11741 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11742 {
11743 // Handle single category
11744 if (!is_array($categories)) {
11745 $categories = array($categories);
11746 }
11747
11748 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11749
11750 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11751
11752 if (empty($type_categ)) {
11753 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11754 return -1;
11755 }
11756
11757 // Get current categories
11758 $c = new Categorie($this->db);
11759 $existing = $c->containing($this->id, $type_categ, 'id');
11760 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11761 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11762 if (!is_array($existing)) {
11763 $existing = array();
11764 }
11765 if ($remove_existing) {
11766 // Diff
11767 $to_del = array_diff($existing, $categories);
11768 $to_add = array_diff($categories, $existing);
11769 } else {
11770 $to_del = array(); // Nothing to delete
11771 $to_add = array_diff($categories, $existing);
11772 }
11773
11774 $error = 0;
11775 $ok = 0;
11776
11777 // Process
11778 foreach ($to_del as $del) {
11779 if ($c->fetch($del) > 0) {
11780 $result = $c->del_type($this, $type_categ);
11781 if ($result < 0) {
11782 $error++;
11783 $this->error = $c->error;
11784 $this->errors = $c->errors;
11785 break;
11786 } else {
11787 $ok += $result;
11788 }
11789 }
11790 }
11791 foreach ($to_add as $add) {
11792 if ($c->fetch($add) > 0) {
11793 $result = $c->add_type($this, $type_categ);
11794 if ($result < 0) {
11795 $error++;
11796 $this->error = $c->error;
11797 $this->errors = $c->errors;
11798 break;
11799 } else {
11800 $ok += $result;
11801 }
11802 }
11803 }
11804
11805 return $error ? (-1 * $error) : $ok;
11806 }
11807
11816 public function cloneCategories($fromId, $toId, $type = '')
11817 {
11818 $this->db->begin();
11819
11820 if (empty($type)) {
11821 $type = $this->table_element;
11822 }
11823
11824 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11825 $categorystatic = new Categorie($this->db);
11826
11827 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11828 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11829
11830 $sql = "INSERT INTO ".$this->db->sanitize($tablename)." (fk_categorie, ".$this->db->sanitize($fkname).")";
11831 $sql .= " SELECT fk_categorie, ".((int) $toId)." FROM ".$this->db->sanitize($tablename);
11832 $sql .= " WHERE ".$this->db->sanitize($fkname)." = ".((int) $fromId);
11833
11834 if (!$this->db->query($sql)) {
11835 $this->error = $this->db->lasterror();
11836 $this->db->rollback();
11837 return -1;
11838 }
11839
11840 $this->db->commit();
11841 return 1;
11842 }
11843
11850 public function deleteEcmFiles($mode = 0)
11851 {
11852 global $conf;
11853
11854 $this->db->begin();
11855
11856 // Delete in database with mode 0
11857 if ($mode == 0) {
11858 switch ($this->element) {
11859 case 'propal':
11860 $element = 'propale';
11861 break;
11862 case 'product':
11863 $element = 'produit';
11864 break;
11865 case 'order_supplier':
11866 $element = 'fournisseur/commande';
11867 break;
11868 case 'invoice_supplier':
11869 // Special cases that need to use get_exdir to get real dir of object
11870 // In future, all object should use this to define path of documents.
11871 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11872 break;
11873 case 'shipping':
11874 $element = 'expedition/sending';
11875 break;
11876 case 'task':
11877 case 'project_task':
11878 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11879
11880 $project_result = $this->fetchProject();
11881 if ($project_result >= 0) {
11882 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11883 }
11884 // no break
11885 case 'contrat':
11886 $element = 'contract';
11887 break;
11888 default:
11889 $element = $this->element;
11890 }
11891 '@phan-var-force string $element';
11892
11893 // Delete ecm_files_extrafields with mode 0 (using name)
11894 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11895 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11896 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11897 $sql .= ")";
11898
11899 if (!$this->db->query($sql)) {
11900 $this->error = $this->db->lasterror();
11901 $this->db->rollback();
11902 return false;
11903 }
11904
11905 // Delete ecm_files with mode 0 (using name)
11906 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11907 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11908 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11909
11910 if (!$this->db->query($sql)) {
11911 $this->error = $this->db->lasterror();
11912 $this->db->rollback();
11913 return false;
11914 }
11915 }
11916
11917 // Delete in database with mode 1
11918 if ($mode == 1) {
11919 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11920 $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).")";
11921 $resql = $this->db->query($sql);
11922 if (!$resql) {
11923 $this->error = $this->db->lasterror();
11924 $this->db->rollback();
11925 return false;
11926 }
11927
11928 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11929 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11930 $resql = $this->db->query($sql);
11931 if (!$resql) {
11932 $this->error = $this->db->lasterror();
11933 $this->db->rollback();
11934 return false;
11935 }
11936 }
11937
11938 $this->db->commit();
11939 return true;
11940 }
11941
11950 public function checkActiveProductInLines($status = 'onsale')
11951 {
11952 global $langs;
11953
11954 if (isModEnabled('product') || isModEnabled('service')) {
11955 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
11956
11957 $ret = true;
11958 $tmpproduct = new Product($this->db);
11959 foreach ($this->lines as $line) {
11960 if ($line->fk_product > 0) {
11961 $tmpproduct->fetch($line->fk_product);
11962 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
11963 if (!$tmpproduct->$statustotest) {
11964 $langs->load('products');
11965 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
11966 $ret = false;
11967 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
11968 break;
11969 }
11970 }
11971 }
11972 if (!$ret) {
11973 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
11974 }
11975 return $ret;
11976 } else {
11977 return true;
11978 }
11979 }
11980
11987 public function setErrorWithoutLog($message)
11988 {
11989 $this->error = $message;
11990 $this->errors[] = $message;
11991 }
11992
12000 public function setErrorWithLog($message, $loglevel = LOG_ERR)
12001 {
12002 global $dolibarr_main_document_root;
12003
12004 $this->setErrorWithoutLog($message);
12005 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
12006 $file = str_replace($dolibarr_main_document_root, '', $trace[0]['file'] ?? 'file unknown');
12007 $line = $trace[0]['line'] ?? 'line unknown';
12008 $syslogMessage = sprintf('%s:%s %s', $file, $line, $message);
12009 dol_syslog($syslogMessage, $loglevel);
12010 }
12011}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
global $dolibarr_main_url_root
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:476
$c
Definition line.php:334
$object ref
Definition info.php:90
Class to manage members of a foundation.
Class to manage categories.
Class to manage comment.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
getCategoriesCommon($type_categ)
Sets object to given categories.
indexFile($destfull, $update_main_doc_field)
Index a file into the ECM database.
getFormatedSupplierRef($objref)
Return supplier ref for screen output.
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteLineCommon(User $user, $idline, $notrigger=0)
Delete a line of object in database.
isEmpty()
isEmpty We consider CommonObject isEmpty if this->id is empty
clearFieldError($fieldKey)
clear validation message result for a field
static getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Count items linked to an object id in association table.
deleteEcmFiles($mode=0)
Delete related files of object in database.
getLastMainDocLink($modulepart, $initsharekey=0, $relativelink=0)
Return the link of last main doc file for direct public download.
liste_type_contact($source='internal', $order='position', $option=0, $activeonly=0, $code='')
Return array with list of possible values for type of contacts.
getTooltipContent($params)
getTooltipContent
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
swapContactStatus($rowid)
Update status of a contact linked to object.
getFieldError($fieldKey)
get field error message
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='')
Set status of an object.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
updateObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $f_user=null, $notrigger=0)
Update object linked of a current object.
setErrorWithoutLog($message)
Set the error message for the object without logging it.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
fetchObjectFrom($table, $field, $key, $element=null)
Load object from specific field.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
static deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Function used to remove all items linked to an object id in association table.
setFieldError($fieldKey, $msg='')
set validation error message a field
validateField($fields, $fieldKey, $fieldValue)
Return validation test result for a field.
getDataToShowPhoto($modulepart, $imagesize)
Function used to get the logos or photos of an object.
setMulticurrencyCode($code)
Change the multicurrency code.
emtpyObjectVars()
Sets all object fields to null.
fetch_projet()
Load the project with id $this->fk_project into this->project.
getIdContact($source, $code, $status=0)
Return id of contacts for a source and a contact code.
setExtraField($key, $value)
Convenience method for setting the value of an extrafield without actually updating it in the databas...
setDocModel($user, $modelpdf)
Set last model used by doc generator.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
getExtraField($key)
Convenience method for retrieving the value of an extrafield without actually fetching it from the da...
updateExtraField($key, $trigger=null, $userused=null)
Update 1 extra field value for the current object.
setPaymentTerms($id, $deposit_percent=null)
Change the payments terms.
isFloat($info)
Function test if type is float.
setBankAccount($fk_account, $notrigger=0, $userused=null)
Change the bank account.
setExtraParameters()
Set extra parameters.
delete_resource($rowid, $element='', $notrigger=0)
Delete a link to resource line.
setErrorsFromObject($object)
setErrorsFromObject
setShippingMethod($shipping_method_id, $notrigger=0, $userused=null)
Change the shipping method.
update_note_public($note)
Update public note (kept for backward compatibility)
getSpecialCode($lineid)
Get special code of a line.
clearObjectLinkedCache()
Clear the cache saying that all linked object were already loaded.
update_ref_ext($ref_ext)
Update external ref of element.
fetchOneLike($ref)
Looks for an object with ref matching the wildcard provided It does only work when $this->table_ref_f...
hasProductsOrServices($predefined=-1)
Function to say how many lines object contains.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
updateRangOfLine($rowid, $rang)
Update position of line (rang)
getDefaultCreateValueFor($fieldname, $alternatevalue=null, $type='alphanohtml')
Return the default value to use for a field when showing the create form of object.
add_element_resource($resource_id, $resource_type, $busy=0, $mandatory=0, $notrigger=0)
Add resources to the current object : add entry into llx_element_resources Need $this->element & $thi...
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
createCommon(User $user, $notrigger=0)
Create object in the database.
getTotalWeightVolume()
Return into unit=0, the calculated total of weight and volume of all lines * qty Calculate by adding ...
update_note($note, $suffix='', $notrigger=0)
Update note of element.
getFullAddress($withcountry=0, $sep="\n", $withregion=0, $extralangcode='')
Return full address of contact.
fetch_project()
Load the project with id $this->fk_project into this->project.
getFieldList($alias='', $excludefields=array())
Function to concat keys of fields.
getNbComments()
Return nb comments already posted.
setVarsFromFetchObj(&$obj)
Function to load data from a SQL pointer into properties of current object $this.
printObjectLines($action, $seller, $buyer, $selected=0, $dateSelector=0, $defaulttpldir='/core/tpl')
Return HTML table for object lines TODO Move this into an output class file (htmlline....
getChildrenOfLine($id, $includealltree=0)
Get children of line.
updateCommon(User $user, $notrigger=0)
Update object into database.
updateExtraLanguages($key, $trigger=null, $userused=null)
Update an extra language value for the current object.
deleteExtraFields()
Delete all extra fields values for the current object.
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
deprecatedProperties()
Provide list of deprecated properties and replacements.
setSaveQuery()
Function to return the array of data key-value from the ->fields and all the ->properties of an objec...
setValuesForExtraLanguages($onlykey='')
Fill array_options property of object by extrafields value (using for data sent by forms) Used for ex...
insertExtraLanguages($trigger='', $userused=null)
Add/Update all extra languages values for the current object.
setTransportMode($id)
Change the transport mode methods.
isArray($info)
Function test if type is array.
isInt($info)
Function test if type is integer.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
delete_contact($rowid, $notrigger=0)
Delete a link to contact line.
updateLineUp($rowid, $rang)
Update position of line up (rang)
fetch_user($userid)
Load the user with id $userid into this->user.
errorsToString()
Method to output saved errors.
getListContactId($source='external')
Return list of id of contacts of object.
setWarehouse($warehouse_id)
Change the warehouse.
setDeliveryAddress($id)
Define delivery address.
fetchBarCode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
getTotalDiscount()
Function that returns the total amount HT of discounts applied for all lines.
initAsSpecimenCommon()
Initialise object with example values Id must be 0 if object instance is a specimen.
printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected=0, $extrafields=null, $defaulttpldir='/core/tpl')
Return HTML content of a detail line TODO Move this into an output class file (htmlline....
copy_linked_contact($objFrom, $source='internal')
Copy contact from one element to current.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
getValueFrom($table, $id, $field)
Getter generic.
trimParameters($parameters)
Trim object parameters.
fetch_product()
Load the product with id $this->fk_product into this->product.
isIndex($info)
Function test if is indexed.
quote($value, $fieldsentry)
Add quote to field value if necessary.
formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir='/core/tpl')
Show add free and predefined products/services form.
fetchComments()
Load comments linked with current task.
line_down($rowid, $fk_parent_line=true)
Update a line to have a higher rank.
setValueFrom($field, $value, $table='', $id=null, $format='', $id_field='', $fuser=null, $trigkey='', $fk_user_field='fk_user_modif')
Setter generic.
fetch_contact($contactid=null)
Load object contact with id=$this->contact_id into $this->contact.
delThumbs($file)
Delete thumbs.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $overwritetitle=0, $usesharelink=0, $cache='', $addphotorefcss='photoref')
Show photos of an object (nbmax maximum), into several columns.
fetchLinesCommon($morewhere='', $noextrafields=0)
Load object in memory from the database.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
getRangOfLine($rowid)
Get position of line (rang)
showInputField($val, $key, $value, $moreparam='', $keysuffix='', $keyprefix='', $morecss=0, $nonewbutton=0)
Return HTML string to put an input field into a page Code very similar with showInputField of extra f...
update_contact($rowid, $statut, $type_contact_id=0, $fk_socpeople=0)
Update a link to contact line.
getElementType()
Return an element type string formatted like element_element target_type and source_type.
load_previous_next_ref($filter, $fieldid, $nodbprefix=0)
Load properties id_previous and id_next by comparing $fieldid with $this->ref.
setCategoriesCommon($categories, $type_categ='', $remove_existing=true)
Sets object to given categories.
fetchValuesForExtraLanguages()
Function to get alternative languages of a data into $this->array_languages This method is NOT called...
fetchNoCompute($id)
Function to make a fetch but set environment to avoid to load computed values before.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
getJSListDependancies($type='_extra')
setProject($projectid, $notrigger=0)
Link element with a project.
isForcedToNullIfZero($info)
Function test if field is forced to null if zero or empty.
line_ajaxorder($rows)
Update position of line with ajax (rang)
printOriginLinesList($restrictlist='', $selectedLines=array())
Return HTML table table of source object lines TODO Move this and previous function into output html ...
fetch_origin()
Read linked origin object.
getIdOfLine($rang)
Get rowid of the line relative to its position.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
deleteByParentField($parentId=0, $parentField='', $filter='', $filtermode="AND")
Delete all child object from a parent ID.
static getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
Function used to get an array with all items linked to an object id in association table.
__clone()
Overwrite magic function to solve problem of cloning object that are kept as references.
setPaymentMethods($id)
Change the payments methods.
updateLineDown($rowid, $rang, $max)
Update position of line down (rang)
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
fetchCommon($id, $ref=null, $morewhere='', $noextrafields=0)
Load object in memory from the database.
deleteCommon(User $user, $notrigger=0, $forcechilddeletion=0)
Delete object in database.
getFormatedCustomerRef($objref)
Return customer ref for screen output.
getTooltipContentArray($params)
Return array of data to show into a tooltip.
isText($info)
Function test if type is text.
isDate($info)
Function test if type is date.
printOriginLine($line, $var, $restrictlist='', $defaulttpldir='/core/tpl', $selectedLines=array())
Return HTML with a line of table array of source object lines TODO Move this and previous function in...
showOptionals($extrafields, $mode='view', $params=null, $keysuffix='', $keyprefix='', $onetrtd='', $display_type='card')
Function to show lines of extrafields with output data.
getCanvas($id=0, $ref='')
Load type of canvas of an object if it exists.
line_up($rowid, $fk_parent_line=true)
Update a line to have a lower rank.
isDuration($info)
Function test if type is duration.
setErrorWithLog($message, $loglevel=LOG_ERR)
Set the error message for the object and logs it, including the file name and line number.
canBeNull($info)
Function test if field can be null.
listeTypeContacts($source='internal', $option=0, $activeonly=0, $code='', $element='', $excludeelement='')
Return array with list of possible values for type of contacts.
getRights()
Returns the rights used for this class.
addThumbs($file, $quality=50)
Build thumb.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
cloneCategories($fromId, $toId, $type='')
Copy related categories to another object.
fetch_barcode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
Class to manage contact/addresses.
Class to manage absolute discounts.
Class to manage a WYSIWYG editor.
Class to manage Dolibarr database access.
Class to manage donations.
Definition don.class.php:41
Class to manage ECM files.
Class to manage standard extra fields.
static isEmptyValue($v, string $type)
Return if a value is "empty" for a mandatory vision.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage generation of HTML components Only common components must be here.
Parent class of subscription templates.
Parent class to manage intervention document templates.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_SERVICE
Service.
Class to manage projects.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
Class toolbox to validate values.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:168
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
getState($id, $withcode='0', $dbtouse=null, $withregion=0, $outputlangs=null, $entconv=1)
Return state translated from an id.
convertSecondToTime($iSecond, $format='all', $lengthOfDay=86400, $lengthOfWeek=7)
Return, in clear text, value of a number of seconds in days, hours and minutes.
Definition date.lib.php:248
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_meta_create($object)
Create a meta file with document file into same directory.
completeFileArrayWithDatabaseInfo(&$filearray, $relativedir, $object=null)
Complete $filearray with data from database.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_is_dir($folder)
Test if filename is a directory.
dol_delete_preview($object)
Delete all preview files linked to object instance.
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getPictoForType($key, $morecss='')
Return the picto for a data type.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
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.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=1, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
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.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png, bmp and webp)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php: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.