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 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
5967 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
5968 // Without the guard, printing origin lines from a PO into a vendor
5969 // invoice fatals (#37663). Other tpl files already follow this pattern.
5970 $this->tpl['strike'] = 1;
5971 }
5972
5973 // Output template part (modules that overwrite templates must declare this into descriptor)
5974 // Use global variables + $dateSelector + $seller and $buyer
5975 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5976 foreach ($dirtpls as $module => $reldir) {
5977 if (!empty($module)) {
5978 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5979 } else {
5980 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5981 }
5982
5983 if (empty($conf->file->strict_mode)) {
5984 $res = @include $tpl;
5985 } else {
5986 $res = include $tpl; // for debug
5987 }
5988 if ($res) {
5989 break;
5990 }
5991 }
5992 }
5993
5994
5995 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6007 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
6008 {
6009 // phpcs:enable
6010 global $user;
6011 $this->db->begin();
6012
6013 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
6014 $sql .= "resource_id";
6015 $sql .= ", resource_type";
6016 $sql .= ", element_id";
6017 $sql .= ", element_type";
6018 $sql .= ", busy";
6019 $sql .= ", mandatory";
6020 $sql .= ") VALUES (";
6021 $sql .= ((int) $resource_id);
6022 $sql .= ", '".$this->db->escape($resource_type)."'";
6023 $sql .= ", '".$this->db->escape((string) $this->id)."'";
6024 $sql .= ", '".$this->db->escape($this->element)."'";
6025 $sql .= ", '".$this->db->escape((string) $busy)."'";
6026 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
6027 $sql .= ")";
6028
6029 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
6030 if ($this->db->query($sql)) {
6031 if (!$notrigger) {
6032 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
6033 if ($result < 0) {
6034 $this->db->rollback();
6035 return -1;
6036 }
6037 }
6038 $this->db->commit();
6039 return 1;
6040 } else {
6041 $this->error = $this->db->lasterror();
6042 $this->db->rollback();
6043 return 0;
6044 }
6045 }
6046
6047 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6056 public function delete_resource($rowid, $element = '', $notrigger = 0)
6057 {
6058 // phpcs:enable
6059 global $user;
6060
6061 $this->db->begin();
6062
6063 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6064 $sql .= " WHERE rowid = ".((int) $rowid);
6065
6066 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6067
6068 $resql = $this->db->query($sql);
6069 if (!$resql) {
6070 $this->error = $this->db->lasterror();
6071 $this->db->rollback();
6072 return -1;
6073 } else {
6074 if (!$notrigger) {
6075 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6076 if ($result < 0) {
6077 $this->db->rollback();
6078 return -1;
6079 }
6080 }
6081 $this->db->commit();
6082 return 1;
6083 }
6084 }
6085
6086
6092 public function __clone()
6093 {
6094 // Force a copy of this->lines, otherwise it will point to same object.
6095 if (isset($this->lines) && is_array($this->lines)) {
6096 $nboflines = count($this->lines);
6097 for ($i = 0; $i < $nboflines; $i++) {
6098 if (is_object($this->lines[$i])) {
6099 $this->lines[$i] = clone $this->lines[$i];
6100 }
6101 }
6102 }
6103 }
6104
6118 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6119 {
6120 global $conf, $langs, $hookmanager, $action;
6121
6122 $srctemplatepath = '';
6123
6124 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6125 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6126
6127 if (!empty($reshook)) {
6128 return $reshook;
6129 }
6130
6131 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6132
6133 if (empty($modele)) {
6134 $this->error = 'BadValueForParameterModele';
6135 return -1;
6136 }
6137
6138 // Increase limit for PDF build
6139 $err = error_reporting();
6140 error_reporting(0);
6141 @set_time_limit(120);
6142 error_reporting($err);
6143
6144 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6145 $tmp = explode(':', $modele, 2);
6146 $saved_model = $modele;
6147 if (!empty($tmp[1])) {
6148 $modele = $tmp[0];
6149 $srctemplatepath = $tmp[1];
6150 }
6151
6152 // Search template files
6153 $file = '';
6154 $classname = '';
6155 $filefound = '';
6156 $dirmodels = array('/');
6157 if (is_array($conf->modules_parts['models'])) {
6158 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6159 }
6160 foreach ($dirmodels as $reldir) {
6161 foreach (array('doc', 'pdf') as $prefix) {
6162 if (in_array(get_class($this), array('Adherent'))) {
6163 // Member module use prefix_modele.class.php
6164 $file = $prefix."_".$modele.".class.php";
6165 } else {
6166 // Other module use prefix_modele.modules.php
6167 $file = $prefix."_".$modele.".modules.php";
6168 }
6169
6170 $file = dol_sanitizeFileName($file);
6171
6172 // We check if the file exists
6173 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6174 if (file_exists($file)) {
6175 $filefound = $file;
6176 $classname = $prefix.'_'.$modele;
6177 break;
6178 }
6179 }
6180 if ($filefound) {
6181 break;
6182 }
6183 }
6184
6185 if ($filefound === '' || $classname === '') {
6186 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6187 $this->errors[] = $this->error;
6188 dol_syslog($this->error, LOG_ERR);
6189 return -1;
6190 }
6191
6192 // Sanitize $filefound
6193 $filefound = dol_sanitizePathName($filefound);
6194
6195 // If generator was found
6196 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6197
6198 require_once $filefound;
6199
6200 $obj = new $classname($this->db);
6201
6202 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6203 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6204 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6205 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6206 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6207 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6208 // ModelePDFStock|ModelePDFStockTransfer|
6209 // ModeleDon|ModelePDFTask|
6210 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6211 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6212 '@phan-var-force ModelePDFMember $obj';
6213 // '@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';
6214
6215 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6216 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6217 $varfortemplatedir = $obj->scandir;
6218 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6219 $dirtoscan = getDolGlobalString($varfortemplatedir);
6220
6221 $listoffiles = array();
6222
6223 // Now we add first model found in directories scanned
6224 $listofdir = explode(',', $dirtoscan);
6225 foreach ($listofdir as $key => $tmpdir) {
6226 $tmpdir = trim($tmpdir);
6227 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6228 if (!$tmpdir) {
6229 unset($listofdir[$key]);
6230 continue;
6231 }
6232 if (is_dir($tmpdir)) {
6233 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6234 if (count($tmpfiles)) {
6235 $listoffiles = array_merge($listoffiles, $tmpfiles);
6236 }
6237 }
6238 }
6239
6240 if (count($listoffiles)) {
6241 foreach ($listoffiles as $record) {
6242 $srctemplatepath = $record['fullname'];
6243 break;
6244 }
6245 }
6246 }
6247
6248 if (empty($srctemplatepath)) {
6249 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6250 return -1;
6251 }
6252 }
6253
6254 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6255 if (!dol_is_file($srctemplatepath)) {
6256 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6257 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6258 return -1;
6259 }
6260 }
6261
6262 // We save charset_output to restore it because write_file can change it if needed for
6263 // output format that does not support UTF8.
6264 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6265
6266 // update model_pdf in object
6267 $this->model_pdf = $saved_model;
6268
6269 try {
6270 if ($obj instanceof ModelePDFMember) {
6271 if ($this instanceof Adherent) {
6272 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6273 } else {
6274 $resultwritefile = -1;
6275 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6276 }
6277 } elseif ($obj instanceof ModeleDon) {
6278 // Only 3 arguments
6279 if ($this instanceof Don) {
6280 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6281 } else {
6282 $resultwritefile = -1;
6283 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6284 }
6285 } else {
6286 // TODO: Try to set type above again
6287 '@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';
6288 $this->context['moreparams'] = $moreparams;
6289 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref); // @phan-suppress-current-line PhanTypeMismatchArgument
6290 }
6291 } catch (\Throwable $e) {
6292 $resultwritefile = -1;
6293 $this->error = $e->getMessage();
6294 dol_syslog("Exception in write_file() for ".get_class($this).": ".$e->getMessage(), LOG_ERR);
6295 }
6296 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6297
6298
6299 if ($resultwritefile > 0) {
6300 $outputlangs->charset_output = $sav_charset_output;
6301
6302 // We delete old preview
6303 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6304 dol_delete_preview($this);
6305
6306 // Index file in database
6307 if (!empty($obj->result['fullpath'])) {
6308 $destfull = $obj->result['fullpath'];
6309
6310 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6311 $update_main_doc_field = 0;
6312 if (!empty($obj->update_main_doc_field)) {
6313 $update_main_doc_field = 1;
6314 }
6315
6316 // Check that the file exists, before indexing it.
6317 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6318 if (dol_is_file($destfull)) {
6319 $this->indexFile($destfull, $update_main_doc_field);
6320 }
6321 } else {
6322 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);
6323 }
6324
6325 // Success in building document. We build meta file.
6326 dol_meta_create($this);
6327
6328 $this->warnings = $obj->warnings;
6329
6330 return 1;
6331 } else {
6332 $outputlangs->charset_output = $sav_charset_output;
6333 if (empty($this->error)) {
6334 $this->error = $obj->error;
6335 }
6336 if (empty($this->errors)) {
6337 $this->errors = $obj->errors;
6338 }
6339 $this->warnings = $obj->warnings;
6340 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$this->error, LOG_ERR);
6341 return -1;
6342 }
6343 }
6344
6354 public function indexFile($destfull, $update_main_doc_field)
6355 {
6356 global $conf, $user;
6357
6358 $upload_dir = dirname($destfull);
6359 $destfile = basename($destfull);
6360 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6361
6362 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6363 $filename = basename($destfile);
6364 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6365 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6366
6367 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6368 $ecmfile = new EcmFiles($this->db);
6369 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6370
6371 // Set the public "share" key
6372 $setsharekey = false;
6373 if (
6374 !empty($this->TRIGGER_PREFIX)
6375 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6376 ) {
6377 $setsharekey = true;
6378 }
6379 // TODO Remove case covered by trigger prefix
6380 if ($this->element == 'propal' || $this->element == 'proposal') {
6381 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6382 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6383 }
6384 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6385 $setsharekey = true;
6386 }
6387 }
6388 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6389 $setsharekey = true;
6390 }
6391 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6392 $setsharekey = true;
6393 }
6394 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6395 $setsharekey = true;
6396 }
6397 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6398 $setsharekey = true;
6399 }
6400 if ($this->element == 'order_supplier' && getDolGlobalInt("SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6401 $setsharekey = true;
6402 }
6403 if ($this->element == 'invoice_supplier' && getDolGlobalInt("SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6404 $setsharekey = true;
6405 }
6406 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6407 $setsharekey = true;
6408 }
6409
6410 if ($setsharekey) {
6411 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6412 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6413 $ecmfile->share = getRandomPassword(true);
6414 }
6415 }
6416
6417 if ($result > 0) {
6418 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6419 $ecmfile->fullpath_orig = '';
6420 $ecmfile->gen_or_uploaded = 'generated';
6421 $ecmfile->description = ''; // indexed content
6422 $ecmfile->keywords = ''; // keyword content
6423 $ecmfile->src_object_type = $this->table_element;
6424 $ecmfile->src_object_id = $this->id;
6425 $result = $ecmfile->update($user);
6426 if ($result < 0) {
6427 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6428 return -1;
6429 }
6430 } else {
6431 $ecmfile->entity = $conf->entity;
6432 $ecmfile->filepath = $rel_dir;
6433 $ecmfile->filename = $filename;
6434 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6435 $ecmfile->fullpath_orig = '';
6436 $ecmfile->gen_or_uploaded = 'generated';
6437 $ecmfile->description = ''; // indexed content
6438 $ecmfile->keywords = ''; // keyword content
6439 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6440 $ecmfile->src_object_id = $this->id;
6441
6442 $result = $ecmfile->create($user);
6443 if ($result < 0) {
6444 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6445 return -1;
6446 }
6447 }
6448
6449 /*$this->result['fullname']=$destfull;
6450 $this->result['filepath']=$ecmfile->filepath;
6451 $this->result['filename']=$ecmfile->filename;*/
6452 //var_dump($obj->update_main_doc_field);exit;
6453
6454 if ($update_main_doc_field && !empty($this->table_element)) {
6455 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6456 $sql .= " WHERE rowid = ".((int) $this->id);
6457
6458 $resql = $this->db->query($sql);
6459 if (!$resql) {
6460 dol_print_error($this->db);
6461 return -1;
6462 } else {
6463 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6464 }
6465 }
6466 }
6467
6468 return 1;
6469 }
6470
6479 public function addThumbs($file, $quality = 50)
6480 {
6481 $file_osencoded = dol_osencode($file);
6482
6483 if (file_exists($file_osencoded)) {
6484 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6485
6486 $tmparraysize = getDefaultImageSizes();
6487 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6488 $maxheightsmall = $tmparraysize['maxheightsmall'];
6489 $maxwidthmini = $tmparraysize['maxwidthmini'];
6490 $maxheightmini = $tmparraysize['maxheightmini'];
6491 //$quality = $tmparraysize['quality'];
6492
6493 // Create small thumbs for company (Ratio is near 16/9)
6494 // Used on logon for example
6495 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6496
6497 // Create mini thumbs for company (Ratio is near 16/9)
6498 // Used on menu or for setup page for example
6499 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6500 }
6501 }
6502
6510 public function delThumbs($file)
6511 {
6512 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6513 dol_delete_file($imgThumbName);
6514 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6515 dol_delete_file($imgThumbName);
6516 }
6517
6518
6519 /* Functions common to commonobject and commonobjectline */
6520
6521 /* For default values */
6522
6536 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6537 {
6538 /* TODO Remove this. Must use now something like:
6539 $note_private = GETPOST('note_private', 'restricthtml');
6540 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6541 $note_private = $objectsrc->note_private;
6542 }
6543 */
6544
6545 // If param here has been posted, we use this value first.
6546 if (GETPOSTISSET($fieldname)) {
6547 return GETPOST($fieldname, $type, 3);
6548 }
6549
6550 if (isset($alternatevalue)) {
6551 return $alternatevalue;
6552 }
6553
6554 $newelement = $this->element;
6555 if ($newelement == 'facture') {
6556 $newelement = 'invoice';
6557 }
6558 if ($newelement == 'commande') {
6559 $newelement = 'order';
6560 }
6561 if (empty($newelement)) {
6562 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6563 return '';
6564 }
6565
6566 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6567 //var_dump($keyforfieldname);
6568 if (getDolGlobalString($keyforfieldname)) {
6569 return getDolGlobalString($keyforfieldname);
6570 }
6571
6572 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6573 // store content into $conf->cache['overwrite_default']
6574
6575 return '';
6576 }
6577
6578
6579 /* Functions for data in other language */
6580
6581
6591 {
6592 // To avoid SQL errors. Probably not the better solution though
6593 if (!$this->element) {
6594 return 0;
6595 }
6596 if (!($this->id > 0)) {
6597 return 0;
6598 }
6599 if (is_array($this->array_languages)) {
6600 return 1;
6601 }
6602
6603 $this->array_languages = array();
6604
6605 $element = $this->element;
6606 if ($element == 'categorie') {
6607 $element = 'categories'; // For compatibility
6608 }
6609
6610 // Request to get translation values for object
6611 $sql = "SELECT rowid, property, lang , value";
6612 $sql .= " FROM ".$this->db->prefix()."object_lang";
6613 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6614 $sql .= " AND fk_object = ".((int) $this->id);
6615
6616 $resql = $this->db->query($sql);
6617 if ($resql) {
6618 $numrows = $this->db->num_rows($resql);
6619 if ($numrows) {
6620 $i = 0;
6621 while ($i < $numrows) {
6622 $obj = $this->db->fetch_object($resql);
6623 $key = $obj->property;
6624 $value = $obj->value;
6625 $codelang = $obj->lang;
6626 $type = $this->fields[$key]['type'];
6627
6628 // we can add this attribute to object
6629 if (preg_match('/date/', $type)) {
6630 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6631 } else {
6632 $this->array_languages[$key][$codelang] = $value;
6633 }
6634
6635 $i++;
6636 }
6637 }
6638
6639 $this->db->free($resql);
6640
6641 if ($numrows) {
6642 return $numrows;
6643 } else {
6644 return 0;
6645 }
6646 } else {
6647 dol_print_error($this->db);
6648 return -1;
6649 }
6650 }
6651
6659 public function setValuesForExtraLanguages($onlykey = '')
6660 {
6661 // Get extra fields
6662 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6663 $tmparray = explode('-', $postfieldkey);
6664 if ($tmparray[0] != 'field') {
6665 continue;
6666 }
6667
6668 $element = $tmparray[1];
6669 $key = $tmparray[2];
6670 $codelang = $tmparray[3];
6671 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6672
6673 if (!empty($onlykey) && $key != $onlykey) {
6674 continue;
6675 }
6676 if ($element != $this->element) {
6677 continue;
6678 }
6679
6680 $key_type = $this->fields[$key]['type'];
6681
6682 $enabled = 1;
6683 if (isset($this->fields[$key]['enabled'])) {
6684 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6685 }
6686 /*$perms = 1;
6687 if (isset($this->fields[$key]['perms']))
6688 {
6689 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6690 }*/
6691 if (empty($enabled)) {
6692 continue;
6693 }
6694 //if (empty($perms)) continue;
6695
6696 if (in_array($key_type, array('date'))) {
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(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6700 } elseif (in_array($key_type, array('datetime'))) {
6701 // Clean parameters
6702 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6703 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6704 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6705 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6706 if (!empty($value_arr)) {
6707 $value_key = implode(',', $value_arr);
6708 } else {
6709 $value_key = '';
6710 }
6711 } elseif (in_array($key_type, array('price', 'double'))) {
6712 $value_arr = GETPOST($postfieldkey, 'alpha');
6713 $value_key = price2num($value_arr);
6714 } else {
6715 $value_key = GETPOST($postfieldkey);
6716 if (in_array($key_type, array('link')) && $value_key == '-1') {
6717 $value_key = '';
6718 }
6719 }
6720
6721 $this->array_languages[$key][$codelang] = $value_key;
6722
6723 /*if ($nofillrequired) {
6724 $langs->load('errors');
6725 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6726 return -1;
6727 }*/
6728 }
6729
6730 return 1;
6731 }
6732
6733
6734 /* Functions for extrafields */
6735
6742 public function fetchNoCompute($id)
6743 {
6744 global $conf;
6745
6746 $savDisableCompute = $conf->disable_compute;
6747 $conf->disable_compute = 1;
6748
6749 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6750
6751 $conf->disable_compute = $savDisableCompute;
6752
6753 return $ret;
6754 }
6755
6756 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6766 public function fetch_optionals($rowid = null, $optionsArray = null)
6767 {
6768 // phpcs:enable
6769 global $conf, $extrafields;
6770
6771 if (empty($rowid)) {
6772 $rowid = $this->id;
6773 }
6774 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6775 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6776 }
6777
6778 // To avoid SQL errors. Probably not the better solution though
6779 if (!$this->table_element) {
6780 return 0;
6781 }
6782 $this->array_options = array();
6783
6784 if (!is_array($optionsArray)) {
6785 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6786 if (!isset($extrafields) || !is_object($extrafields)) {
6787 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6788 $extrafields = new ExtraFields($this->db);
6789 }
6790
6791 // Load array of extrafields for elementype = $this->table_element
6792 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6793 $extrafields->fetch_name_optionals_label($this->table_element);
6794 }
6795 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6796 } else {
6797 global $extrafields;
6798 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6799 }
6800
6801 $table_element = $this->table_element;
6802 if ($table_element == 'categorie') {
6803 $table_element = 'categories'; // For compatibility
6804 }
6805
6806 // Request to get complementary values
6807 if (is_array($optionsArray) && count($optionsArray) > 0) {
6808 $sql = "SELECT rowid";
6809 foreach ($optionsArray as $name => $label) {
6810 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6811 $sql .= ", ".$this->db->sanitize($name);
6812 }
6813 // use geo sql fonction to read as text
6814 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6815 // TODO Add an abstraction method in the database driver
6816 $sql .= ", ST_AsWKT(".$name.") as ".$this->db->sanitize($name);
6817 }
6818 }
6819 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6820 $sql .= " WHERE fk_object = ".((int) $rowid);
6821
6822 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6823 $resql = $this->db->query($sql);
6824 if ($resql) {
6825 $numrows = $this->db->num_rows($resql);
6826 if ($numrows) {
6827 $tab = $this->db->fetch_array($resql);
6828 foreach ($tab as $key => $value) {
6829 // 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)
6830 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6831 // we can add this attribute to object
6832 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6833 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6834 $this->array_options["options_".$key] = $this->db->jdate($value);
6835 } else {
6836 $this->array_options["options_".$key] = $value;
6837 }
6838
6839 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6840 }
6841 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6842 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6843 $this->array_options["options_".$key] = dolDecrypt($value);
6844 }
6845 }
6846 }
6847 } else {
6852 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6853 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6854 $this->array_options['options_' . $key] = null;
6855 }
6856 }
6857 }
6858
6859 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6860 // in the element's extrafields table)
6861 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6862 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6863 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6864 //var_dump($conf->disable_compute);
6865 if (empty($conf->disable_compute)) {
6866 // We set a global variable to $objectoffield so we can use it inside computed formula
6867 $objectoffield = dol_clone($this, 2);
6868 global $objectoffield;
6869 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6870 }
6871 }
6872 }
6873 }
6874
6875 $this->db->free($resql);
6876
6877 if ($numrows) {
6878 return $numrows;
6879 } else {
6880 return 0;
6881 }
6882 } else {
6883 $this->errors[] = $this->db->lasterror();
6884 return -1;
6885 }
6886 }
6887 return 0;
6888 }
6889
6896 public function deleteExtraFields()
6897 {
6898 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6899 return 0;
6900 }
6901
6902 $this->db->begin();
6903
6904 $table_element = $this->table_element;
6905 if ($table_element == 'categorie') {
6906 $table_element = 'categories'; // For compatibility
6907 }
6908
6909 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6910
6911 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6912
6913 $resql = $this->db->query($sql_del);
6914 if (!$resql) {
6915 $this->error = $this->db->lasterror();
6916 $this->db->rollback();
6917 return -1;
6918 } else {
6919 $this->db->commit();
6920 return 1;
6921 }
6922 }
6923
6934 public function insertExtraFields($trigger = '', $userused = null)
6935 {
6936 global $langs, $extrafields, $user;
6937
6938 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6939 return 0;
6940 }
6941
6942 if (empty($userused)) {
6943 $userused = $user;
6944 }
6945
6946 $error = 0;
6947
6948 // Check parameters
6949 $langs->load('admin');
6950 if (!isset($extrafields) || !is_object($extrafields)) {
6951 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6952 $extrafields = new ExtraFields($this->db);
6953 }
6954 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6955
6956 // Eliminate copied source object extra fields that do not exist in target object
6957 $new_array_options = array();
6958 foreach ($this->array_options as $key => $value) {
6959 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6960 $new_array_options[$key] = $value;
6961 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6962 $new_array_options['options_'.$key] = $value;
6963 }
6964 }
6965
6966 foreach ($new_array_options as $key => $value) {
6967 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6968 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6969 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6970 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6971 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6972 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6973 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6974 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
6975 /*
6976 $attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
6977 $enabled = 0;
6978 if (!empty($extrafields->attributes[$this->table_element]['enabled'][$key])) {
6979 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
6980 }
6981 */
6982
6983 // If we clone, we have to clean unique extrafields to prevent duplicates.
6984 // If we clone, we have to clean extrafields having "empty on clone" option on.
6985 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6986 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
6987 $new_array_options[$key] = null;
6988 continue;
6989 }
6990
6991 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6992 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6993 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6994 $new_array_options[$key] = null;
6995 continue;
6996 }
6997
6998 // Similar code than into insertExtraFields
6999 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'.
7000 $v = $this->array_options[$key];
7001 if (ExtraFields::isEmptyValue($v, $attributeType)) {
7002 $langs->load("errors");
7003 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
7004 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7005 return -1;
7006 }
7007 }
7008
7009 if (!empty($attrfieldcomputed)) {
7010 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7011 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7012 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7013 $new_array_options[$key] = $value;
7014 } else {
7015 $new_array_options[$key] = null;
7016 }
7017 continue;
7018 }
7019
7020 switch ($attributeType) {
7021 case 'int':
7022 case 'duration':
7023 case 'stars':
7024 if (!is_numeric($value) && $value != '') {
7025 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7026 return -1;
7027 } elseif ($value === '') {
7028 $new_array_options[$key] = null;
7029 }
7030 break;
7031 case 'boolean':
7032 if ($value === '' || $value === false || $value === null) {
7033 $new_array_options[$key] = null;
7034 }
7035 break;
7036 case 'price':
7037 case 'double':
7038 $value = price2num($value);
7039 if (!is_numeric($value) && $value != '') {
7040 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7041 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7042 return -1;
7043 } elseif ($value == '') {
7044 $value = null;
7045 }
7046 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7047 $new_array_options[$key] = $value;
7048 break;
7049 /*case 'select': // Not required, we chose value='0' for undefined values
7050 if ($value=='-1')
7051 {
7052 $this->array_options[$key] = null;
7053 }
7054 break;*/
7055 case 'password':
7056 $algo = '';
7057 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7058 // If there is an encryption choice, we use it to encrypt data before insert
7059 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7060 $algo = reset($tmparrays);
7061 if ($algo != '') {
7062 //global $action; // $action may be 'create', 'update', 'update_extras'...
7063 //var_dump($action);
7064 //var_dump($this->oldcopy);exit;
7065 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
7066 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7067 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7068 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7069 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7070 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7071 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7072 } else {
7073 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7074 }
7075 } else {
7076 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7077 }
7078 } else {
7079 // If value has changed
7080 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7081 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7082 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7083 } else {
7084 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7085 }
7086 } else {
7087 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7088 }
7089 }
7090 } else {
7091 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7092 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7093 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7094 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7095 } else {
7096 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7097 }
7098 }
7099 } else {
7100 // No encryption
7101 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7102 }
7103 } else { // Common usage
7104 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7105 }
7106 break;
7107 case 'date':
7108 case 'datetime':
7109 // If data is a string instead of a timestamp, we convert it
7110 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7111 $this->array_options[$key] = strtotime($this->array_options[$key]);
7112 }
7113 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7114 break;
7115 case 'datetimegmt':
7116 // If data is a string instead of a timestamp, we convert it
7117 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7118 $this->array_options[$key] = strtotime($this->array_options[$key]);
7119 }
7120 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7121 break;
7122 case 'link':
7123 $param_list = array_keys($attributeParam['options']);
7124 // 0 : ObjectName
7125 // 1 : classPath
7126 $InfoFieldList = explode(":", $param_list[0]);
7127 dol_include_once($InfoFieldList[1]);
7128 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7129 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7130 $new_array_options[$key] = '';
7131 } elseif ($value) {
7132 $object = new $InfoFieldList[0]($this->db);
7133 '@phan-var-force CommonObject $object';
7134
7135 $objectId = 0;
7136
7137 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7138 if (is_numeric($value)) {
7139 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7140 } else {
7141 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7142 }
7143
7144 $obj = $this->db->getRow($sqlFetchObject);
7145
7146 if ($obj !== false) {
7147 $objectId = $obj->rowid;
7148 $res = 1;
7149 } else {
7150 $res = -1;
7151 }
7152
7153 if ($res > 0) {
7154 $new_array_options[$key] = $objectId;
7155 } else {
7156 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7157 return -1;
7158 }
7159 }
7160 } else {
7161 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7162 }
7163 break;
7164 case 'checkbox':
7165 case 'chkbxlst':
7166 if (is_array($this->array_options[$key])) {
7167 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7168 } else {
7169 $new_array_options[$key] = $this->array_options[$key];
7170 }
7171 break;
7172 }
7173 }
7174
7175 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7176 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7177
7178 foreach ($new_array_options as $key => $newValue) {
7179 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7180 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7181 if ($attributeType === 'separate') {
7182 // separator extrafields don't have data per object so they don't have a comlumn in the database
7183 continue;
7184 }
7185 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7186 // Add field of attribute
7187 if (! $geoDataType) {
7188 // not a geodata type
7189 if ($newValue != '') {
7190 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7191 } else {
7192 $sqlColumnValues[$attributeKey] = 'null';
7193 }
7194 continue;
7195 }
7196 if (empty($newValue)) {
7197 $sqlColumnValues[$attributeKey] = 'null';
7198 continue;
7199 }
7200 if (preg_match('/error/i', $newValue)) {
7201 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7202 $sqlColumnValues[$attributeKey] = 'null';
7203 continue;
7204 }
7205
7206 // Geodata type: Text must be a WKT string. Examples:
7207 // - point => "POINT(15 20)"
7208 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7209 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7210 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7211 $sqlColumnValues[$attributeKey] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')";
7212 }
7213
7214 $this->db->begin();
7215
7216 $table_element = $this->table_element;
7217 if ($table_element == 'categorie') {
7218 $table_element = 'categories'; // For compatibility
7219 }
7220 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7221
7222 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7223
7224 $linealreadyfound = 0;
7225 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7226 $resql = $this->db->query($sql);
7227 if ($resql) {
7228 $tmpobj = $this->db->fetch_object($resql);
7229 if ($tmpobj) {
7230 $linealreadyfound = $tmpobj->nb;
7231 }
7232 }
7233
7234 // if the extrafields row already exists for the object, we update it
7235 if ($linealreadyfound) {
7236 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7237 $sqlColumnValueString = implode(
7238 ',',
7243 array_map(function ($key) use ($sqlColumnValues) {
7244 return "{$key} = {$sqlColumnValues[$key]}";
7245 }, array_keys($sqlColumnValues))
7246 );
7247 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7248 } else {
7249 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7250 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7251 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7252 if (isset($sqlColumnValues[$key])) {
7253 // extrafield value already provided: no need to add it
7254 continue;
7255 }
7256 // default value: empty string, except for 'int', 'double' and 'price'
7257 $sqlColumnValues[$key] = "''";
7258 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7259 $sqlColumnValues[$key] = 0;
7260 }
7261 }
7262 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7263 $sqlValues = implode(',', array_values($sqlColumnValues));
7264 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7265 }
7266
7267 $resql = $this->db->query($sql);
7268 if (!$resql) {
7269 $this->error = $this->db->lasterror();
7270 $error++;
7271 }
7272
7273 // Update also the user of last modification in parent table
7274 if (!$error && !empty($this->fields['fk_user_modif'])) { // @phan-suppress-current-line PhanTypeMismatchProperty
7275 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7276 $sql .= " SET fk_user_modif = ".(int) $user->id;
7277 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7278 $this->db->query($sql);
7279 }
7280
7281 if (!$error && $trigger) {
7282 // Call trigger
7283 $this->context = array('extrafieldaddupdate' => 1);
7284 $result = $this->call_trigger($trigger, $userused);
7285 if ($result < 0) {
7286 $error++;
7287 }
7288 // End call trigger
7289 }
7290
7291 if ($error) {
7292 $this->db->rollback();
7293 return -1;
7294 } else {
7295 $this->db->commit();
7296 return 1;
7297 }
7298 }
7299
7311 public function insertExtraLanguages($trigger = '', $userused = null)
7312 {
7313 global $langs, $user;
7314
7315 if (empty($userused)) {
7316 $userused = $user;
7317 }
7318
7319 $error = 0;
7320
7321 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7322 return 0; // For avoid conflicts if trigger used
7323 }
7324
7325 if (is_array($this->array_languages)) {
7326 $new_array_languages = $this->array_languages;
7327
7328 foreach ($new_array_languages as $key => $value) {
7329 $attributeKey = $key;
7330 $attributeType = $this->fields[$attributeKey]['type'];
7331 $attributeLabel = $this->fields[$attributeKey]['label'];
7332
7333 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7334 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7335
7336 switch ($attributeType) {
7337 case 'int':
7338 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7339 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7340 return -1;
7341 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7342 $new_array_languages[$key] = null;
7343 }
7344 break;
7345 case 'double':
7346 $value = price2num((string) $value);
7347 if (!is_numeric($value) && $value != '') {
7348 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7349 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7350 return -1;
7351 } elseif ($value == '') {
7352 $new_array_languages[$key] = null;
7353 } else {
7354 $new_array_languages[$key] = $value;
7355 }
7356 break;
7357 /*case 'select': // Not required, we chose value='0' for undefined values
7358 if ($value=='-1')
7359 {
7360 $this->array_options[$key] = null;
7361 }
7362 break;*/
7363 }
7364 }
7365
7366 $this->db->begin();
7367
7368 $table_element = $this->table_element;
7369 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7370 $table_element = 'categories'; // For compatibility
7371 }
7372
7373 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7374
7375 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7376 foreach ($langcodearray as $langcode => $value) {
7377 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7378 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7379 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7380 $this->db->query($sql_del);
7381
7382 if ($value !== '') {
7383 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7384 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7385 $sql .= ")";
7386
7387 $resql = $this->db->query($sql);
7388 if (!$resql) {
7389 $this->error = $this->db->lasterror();
7390 $error++;
7391 break;
7392 }
7393 }
7394 }
7395 }
7396
7397 if (!$error && $trigger) {
7398 // Call trigger
7399 $this->context = array('extralanguagesaddupdate' => 1);
7400 $result = $this->call_trigger($trigger, $userused);
7401 if ($result < 0) {
7402 $error++;
7403 }
7404 // End call trigger
7405 }
7406
7407 if ($error) {
7408 $this->db->rollback();
7409 return -1;
7410 } else {
7411 $this->db->commit();
7412 return 1;
7413 }
7414 } else {
7415 return 0;
7416 }
7417 }
7418
7429 public function updateExtraField($key, $trigger = null, $userused = null)
7430 {
7431 global $langs, $user, $extrafields, $hookmanager;
7432
7433 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7434 return 0;
7435 }
7436
7437 if (empty($userused)) {
7438 $userused = $user;
7439 }
7440
7441 $error = 0;
7442
7443 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7444 // Check parameters
7445 $langs->load('admin');
7446 if (!isset($extrafields) || !is_object($extrafields)) {
7447 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7448 $extrafields = new ExtraFields($this->db);
7449 }
7450 $extrafields->fetch_name_optionals_label($this->table_element);
7451
7452 $value = $this->array_options["options_".$key];
7453
7454 $attributeKey = $key;
7455 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7456 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7457 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7458 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7459 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7460 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7461 //$attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7462
7463 // Similar code than into insertExtraFields
7464 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'.
7465 $mandatorypb = false;
7466 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7467 $mandatorypb = true;
7468 }
7469 if ($this->array_options["options_".$key] === '') {
7470 $mandatorypb = true;
7471 }
7472 if ($mandatorypb) {
7473 $langs->load("errors");
7474 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7475 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7476 return -1;
7477 }
7478 }
7479
7480 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7481 $new_array_options = $this->array_options;
7482
7483 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7484 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7485 if (!empty($attrfieldcomputed)) {
7486 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7487 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7488 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7489
7490 $new_array_options["options_".$key] = $value;
7491
7492 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7493 } else {
7494 $new_array_options["options_".$key] = null;
7495
7496 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7497 }
7498 }
7499
7500 switch ($attributeType) {
7501 case 'int':
7502 if (!is_numeric($value) && $value != '') {
7503 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7504 return -1;
7505 } elseif ($value === '') {
7506 $new_array_options["options_".$key] = null;
7507
7508 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7509 }
7510 break;
7511 case 'price':
7512 case 'double':
7513 $value = price2num($value);
7514 if (!is_numeric($value) && $value != '') {
7515 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7516 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7517 return -1;
7518 } elseif ($value === '') {
7519 $value = null;
7520 }
7521 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7522 $new_array_options["options_".$key] = $value;
7523
7524 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7525 break;
7526 /*case 'select': // Not required, we chose value='0' for undefined values
7527 if ($value=='-1')
7528 {
7529 $new_array_options["options_".$key] = $value;
7530
7531 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7532 }
7533 break;*/
7534 case 'password':
7535 $algo = '';
7536 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7537 // If there is an encryption choice, we use it to encrypt data before insert
7538 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7539 $algo = reset($tmparrays);
7540 if ($algo != '') {
7541 //global $action; // $action may be 'create', 'update', 'update_extras'...
7542 //var_dump($action);
7543 //var_dump($this->oldcopy);exit;
7544 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7545 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
7546 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7547 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.
7548 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7549 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7550 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7551 } else {
7552 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7553 }
7554 } else {
7555 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7556 }
7557 } else {
7558 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7559 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7560 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7561 } else {
7562 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7563 }
7564 } else {
7565 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7566 }
7567 }
7568 } else {
7569 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7570 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7571 } else {
7572 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7573 }
7574 }
7575 } else {
7576 // No encryption
7577 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7578 }
7579 } else { // Common usage
7580 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7581 }
7582
7583 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7584 break;
7585 case 'date':
7586 case 'datetime':
7587 if (empty($this->array_options["options_".$key])) {
7588 $new_array_options["options_".$key] = null;
7589
7590 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7591 } else {
7592 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7593 }
7594 break;
7595 case 'datetimegmt':
7596 if (empty($this->array_options["options_".$key])) {
7597 $new_array_options["options_".$key] = null;
7598
7599 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7600 } else {
7601 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7602 }
7603 break;
7604 case 'boolean':
7605 if (empty($this->array_options["options_".$key])) {
7606 $new_array_options["options_".$key] = null;
7607
7608 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7609 }
7610 break;
7611 case 'link':
7612 if ($this->array_options["options_".$key] === '') {
7613 $new_array_options["options_".$key] = null;
7614
7615 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7616 }
7617 break;
7618 /*
7619 case 'link':
7620 $param_list = array_keys($attributeParam['options']);
7621 // 0 : ObjectName
7622 // 1 : classPath
7623 $InfoFieldList = explode(":", $param_list[0]);
7624 dol_include_once($InfoFieldList[1]);
7625 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7626 {
7627 if ($value == '-1') // -1 is key for no defined in combo list of objects
7628 {
7629 $new_array_options[$key] = '';
7630 } elseif ($value) {
7631 $object = new $InfoFieldList[0]($this->db);
7632 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7633 else $res = $object->fetch(0, $value); // For compatibility
7634
7635 if ($res > 0) $new_array_options[$key] = $object->id;
7636 else {
7637 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7638 $this->db->rollback();
7639 return -1;
7640 }
7641 }
7642 } else {
7643 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7644 }
7645 break;
7646 */
7647 case 'checkbox':
7648 case 'chkbxlst':
7649 $new_array_options = array();
7650 if (is_array($this->array_options["options_".$key])) {
7651 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7652 } else {
7653 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7654 }
7655
7656 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7657 break;
7658 }
7659
7660 $this->db->begin();
7661
7662 $linealreadyfound = 0;
7663
7664 // 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)
7665 $table_element = $this->table_element;
7666 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7667 $table_element = 'categories'; // For compatibility
7668 }
7669
7670 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7671 $resql = $this->db->query($sql);
7672 if ($resql) {
7673 $tmpobj = $this->db->fetch_object($resql);
7674 if ($tmpobj) {
7675 $linealreadyfound = $tmpobj->nb;
7676 }
7677 }
7678
7679 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7680 if ($linealreadyfound) {
7681 $sanitizedGeoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7682 if ($this->array_options["options_".$key] === null) {
7683 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7684 $sql .= " SET ".$this->db->sanitize($key)." = null";
7685 } elseif (!empty($sanitizedGeoDataType['ST_Function'])) {
7686 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7687 $sql .= " SET ".$this->db->sanitize($key)." = ".$this->db->sanitize($sanitizedGeoDataType['ST_Function'])."('".$this->db->escape($this->array_options["options_".$key])."')";
7688 } else {
7689 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7690 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7691 $sql .= " SET ".$this->db->sanitize($key)." = '".$this->db->escape($new_array_options["options_".$key])."'";
7692 }
7693 $sql .= " WHERE fk_object = ".((int) $this->id);
7694
7695 $resql = $this->db->query($sql);
7696 if (!$resql) {
7697 $error++;
7698 $this->error = $this->db->lasterror();
7699 }
7700 } else {
7701 $result = $this->insertExtraFields('', $user);
7702 if ($result < 0) {
7703 $error++;
7704 }
7705 }
7706
7707 // Update also the user of last modification in parent table
7708 if (!$error && !empty($this->fields['fk_user_modif'])) {
7709 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7710 $sql .= " SET fk_user_modif = ".(int) $user->id;
7711 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7712 $this->db->query($sql);
7713 }
7714
7715 if (!$error) {
7716 $parameters = array('key' => $key);
7717 global $action;
7718 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7719 if ($reshook < 0) {
7720 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7721 }
7722 }
7723
7724 if (!$error && $trigger) {
7725 // Call trigger
7726 $this->context = array('extrafieldupdate' => 1);
7727 $result = $this->call_trigger($trigger, $userused);
7728 if ($result < 0) {
7729 $error++;
7730 }
7731 // End call trigger
7732 }
7733
7734 if ($error) {
7735 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7736 $this->db->rollback();
7737 return -1;
7738 } else {
7739 $this->db->commit();
7740 return 1;
7741 }
7742 } else {
7743 return 0;
7744 }
7745 }
7746
7753 public function getExtraField($key)
7754 {
7755 return $this->array_options['options_'.$key] ?? null;
7756 }
7757
7765 public function setExtraField($key, $value)
7766 {
7767 $this->array_options['options_'.$key] = $value;
7768 }
7769
7780 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7781 {
7782 global $user;
7783
7784 if (empty($userused)) {
7785 $userused = $user;
7786 }
7787
7788 //$error = 0;
7789
7790 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7791 return 0; // For avoid conflicts if trigger used
7792 }
7793
7794 return 0;
7795 }
7796
7797
7812 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7813 {
7814 global $conf, $langs, $form;
7815
7816
7817 // 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...)
7818
7819 if (!is_object($form)) {
7820 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7821 $form = new Form($this->db);
7822 }
7823
7824 if (!empty($this->fields)) {
7825 $val = $this->fields[$key];
7826 }
7827
7828 // Validation tests and output
7829 $fieldValidationErrorMsg = '';
7830 $validationClass = '';
7831 $fieldValidationErrorMsg = $this->getFieldError($key);
7832 if (!empty($fieldValidationErrorMsg)) {
7833 $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
7834 } else {
7835 $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
7836 }
7837
7838 //$valuemultiselectinput = array();
7839 $out = '';
7840 $type = '';
7841 $isDependList = 0;
7842 $param = array();
7843 $param['options'] = array();
7844 $reg = array();
7845 // @phan-suppress-next-line PhanTypeMismatchProperty
7846 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7847 // Because we work on extrafields
7848 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7849 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7850 $type = 'link';
7851 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7852 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7853 $type = 'link';
7854 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7855 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7856 $type = 'link';
7857 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7858 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7859 $type = 'sellist';
7860 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7861 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7862 $type = 'sellist';
7863 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7864 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7865 $type = 'sellist';
7866 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7867 $param['options'] = array($reg[1] => 'N');
7868 $type = 'chkbxlst';
7869 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7870 $param['options'] = array();
7871 $type = 'varchar';
7872 $size = $reg[1];
7873 } elseif (preg_match('/varchar/', $val['type'])) {
7874 $param['options'] = array();
7875 $type = 'varchar';
7876 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7877 $param['options'] = array();
7878 $type = 'stars';
7879 $size = $reg[1];
7880 } else {
7881 $param['options'] = array();
7882 $type = $this->fields[$key]['type'];
7883 }
7884 //var_dump($type); var_dump($param['options']);
7885
7886 // Special case that force options and type ($type can be integer, varchar, ...)
7887 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7888 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7889 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7890 if (empty($this->fields[$key]['multiinput'])) {
7891 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7892 }
7893 }
7894
7895 $label = $this->fields[$key]['label'];
7896 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7897 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7898 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7899 // @phan-suppress-next-line PhanTypeMismatchProperty
7900 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7901 // @phan-suppress-next-line PhanTypeMismatchProperty
7902 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7903 // @phan-suppress-next-line PhanTypeMismatchProperty
7904 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7905 // @phan-suppress-next-line PhanTypeMismatchProperty
7906 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7907 // @phan-suppress-next-line PhanTypeMismatchProperty
7908 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7909
7910 // @phan-suppress-next-line PhanTypeMismatchProperty
7911 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7912 // @phan-suppress-next-line PhanTypeMismatchProperty
7913 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7914 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7915
7916 $objectid = $this->id;
7917
7918 if ($computed) {
7919 if (!preg_match('/^search_/', $keyprefix)) {
7920 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7921 } else {
7922 return '';
7923 }
7924 }
7925
7926 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7927 if (empty($morecss) && !empty($val['css'])) {
7928 $morecss = $val['css'];
7929 } elseif (empty($morecss)) {
7930 if ($type == 'date') {
7931 $morecss = 'minwidth100imp';
7932 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7933 $morecss = 'minwidth200imp';
7934 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7935 $morecss = 'maxwidth75';
7936 } elseif ($type == 'url') {
7937 $morecss = 'minwidth400';
7938 } elseif ($type == 'boolean') {
7939 $morecss = '';
7940 } else {
7941 if (is_numeric($size) && round((float) $size) < 12) {
7942 $morecss = 'minwidth100';
7943 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7944 $morecss = 'minwidth200';
7945 } else {
7946 $morecss = 'minwidth400';
7947 }
7948 }
7949 }
7950
7951 // Add validation state class
7952 if (!empty($validationClass)) {
7953 $morecss .= $validationClass;
7954 }
7955
7956 if (in_array($type, array('date'))) {
7957 $tmp = explode(',', $size);
7958 $newsize = $tmp[0];
7959 $showtime = 0;
7960
7961 // Do not show current date when field not required (see selectDate() method)
7962 if (!$required && $value == '') {
7963 $value = '-1';
7964 }
7965
7966 // TODO Must also support $moreparam
7967 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7968 } elseif (in_array($type, array('datetime'))) {
7969 $tmp = explode(',', $size);
7970 $newsize = $tmp[0];
7971 $showtime = 1;
7972
7973 // Do not show current date when field not required (see selectDate() method)
7974 if (!$required && $value == '') {
7975 $value = '-1';
7976 }
7977
7978 // TODO Must also support $moreparam
7979 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7980 } elseif (in_array($type, array('duration'))) {
7981 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7982 } elseif (in_array($type, array('int', 'integer'))) {
7983 $tmp = explode(',', $size);
7984 $newsize = $tmp[0];
7985 $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' : '').'>';
7986 } elseif (in_array($type, array('real'))) {
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('/varchar/', (string) $type)) {
7989 $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' : '').'>';
7990 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7991 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7992 } elseif (preg_match('/^text/', (string) $type)) {
7993 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7994 if (!empty($param['options'])) {
7995 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7996 //var_dump($param['options']);
7997 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7998 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7999 $out .= "<script>";
8000 $out .= '
8001 function handlemultiinputdisabling(htmlname){
8002 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
8003 multiinput = $("#"+htmlname+"_multiinput");
8004 multiinput.find("option").each(function(){
8005 tmpval = $("#"+htmlname).val();
8006 tmpvalarray = tmpval.split("\n");
8007 valtotest = $(this).val();
8008 if(tmpvalarray.includes(valtotest)){
8009 $(this).prop("disabled",true);
8010 } else {
8011 if($(this).prop("disabled") == true){
8012 console.log(valtotest)
8013 $(this).prop("disabled", false);
8014 }
8015 }
8016 });
8017 }
8018
8019 $(document).ready(function () {
8020 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
8021 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
8022 tmpvalarray = tmpval.split(",");
8023 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
8024 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8025 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
8026 if(tmpval == ""){
8027 tmpval = valtotest;
8028 } else {
8029 tmpval = tmpval + "\n" + valtotest;
8030 }
8031 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
8032 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8033 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
8034 } else {
8035 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
8036 }
8037 });
8038 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
8039 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8040 });
8041 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8042 })';
8043 $out .= "</script>";
8044 $value = str_replace(',', "\n", $value);
8045 }
8046 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8047 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8048 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
8049 } else {
8050 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8051 }
8052 } elseif (preg_match('/^html/', (string) $type)) {
8053 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8054 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8055 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8056 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8057 } else {
8058 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8059 }
8060 } elseif ($type == 'boolean') {
8061 $checked = '';
8062 if (!empty($value)) {
8063 $checked = ' checked value="1" ';
8064 } else {
8065 $checked = ' value="1" ';
8066 }
8067 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
8068 } elseif ($type == 'price') {
8069 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8070 $value = price($value);
8071 }
8072 $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>';
8073 } elseif ($type == 'stars') {
8074 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8075 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8076 $i = 1;
8077 while ($i <= $size) {
8078 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8079 $i++;
8080 }
8081 $out .= '</div>';
8082 $out .= '<script>
8083 jQuery(function($) { /* commonobject.class.php 1 */
8084 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8085 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8086 container.find(".star").each(function() {
8087 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8088 });
8089 container.find(".star").on("mouseover", function() {
8090 let selectedStar = $(this).data("value");
8091 container.find(".star").each(function() {
8092 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8093 });
8094 });
8095 container.on("mouseout", function() {
8096 container.find(".star").each(function() {
8097 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8098 });
8099 });
8100 container.find(".star").off("click").on("click", function() {
8101 selectedStars = $(this).data("value");
8102 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8103 selectedStars = 0;
8104 }
8105 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8106 container.find(".star").each(function() {
8107 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8108 });
8109 });
8110 });
8111 </script>';
8112 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8113 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8114 $value = price($value);
8115 }
8116 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8117 } elseif ($type == 'select') { // combo list
8118 $out = '';
8119 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8120 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8121 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8122 }
8123
8124 $tmpselect = '';
8125 $nbchoice = 0;
8126
8127 foreach ($param['options'] as $keyb => $valb) {
8128 if ((string) $keyb == '') {
8129 continue;
8130 }
8131 if (strpos($valb, "|") !== false) {
8132 list($valb, $parent) = explode('|', $valb);
8133 }
8134 $nbchoice++;
8135 $tmpselect .= '<option value="'.$keyb.'"';
8136 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8137 if (!empty($parent)) {
8138 $isDependList = 1;
8139 }
8140 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8141 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8142 }
8143
8144 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8145 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8146 $out .= '<option value="0">&nbsp;</option>';
8147 }
8148 $out .= $tmpselect;
8149 $out .= '</select>';
8150 } elseif ($type == 'sellist') {
8151 $out = '';
8152 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8153 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8154 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8155 }
8156
8157 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8158 if (is_array($param['options'])) {
8159 $tmpparamoptions = array_keys($param['options']);
8160 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8161
8162 $InfoFieldList = explode(":", $paramoptions[0], 5);
8163 // 0 : tableName
8164 // 1 : label field name
8165 // 2 : key fields name (if different of rowid)
8166 // optional parameters...
8167 // 3 : key field parent (for dependent lists). How this is used ?
8168 // 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".
8169 // 5 : string category type. This replace the filter.
8170 // 6 : ids categories list separated by comma for category root. This replace the filter.
8171 // 7 : sort field
8172
8173 // If there is filter
8174 if (! empty($InfoFieldList[4])) {
8175 $pos = 0;
8176 $parenthesisopen = 0;
8177 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8178 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8179 $parenthesisopen++;
8180 }
8181 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8182 $parenthesisopen--;
8183 }
8184 $pos++;
8185 }
8186 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8187 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8188 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8189 $InfoFieldList[4] = $tmpbefore;
8190 if ($tmpafter !== '') {
8191 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8192 }
8193 //var_dump($InfoFieldList);
8194
8195 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8196 $reg = array();
8197 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8198 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8199 }
8200
8201 //var_dump($InfoFieldList);
8202 }
8203
8204 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8205
8206 $parentName = '';
8207 $parentField = '';
8208
8209 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8210
8211 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8212 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8213 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8214 } else {
8215 $keyList = $InfoFieldList[2].' as rowid';
8216 }
8217 }
8218 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8219 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8220 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8221 $keyList .= ', main.'.$parentField;
8222 } else {
8223 $keyList .= ', '.$parentField;
8224 }
8225 }
8226
8227 $filter_categorie = false;
8228 if (count($InfoFieldList) > 5) {
8229 if ($InfoFieldList[0] == 'categorie') {
8230 $filter_categorie = true;
8231 }
8232 }
8233
8234 if (!$filter_categorie) {
8235 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8236 if (!empty($fields_label)) {
8237 $keyList .= ', ';
8238 $keyList .= implode(', ', $fields_label);
8239 }
8240
8241 $sqlwhere = '';
8242 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8243 $sql .= " FROM " . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8244
8245 if (!empty($InfoFieldList[4])) {
8246 // can use SELECT request
8247 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8248 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8249 }
8250
8251 // current object id can be use into filter
8252 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8253 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8254 } else {
8255 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8256 }
8257
8258 // We have to join on extrafield table
8259 $errstr = '';
8260 if (strpos($InfoFieldList[4], 'extra') !== false) {
8261 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8262 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8263 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8264 } else {
8265 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8266 }
8267 } else {
8268 $sqlwhere .= ' WHERE 1=1';
8269 }
8270
8271 // Add Usf filter on second line
8272 /*
8273 if ($Usf) {
8274 $errorstr = '';
8275 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8276 if (!$errorstr) {
8277 $sqlwhere .= $sqlusf;
8278 } else {
8279 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8280 }
8281 }
8282 */
8283
8284 // Some tables may have field, some other not. For the moment we disable it.
8285 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8286 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8287 }
8288 $sql .= $sqlwhere;
8289
8290 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8291 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8292 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8293 } else {
8294 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8295 }
8296 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8297 // print $sql;
8298
8299 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8300 $resql = $this->db->query($sql);
8301 if ($resql) {
8302 $out .= '<option value="0">&nbsp;</option>';
8303 $num = $this->db->num_rows($resql);
8304 $i = 0;
8305 while ($i < $num) {
8306 $labeltoshow = '';
8307 $obj = $this->db->fetch_object($resql);
8308
8309 // Several field into label (eq table:code|libelle:rowid)
8310 $notrans = false;
8311 $fields_label = explode('|', $InfoFieldList[1]);
8312 if (count($fields_label) > 1) {
8313 $notrans = true;
8314 foreach ($fields_label as $field_toshow) {
8315 $labeltoshow .= $obj->$field_toshow . ' ';
8316 }
8317 } else {
8318 $labeltoshow = $obj->{$InfoFieldList[1]};
8319 }
8320 $labeltoshow = dol_trunc($labeltoshow, 45);
8321
8322 if ($value == $obj->rowid) {
8323 foreach ($fields_label as $field_toshow) {
8324 $translabel = $langs->trans($obj->$field_toshow);
8325 if ($translabel != $obj->$field_toshow) {
8326 $labeltoshow = dol_trunc($translabel) . ' ';
8327 } else {
8328 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8329 }
8330 }
8331 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8332 } else {
8333 if (!$notrans) {
8334 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8335 if ($translabel != $obj->{$InfoFieldList[1]}) {
8336 $labeltoshow = dol_trunc($translabel, 18);
8337 } else {
8338 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8339 }
8340 }
8341 if (empty($labeltoshow)) {
8342 $labeltoshow = '(not defined)';
8343 }
8344 if ($value == $obj->rowid) {
8345 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8346 }
8347
8348 if (!empty($InfoFieldList[3]) && $parentField) {
8349 $parent = $parentName . ':' . $obj->{$parentField};
8350 $isDependList = 1;
8351 }
8352
8353 $out .= '<option value="' . $obj->rowid . '"';
8354 $out .= ($value == $obj->rowid ? ' selected' : '');
8355 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8356 $out .= '>' . $labeltoshow . '</option>';
8357 }
8358
8359 $i++;
8360 }
8361 $this->db->free($resql);
8362 } else {
8363 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8364 }
8365 } else {
8366 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8367 $categcode = $InfoFieldList[5];
8368 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8369 $tmpcategory = new Categorie($this->db);
8370 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8371 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8372 }
8373
8374 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8375 $out .= '<option value="0">&nbsp;</option>';
8376 foreach ($data as $data_key => $data_value) {
8377 $out .= '<option value="' . $data_key . '"';
8378 $out .= ($value == $data_key ? ' selected' : '');
8379 $out .= '>' . $data_value . '</option>';
8380 }
8381 }
8382 }
8383 $out .= '</select>';
8384 } elseif ($type == 'checkbox') {
8385 $value_arr = explode(',', $value);
8386 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8387 } elseif ($type == 'radio') {
8388 $out = '';
8389 foreach ($param['options'] as $keyopt => $valopt) {
8390 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8391 $out .= ' value="'.$keyopt.'"';
8392 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8393 $out .= ($value == $keyopt ? 'checked' : '');
8394 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8395 }
8396 } elseif ($type == 'chkbxlst') {
8397 if (is_array($value)) {
8398 $value_arr = $value;
8399 } else {
8400 $value_arr = explode(',', $value);
8401 }
8402
8403 if (is_array($param['options'])) {
8404 $tmpparamoptions = array_keys($param['options']);
8405 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8406
8407 $InfoFieldList = explode(":", $paramoptions[0], 5);
8408 // 0 : tableName
8409 // 1 : label field name
8410 // 2 : key fields name (if different of rowid)
8411 // optional parameters...
8412 // 3 : key field parent (for dependent lists). How this is used ?
8413 // 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".
8414 // 5 : string category type. This replace the filter.
8415 // 6 : ids categories list separated by comma for category root. This replace the filter.
8416 // 7 : sort field
8417
8418 // If there is a filter
8419 if (! empty($InfoFieldList[4])) {
8420 $pos = 0;
8421 $parenthesisopen = 0;
8422 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8423 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8424 $parenthesisopen++;
8425 }
8426 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8427 $parenthesisopen--;
8428 }
8429 $pos++;
8430 }
8431 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8432 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8433 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8434 $InfoFieldList[4] = $tmpbefore;
8435 if ($tmpafter !== '') {
8436 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8437 }
8438
8439 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8440 $reg = array();
8441 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8442 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8443 }
8444
8445 //var_dump($InfoFieldList);
8446 }
8447
8448 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8449
8450 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8451
8452 $parentName = '';
8453 $parentField = '';
8454
8455 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8456
8457 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8458 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8459 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8460 } else {
8461 $keyList = $InfoFieldList[2].' as rowid';
8462 }
8463 }
8464 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8465 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8466 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8467 $keyList .= ', main.'.$parentField;
8468 } else {
8469 $keyList .= ', '.$parentField;
8470 }
8471 }
8472
8473 $filter_categorie = false;
8474 if (count($InfoFieldList) > 5) {
8475 if ($InfoFieldList[0] == 'categorie') {
8476 $filter_categorie = true;
8477 }
8478 }
8479
8480 // Common filter
8481 if (!$filter_categorie) {
8482 $fields_label = explode('|', $InfoFieldList[1]);
8483 if (is_array($fields_label)) {
8484 $keyList .= ', ';
8485 $keyList .= implode(', ', $fields_label);
8486 }
8487
8488 $sqlwhere = '';
8489 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8490 $sql .= ' FROM ' . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8491
8492 if (!empty($InfoFieldList[4])) {
8493 // can use SELECT request
8494 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8495 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8496 }
8497
8498 // current object id can be use into filter
8499 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8500 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8501 } else {
8502 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8503 }
8504
8505 // We have to join on extrafield table
8506 $errstr = '';
8507 if (strpos($InfoFieldList[4], 'extra') !== false) {
8508 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8509 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8510 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8511 } else {
8512 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8513 }
8514 } else {
8515 $sqlwhere .= ' WHERE 1=1';
8516 }
8517
8518 // Add Usf filter on second line
8519 /*
8520 if ($Usf) {
8521 $errorstr = '';
8522 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8523 if (!$errorstr) {
8524 $sqlwhere .= $sqlusf;
8525 } else {
8526 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8527 }
8528 }
8529 */
8530
8531 // Some tables may have field, some other not. For the moment we disable it.
8532 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8533 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8534 }
8535 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8536 // print $sql;
8537
8538 $sql .= $sqlwhere;
8539
8540 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8541
8542 $resql = $this->db->query($sql);
8543 if ($resql) {
8544 $num = $this->db->num_rows($resql);
8545 $i = 0;
8546
8547 $data = array();
8548
8549 while ($i < $num) {
8550 $labeltoshow = '';
8551 $obj = $this->db->fetch_object($resql);
8552
8553 $notrans = false;
8554 // Several field into label (eq table:code|libelle:rowid)
8555 $fields_label = explode('|', $InfoFieldList[1]);
8556 if (count($fields_label) > 1) {
8557 $notrans = true;
8558 foreach ($fields_label as $field_toshow) {
8559 $labeltoshow .= $obj->$field_toshow . ' ';
8560 }
8561 } else {
8562 $labeltoshow = $obj->{$InfoFieldList[1]};
8563 }
8564 $labeltoshow = dol_trunc($labeltoshow, 45);
8565
8566 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8567 foreach ($fields_label as $field_toshow) {
8568 $translabel = $langs->trans($obj->$field_toshow);
8569 if ($translabel != $obj->$field_toshow) {
8570 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8571 } else {
8572 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8573 }
8574 }
8575
8576 $data[$obj->rowid] = $labeltoshow;
8577 } else {
8578 if (!$notrans) {
8579 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8580 if ($translabel != $obj->{$InfoFieldList[1]}) {
8581 $labeltoshow = dol_trunc($translabel, 18);
8582 } else {
8583 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8584 }
8585 }
8586 if (empty($labeltoshow)) {
8587 $labeltoshow = '(not defined)';
8588 }
8589
8590 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8591 $data[$obj->rowid] = $labeltoshow;
8592 }
8593
8594 if (!empty($InfoFieldList[3]) && $parentField) {
8595 $parent = $parentName . ':' . $obj->{$parentField};
8596 $isDependList = 1;
8597 }
8598
8599 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8600 }
8601
8602 $i++;
8603 }
8604 $this->db->free($resql);
8605
8606 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8607 } else {
8608 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8609 }
8610 } else {
8611 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8612 $categcode = $InfoFieldList[5];
8613 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8614 $tmpcategory = new Categorie($this->db);
8615 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8616 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8617 }
8618
8619 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8620 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8621 }
8622 }
8623 } elseif ($type == 'link') {
8624 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8625 // Filter can contains some ':' inside.
8626 $param_list = array_keys($param['options']);
8627 $param_list_array = explode(':', $param_list[0], 4);
8628
8629 $showempty = (($required && $default != '') ? 0 : 1);
8630
8631 if (!preg_match('/search_/', $keyprefix)) {
8632 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8633 // @phan-suppress-next-line PhanTypeMismatchProperty
8634 if (!empty($this->fields[$key]['picto'])) {
8635 $morecss .= ' widthcentpercentminusxx';
8636 } else {
8637 $morecss .= ' widthcentpercentminusx';
8638 }
8639 } else {
8640 // @phan-suppress-next-line PhanTypeMismatchProperty
8641 if (!empty($this->fields[$key]['picto'])) {
8642 $morecss .= ' widthcentpercentminusx';
8643 }
8644 }
8645 }
8646
8647 // $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.
8648
8649 // $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.
8650
8651 $objectfield = $val;
8652
8653 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8654 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8655
8656 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8657 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8658 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8659 list($class, $classfile) = explode(':', $param_list[0]);
8660 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8661 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8662 } else {
8663 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8664 }
8665 $paramforthenewlink = '';
8666 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8667 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8668 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8669 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8670 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8671 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8672 $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>';
8673 }
8674 }
8675 } elseif ($type == 'password') {
8676 // If prefix is 'search_', field is used as a filter, we use a common text field.
8677 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8678 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8679 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8680 } else {
8681 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8682 }
8683 } elseif ($type == 'array') {
8684 $newval = $val;
8685 $newval['type'] = 'varchar(256)';
8686
8687 $out = '';
8688 if (!empty($value)) {
8689 foreach ($value as $option) {
8690 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8691 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8692 }
8693 }
8694 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8695
8696 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8697 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8698
8699 if (!empty($conf->use_javascript_ajax)) {
8700 $out .= '
8701 <script nonce="'.getNonce().'">
8702 $(document).ready(function() {
8703 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8704 $("'.dol_escape_js($newInput).'").insertBefore(this);
8705 });
8706
8707 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8708 $(this).parent().remove();
8709 });
8710 });
8711 </script>';
8712 }
8713 }
8714 if (!empty($hidden)) {
8715 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8716 }
8717
8718 if ($isDependList == 1) {
8719 $out .= $this->getJSListDependancies('_common');
8720 }
8721 /* Add comments
8722 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8723 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8724 */
8725
8726 // Display error message for field
8727 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8728 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8729 }
8730
8731 return $out;
8732 }
8733
8747 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8748 {
8749 global $conf, $langs, $form;
8750
8751 // 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...)
8752
8753 if (!is_object($form)) {
8754 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8755 $form = new Form($this->db);
8756 }
8757
8758 //$label = empty($val['label']) ? '' : $val['label'];
8759 $type = empty($val['type']) ? '' : $val['type'];
8760 $size = empty($val['css']) ? '' : $val['css'];
8761
8762 $reg = array();
8763
8764 // Convert var to be able to share same code than showOutputField of extrafields
8765 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8766 $type = 'varchar'; // convert varchar(xx) int varchar
8767 $size = $reg[1];
8768 } elseif (preg_match('/varchar/', $type)) {
8769 $type = 'varchar'; // convert varchar(xx) int varchar
8770 }
8771 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8772 // @phan-suppress-next-line PhanTypeMismatchProperty
8773 if (empty($this->fields[$key]['multiinput'])) {
8774 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8775 }
8776 }
8777 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8778 $type = 'link';
8779 }
8780
8781 $default = empty($val['default']) ? '' : $val['default'];
8782 $computed = empty($val['computed']) ? '' : $val['computed'];
8783 $unique = empty($val['unique']) ? '' : $val['unique'];
8784 $required = empty($val['required']) ? '' : $val['required'];
8785 $param = array();
8786 $param['options'] = array();
8787
8788 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8789 $param['options'] = $val['arrayofkeyval'];
8790 }
8791 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8792 $type = 'link';
8793 $stringforoptions = $reg[1].':'.$reg[2];
8794 // Special case: Force addition of getnomurlparam1 to -1 for users
8795 if ($reg[1] == 'User') {
8796 $stringforoptions .= ':#getnomurlparam1=-1';
8797 }
8798 $param['options'] = array($stringforoptions => $stringforoptions);
8799 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8800 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8801 $type = 'sellist';
8802 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8803 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8804 $type = 'sellist';
8805 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8806 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8807 $type = 'sellist';
8808 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8809 $param['options'] = array($reg[1] => 'N');
8810 $type = 'chkbxlst';
8811 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8812 $param['options'] = array();
8813 $type = 'stars';
8814 $size = $reg[1];
8815 }
8816
8817 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8818 $list = (empty($val['list']) ? '' : $val['list']);
8819 $help = (empty($val['help']) ? '' : $val['help']);
8820 $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)
8821
8822 if ($hidden) {
8823 return '';
8824 }
8825
8826 // If field is a computed field, value must become result of compute
8827 if ($computed) {
8828 // Make the eval of compute string
8829 //var_dump($computed);
8830 $value = dol_eval((string) $computed, 1, 0, '2');
8831 }
8832
8833 if (empty($morecss)) {
8834 if ($type == 'date') {
8835 $morecss = 'minwidth100imp';
8836 } elseif ($type == 'datetime' || $type == 'timestamp') {
8837 $morecss = 'minwidth200imp';
8838 } elseif (in_array($type, array('int', 'double', 'price'))) {
8839 $morecss = 'maxwidth75';
8840 } elseif ($type == 'url') {
8841 $morecss = 'minwidth400';
8842 } elseif ($type == 'boolean') {
8843 $morecss = '';
8844 } else {
8845 if (is_numeric($size) && round((float) $size) < 12) {
8846 $morecss = 'minwidth100';
8847 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8848 $morecss = 'minwidth200';
8849 } else {
8850 $morecss = 'minwidth400';
8851 }
8852 }
8853 }
8854
8855 // Format output value differently according to properties of field
8856 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8857 // @phan-suppress-next-line PhanTypeMismatchProperty
8858 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.
8859 $value = $this->getNomUrl(1, '', 0, '', 1);
8860 }
8861 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8862 $value = $this->getLibStatut(3);
8863 } elseif ($type == 'date') {
8864 if (!empty($value)) {
8865 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8866 } else {
8867 $value = '';
8868 }
8869 } elseif ($type == 'datetime' || $type == 'timestamp') {
8870 if (!empty($value)) {
8871 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8872 } else {
8873 $value = '';
8874 }
8875 } elseif ($type == 'duration') {
8876 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8877 if (!is_null($value) && $value !== '') {
8878 $value = convertSecondToTime((int) $value, 'allhourmin');
8879 }
8880 } elseif ($type == 'double' || $type == 'real') {
8881 if (!is_null($value) && $value !== '') {
8882 $value = price($value);
8883 }
8884 } elseif ($type == 'boolean') {
8885 $checked = '';
8886 if (!empty($value)) {
8887 $checked = ' checked ';
8888 }
8889 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8890 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8891 } else {
8892 $value = yn($value ? 1 : 0);
8893 }
8894 } elseif ($type == 'mail' || $type == 'email') {
8895 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8896 } elseif ($type == 'url') {
8897 $value = dol_print_url((string) $value, '_blank', 32, 1);
8898 } elseif ($type == 'phone') {
8899 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8900 } elseif ($type == 'ip') {
8901 $value = dol_print_ip((string) $value, 0);
8902 } elseif ($type == 'stars') {
8903 $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 : '').'>';
8904 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8905 $i = 1;
8906 while ($i <= $size) {
8907 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8908 $i++;
8909 }
8910 $value .= '</div>';
8911 $value .= '<script>
8912 $(document).ready(function() { /* commonobject.class.php 2 */
8913 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8914 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8915 container.find(".star").each(function() {
8916 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8917 });
8918 container.find(".star").on("mouseover", function() {
8919 let selectedStar = $(this).data("value");
8920 container.find(".star").each(function() {
8921 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8922 });
8923 });
8924 container.on("mouseout", function() {
8925 container.find(".star").each(function() {
8926 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8927 });
8928 });
8929 container.find(".star").off("click").on("click", function() {
8930 selectedStars = $(this).data("value");
8931 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8932 selectedStars = 0;
8933 }
8934 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8935 container.find(".star").each(function() {
8936 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8937 });
8938 $.ajax({
8939 url: "ajax/'.$this->element.'.php",
8940 method: "POST",
8941 data: {
8942 objectId: "'.$this->id.'",
8943 field: "'.$keyprefix.$key.$keysuffix.'",
8944 value: selectedStars,
8945 token: "'.newToken().'"
8946 },
8947 success: function(response) {
8948 var res = JSON.parse(response);
8949 console[res.status === "success" ? "log" : "error"](res.message);
8950 },
8951 error: function(xhr, status, error) {
8952 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8953 }
8954 });
8955 });
8956 });
8957 </script>';
8958 } elseif ($type == 'price') {
8959 if (!is_null($value) && $value !== '') {
8960 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8961 }
8962 } elseif ($type == 'select') {
8963 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8964 if (strpos($value, "|") !== false) {
8965 $value = $langs->trans(explode('|', $value)[0]);
8966 } elseif (! is_numeric($value)) {
8967 $value = $langs->trans($value);
8968 }
8969 } elseif ($type == 'sellist') {
8970 $param_list = array_keys($param['options']);
8971 $InfoFieldList = explode(":", $param_list[0]);
8972
8973 $selectkey = "rowid";
8974 $keyList = 'rowid';
8975
8976 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8977 $selectkey = $InfoFieldList[2];
8978 $keyList = $InfoFieldList[2].' as rowid';
8979 }
8980
8981 $fields_label = explode('|', $InfoFieldList[1]);
8982 if (is_array($fields_label)) {
8983 $keyList .= ', ';
8984 $keyList .= implode(', ', $fields_label);
8985 }
8986
8987 $filter_categorie = false;
8988 if (count($InfoFieldList) > 5) {
8989 if ($InfoFieldList[0] == 'categorie') {
8990 $filter_categorie = true;
8991 }
8992 }
8993
8994 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
8995 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
8996 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8997 $sql .= ' as main';
8998 }
8999 if ($selectkey == 'rowid' && empty($value)) {
9000 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = 0";
9001 } elseif ($selectkey == 'rowid') {
9002 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = ".((int) $value);
9003 } else {
9004 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = '".$this->db->escape((string) $value)."'";
9005 }
9006
9007 //$sql.= ' AND entity = '.$conf->entity;
9008
9009 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
9010 $resql = $this->db->query($sql);
9011 if ($resql) {
9012 if (!$filter_categorie) {
9013 $value = ''; // value was used, so now we reset it to use it to build final output
9014 $numrows = $this->db->num_rows($resql);
9015 if ($numrows) {
9016 $obj = $this->db->fetch_object($resql);
9017
9018 // Several field into label (eq table:code|libelle:rowid)
9019 $fields_label = explode('|', $InfoFieldList[1]);
9020
9021 if (is_array($fields_label) && count($fields_label) > 1) {
9022 foreach ($fields_label as $field_toshow) {
9023 $translabel = '';
9024 if (!empty($obj->$field_toshow)) {
9025 $translabel = $langs->trans($obj->$field_toshow);
9026 }
9027 if ($translabel != $field_toshow) {
9028 $value .= dol_trunc($translabel, 18) . ' ';
9029 } else {
9030 $value .= $obj->$field_toshow . ' ';
9031 }
9032 }
9033 } else {
9034 $translabel = '';
9035 if (!empty($obj->{$InfoFieldList[1]})) {
9036 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9037 }
9038 if ($translabel != $obj->{$InfoFieldList[1]}) {
9039 $value = dol_trunc($translabel, 18);
9040 } else {
9041 $value = $obj->{$InfoFieldList[1]};
9042 }
9043 }
9044 }
9045 } else {
9046 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9047
9048 $toprint = array();
9049 $obj = $this->db->fetch_object($resql);
9050 $c = new Categorie($this->db);
9051 $c->fetch($obj->rowid);
9052 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9053 foreach ($ways as $way) {
9054 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9055 }
9056 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9057 }
9058 } else {
9059 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9060 }
9061 } elseif ($type == 'radio') {
9062 $value = $param['options'][(string) $value];
9063 } elseif ($type == 'checkbox') {
9064 $value_arr = explode(',', (string) $value);
9065 $value = '';
9066 if (is_array($value_arr) && count($value_arr) > 0) {
9067 $toprint = array();
9068 foreach ($value_arr as $keyval => $valueval) {
9069 if (!empty($valueval)) {
9070 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9071 }
9072 }
9073 if (!empty($toprint)) {
9074 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9075 }
9076 }
9077 } elseif ($type == 'chkbxlst') {
9078 $value_arr = (isset($value) ? explode(',', $value) : array());
9079
9080 $param_list = array_keys($param['options']);
9081 $InfoFieldList = explode(":", $param_list[0]);
9082
9083 $selectkey = "rowid";
9084 $keyList = 'rowid';
9085
9086 if (count($InfoFieldList) >= 3) {
9087 $selectkey = $InfoFieldList[2];
9088 $keyList = $InfoFieldList[2].' as rowid';
9089 }
9090
9091 $fields_label = explode('|', $InfoFieldList[1]);
9092 if (is_array($fields_label)) {
9093 $keyList .= ', ';
9094 $keyList .= implode(', ', $fields_label);
9095 }
9096
9097 $filter_categorie = false;
9098 if (count($InfoFieldList) > 5) {
9099 if ($InfoFieldList[0] == 'categorie') {
9100 $filter_categorie = true;
9101 }
9102 }
9103
9104 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9105 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9106 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9107 $sql .= ' as main';
9108 }
9109 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9110 // $sql.= ' AND entity = '.$conf->entity;
9111
9112 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9113 $resql = $this->db->query($sql);
9114 if ($resql) {
9115 if (!$filter_categorie) {
9116 $value = ''; // value was used, so now we reset it to use it to build final output
9117 $toprint = array();
9118 while ($obj = $this->db->fetch_object($resql)) {
9119 // Several field into label (eq table:code|libelle:rowid)
9120 $fields_label = explode('|', $InfoFieldList[1]);
9121 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9122 if (is_array($fields_label) && count($fields_label) > 1) {
9123 foreach ($fields_label as $field_toshow) {
9124 $translabel = '';
9125 if (!empty($obj->$field_toshow)) {
9126 $translabel = $langs->trans($obj->$field_toshow);
9127 }
9128 if ($translabel != $field_toshow) {
9129 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9130 } else {
9131 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9132 }
9133 }
9134 } else {
9135 $translabel = '';
9136 if (!empty($obj->{$InfoFieldList[1]})) {
9137 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9138 }
9139 if ($translabel != $obj->{$InfoFieldList[1]}) {
9140 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9141 } else {
9142 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9143 }
9144 }
9145 }
9146 }
9147 } else {
9148 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9149
9150 $toprint = array();
9151 while ($obj = $this->db->fetch_object($resql)) {
9152 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9153 $c = new Categorie($this->db);
9154 $c->fetch($obj->rowid);
9155 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9156 foreach ($ways as $way) {
9157 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9158 }
9159 }
9160 }
9161 }
9162 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9163 } else {
9164 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9165 }
9166 } elseif ($type == 'link') {
9167 $out = '';
9168
9169 // only if something to display (perf)
9170 if ($value) {
9171 $param_list = array_keys($param['options']);
9172 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9173 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9174
9175 $InfoFieldList = explode(":", $param_list[0]);
9176
9177 $classname = $InfoFieldList[0];
9178 $classpath = $InfoFieldList[1];
9179
9180 // Set $getnomurlparam1 et getnomurlparam2
9181 $getnomurlparam = 3;
9182 $getnomurlparam2 = '';
9183 $regtmp = array();
9184 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9185 $getnomurlparam = $regtmp[1];
9186 }
9187 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9188 $getnomurlparam2 = $regtmp[1];
9189 }
9190
9191 if (!empty($classpath)) {
9192 dol_include_once($InfoFieldList[1]);
9193
9194 if ($classname && !class_exists($classname)) {
9195 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9196 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9197 $element_prop = getElementProperties($classname);
9198 if ($element_prop) {
9199 $classname = $element_prop['classname'];
9200 }
9201 }
9202
9203
9204 if ($classname && class_exists($classname)) {
9205 $object = new $classname($this->db);
9206 '@phan-var-force CommonObject $object';
9207 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9208 '@phan-var-force Product $object';
9209 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9210 } else {
9211 $result = $object->fetch($value);
9212 }
9213 if ($result > 0) {
9214 if ($object->element === 'product') {
9215 '@phan-var-force Product $object';
9216 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9217 if (isset($val['get_name_url_params'])) {
9218 $get_name_url_params = explode(':', $val['get_name_url_params']);
9219 if (!empty($get_name_url_params)) {
9220 $param_num_max = count($get_name_url_param_arr) - 1;
9221 foreach ($get_name_url_params as $param_num => $param_value) {
9222 if ($param_num > $param_num_max) {
9223 break;
9224 }
9225 $get_name_url_param_arr[$param_num] = $param_value;
9226 }
9227 }
9228 }
9229
9233 $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]);
9234 } else {
9235 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9236 }
9237 } else {
9238 $value = '';
9239 }
9240 }
9241 } else {
9242 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9243 return 'Error bad setup of extrafield';
9244 }
9245 } else {
9246 $value = '';
9247 }
9248 } elseif ($type == 'password') {
9249 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9250 //$value = preg_replace('/./i', '*', $value);
9251 } elseif ($type == 'array') {
9252 if (is_array($value)) {
9253 $value = implode('<br>', $value);
9254 } else {
9255 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9256 return 'Error unexpected result from code evaluation';
9257 }
9258 } else { // text|html|varchar
9259 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9260 $value = str_replace(',', "\n", $value);
9261 }
9262 $value = dol_htmlentitiesbr((string) $value);
9263 }
9264
9265 //print $type.'-'.$size.'-'.$value;
9266 $out = $value;
9267
9268 return is_null($out) ? '' : $out;
9269 }
9270
9277 public function clearFieldError($fieldKey)
9278 {
9279 $this->error = '';
9280 unset($this->validateFieldsErrors[$fieldKey]);
9281 }
9282
9290 public function setFieldError($fieldKey, $msg = '')
9291 {
9292 global $langs;
9293 if (empty($msg)) {
9294 $msg = $langs->trans("UnknownError");
9295 }
9296
9297 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9298 }
9299
9306 public function getFieldError($fieldKey)
9307 {
9308 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9309 return $this->validateFieldsErrors[$fieldKey];
9310 }
9311 return '';
9312 }
9313
9322 public function validateField($fields, $fieldKey, $fieldValue)
9323 {
9324 global $langs;
9325
9326 if (!class_exists('Validate')) {
9327 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9328 }
9329
9330 $this->clearFieldError($fieldKey);
9331
9332 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9333 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9334 return false;
9335 }
9336
9337 $val = $fields[$fieldKey];
9338
9339 $param = array();
9340 $param['options'] = array();
9341 $type = $val['type'];
9342
9343 $required = false;
9344 if (isset($val['notnull']) && $val['notnull'] === 1) {
9345 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9346 $required = true;
9347 }
9348
9349 $maxSize = 0;
9350 $minSize = 0;
9351
9352 //
9353 // PREPARE Elements
9354 //
9355 $reg = array();
9356
9357 // Convert var to be able to share same code than showOutputField of extrafields
9358 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9359 $type = 'varchar'; // convert varchar(xx) int varchar
9360 $maxSize = (int) $reg[1];
9361 } elseif (preg_match('/varchar/', $type)) {
9362 $type = 'varchar'; // convert varchar(xx) int varchar
9363 }
9364
9365 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9366 $type = 'select';
9367 }
9368
9369 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9370 $type = 'link';
9371 }
9372
9373 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9374 $param['options'] = $val['arrayofkeyval'];
9375 }
9376
9377 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9378 $type = 'link';
9379 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9380 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9381 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9382 $type = 'sellist';
9383 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9384 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9385 $type = 'sellist';
9386 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9387 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9388 $type = 'sellist';
9389 }
9390
9391 //
9392 // TEST Value
9393 //
9394
9395 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9396 $validate = new Validate($this->db, $langs);
9397
9398
9399 // little trick : to perform tests with good performances sort tests by quick to low
9400
9401 //
9402 // COMMON TESTS
9403 //
9404
9405 // Required test and empty value
9406 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9407 $this->setFieldError($fieldKey, $validate->error);
9408 return false;
9409 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9410 // if no value sent and the field is not mandatory, no need to perform tests
9411 return true;
9412 }
9413
9414 // MAX Size test
9415 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9416 $this->setFieldError($fieldKey, $validate->error);
9417 return false;
9418 }
9419
9420 // MIN Size test
9421 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9422 $this->setFieldError($fieldKey, $validate->error);
9423 return false;
9424 }
9425
9426 //
9427 // TESTS for TYPE
9428 //
9429
9430 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9431 if (!$validate->isTimestamp($fieldValue)) {
9432 $this->setFieldError($fieldKey, $validate->error);
9433 return false;
9434 } else {
9435 return true;
9436 }
9437 } elseif ($type == 'duration') {
9438 if (!$validate->isDuration($fieldValue)) {
9439 $this->setFieldError($fieldKey, $validate->error);
9440 return false;
9441 } else {
9442 return true;
9443 }
9444 } elseif (in_array($type, array('double', 'real', 'price'))) {
9445 // is numeric
9446 if (!$validate->isNumeric($fieldValue)) {
9447 $this->setFieldError($fieldKey, $validate->error);
9448 return false;
9449 } else {
9450 return true;
9451 }
9452 } elseif ($type == 'boolean') {
9453 if (!$validate->isBool($fieldValue)) {
9454 $this->setFieldError($fieldKey, $validate->error);
9455 return false;
9456 } else {
9457 return true;
9458 }
9459 } elseif ($type == 'mail' || $type == 'email') {
9460 if (!$validate->isEmail($fieldValue)) {
9461 $this->setFieldError($fieldKey, $validate->error);
9462 return false;
9463 }
9464 } elseif ($type == 'url') {
9465 if (!$validate->isUrl($fieldValue)) {
9466 $this->setFieldError($fieldKey, $validate->error);
9467 return false;
9468 } else {
9469 return true;
9470 }
9471 } elseif ($type == 'phone') {
9472 if (!$validate->isPhone($fieldValue)) {
9473 $this->setFieldError($fieldKey, $validate->error);
9474 return false;
9475 } else {
9476 return true;
9477 }
9478 } elseif ($type == 'select' || $type == 'radio') {
9479 if (!isset($param['options'][$fieldValue])) {
9480 $this->error = $langs->trans('RequireValidValue');
9481 return false;
9482 } else {
9483 return true;
9484 }
9485 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9486 $param_list = array_keys($param['options']);
9487 $InfoFieldList = explode(":", $param_list[0]);
9488 $value_arr = explode(',', $fieldValue);
9489
9490 $selectkey = "rowid";
9491 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9492 $selectkey = $InfoFieldList[2];
9493 }
9494
9495 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9496 $this->setFieldError($fieldKey, $validate->error);
9497 return false;
9498 } else {
9499 return true;
9500 }
9501 } elseif ($type == 'link') {
9502 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9503 $InfoFieldList = explode(":", $param_list[0]);
9504 $classname = $InfoFieldList[0];
9505 $classpath = $InfoFieldList[1];
9506 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9507 $lastIsFetchableError = $validate->error;
9508
9509 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9510 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9511 return true;
9512 }
9513
9514 $this->setFieldError($fieldKey, $lastIsFetchableError);
9515 return false;
9516 } else {
9517 return true;
9518 }
9519 }
9520
9521 // if no test failed all is ok
9522 return true;
9523 }
9524
9538 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9539 {
9540 global $db, $conf, $langs, $action, $form, $hookmanager;
9541 global $objectoffield;
9542
9543 if (!is_object($form)) {
9544 $form = new Form($db);
9545 }
9546 if (!is_object($extrafields)) {
9547 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9548 return 'Bad parameter extrafields for showOptionals';
9549 }
9550 if (!is_array($extrafields->attributes[$this->table_element])) {
9551 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9552 }
9553
9554 // Ensure $objectoffield is available for dol_eval visibility formulas.
9555 $objectoffield = $this;
9556
9557 $out = '';
9558
9559 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9560 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9561 $hookResPrint = $hookmanager->resPrint;
9562
9563 if (empty($reshook)) {
9564 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) {
9565 $out .= "\n";
9566 $out .= '<!-- commonobject:showOptionals --> ';
9567 $out .= "\n";
9568
9569 $nbofextrafieldsshown = 0;
9570 $e = 0; // var to manage the modulo (odd/even)
9571
9572 $lastseparatorkeyfound = '';
9573 $extrafields_collapse_num = '';
9574 $extrafields_collapse_num_old = '';
9575 $i = 0;
9576
9577 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9578 $i++;
9579
9580 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9581 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9582 continue;
9583 }
9584
9585 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9586 $enabled = 1;
9587 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9588 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9589 }
9590 if (empty($enabled)) {
9591 continue;
9592 }
9593
9594 $visibility = 1;
9595 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9596 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9597 }
9598
9599 $perms = 1;
9600 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9601 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9602 }
9603
9604 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9605 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9606 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9607 // 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
9608 $ef_name = 'options_' . $key;
9609 $ef_value = $this->array_options[$ef_name] ?? '';
9610 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . $ef_value . '" />' . "\n";
9611 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9612 } elseif ($mode == 'view' && empty($visibility)) {
9613 continue;
9614 }
9615 if (empty($perms)) {
9616 continue;
9617 }
9618
9619 // Load language if required
9620 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9621 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9622 }
9623
9624 $colspan = 0;
9625 $value = null;
9626 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9627 if (array_key_exists('cols', $params)) {
9628 $colspan = $params['cols'];
9629 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9630 $reg = array();
9631 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9632 $colspan = $reg[1];
9633 } else {
9634 $colspan = $params['colspan'];
9635 }
9636 }
9637 }
9638 $colspan = intval($colspan);
9639
9640 switch ($mode) {
9641 case "view":
9642 $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
9643 break;
9644 case "create":
9645 case "edit":
9646 // We get the value of property found with GETPOST so it takes into account:
9647 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9648 $check = 'alphanohtml';
9649 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9650 $check = 'restricthtml';
9651 }
9652 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9653 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9654 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9655 if (is_array($getposttemp)) {
9656 // $getposttemp is an array but following code expects a comma separated string
9657 $value = implode(",", $getposttemp);
9658 } else {
9659 $value = $getposttemp;
9660 }
9661 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9662 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9663 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('varchar', 'char'))) {
9664 $value = (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] !== '') ? $this->array_options["options_".$key] : '';
9665 } else {
9666 $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.
9667 }
9668 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9669 break;
9670 }
9671
9672 $nbofextrafieldsshown++;
9673
9674 // Output value of the current field
9675 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9676 $extrafields_collapse_num = $key;
9677 /*
9678 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9679 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9680 $extrafield_param_list = array_keys($extrafield_param['options']);
9681
9682 if (count($extrafield_param_list) > 0) {
9683 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9684
9685 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9686 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9687 $extrafields_collapse_num = $key;
9688 }
9689 }
9690 }
9691 */
9692
9693 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9694 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9695
9696 $lastseparatorkeyfound = $key;
9697 } else {
9698 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9699
9700 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9701 $csstyle = '';
9702 if (is_array($params) && count($params) > 0) {
9703 if (array_key_exists('class', $params)) {
9704 $class .= $params['class'].' ';
9705 }
9706 if (array_key_exists('style', $params)) {
9707 $csstyle = $params['style'];
9708 }
9709 }
9710
9711 // add html5 elements
9712 $domData = ' data-element="extrafield"';
9713 $domData .= ' data-targetelement="'.$this->element.'"';
9714 $domData .= ' data-targetid="'.$this->id.'"';
9715
9716 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9717 if ($display_type == 'card') {
9718 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9719 $colspan = 0;
9720 }
9721
9722 if ($action == 'selectlines') {
9723 $colspan++;
9724 }
9725 }
9726
9727 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9728 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9729 $force_values_on_change_company = (
9730 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9731 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9732 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9733 );
9734 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9735 $value = $this->array_options['options_'.$key] ?? $value;
9736 }
9737
9738 // Convert date into timestamp format (value in memory must be a timestamp)
9739 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9740 $datenotinstring = null;
9741 if (array_key_exists('options_'.$key, $this->array_options)) {
9742 $datenotinstring = $this->array_options['options_'.$key];
9743 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9744 $datenotinstring = $this->db->jdate($datenotinstring);
9745 }
9746 }
9747 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9748 $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;
9749 }
9750 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9751 $datenotinstring = null;
9752 if (array_key_exists('options_'.$key, $this->array_options)) {
9753 $datenotinstring = $this->array_options['options_'.$key];
9754 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9755 $datenotinstring = $this->db->jdate($datenotinstring);
9756 }
9757 }
9758 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9759 $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;
9760 }
9761 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9762 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9763 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9764 $value = price2num($value);
9765 } elseif (isset($this->array_options['options_'.$key])) {
9766 $value = $this->array_options['options_'.$key];
9767 }
9768 }
9769
9770 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9771 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9772 if ($action == 'create' || $mode == 'create') {
9773 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9774 }
9775 }
9776
9777 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9778 if ($action == 'create') {
9779 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9780 }
9781 }
9782
9783
9784 $labeltoshow = $langs->trans($label);
9785 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9786 if ($display_type == 'card') {
9787 $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.' >';
9788 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9789 $out .= '<td></td>';
9790 }
9791 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9792 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9793 $out .= ' tdtop';
9794 }
9795 } elseif ($display_type == 'line') {
9796 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9797 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9798 }
9799 //$out .= "titlefield";
9800 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9801 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9802 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9803 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9804 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9805 $out .= ' fieldrequired';
9806 }
9807 }
9808 $out .= '">';
9809 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9810 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9811 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9812 } else {
9813 $out .= $labeltoshow;
9814 }
9815 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9816 $out .= '&nbsp;<span style="color: red">*</span>';
9817 }
9818 } else {
9819 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9820 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9821 } else {
9822 $out .= $labeltoshow;
9823 }
9824 }
9825
9826 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9827
9828 // Second column
9829 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9830 if ($display_type == 'card') {
9831 // 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
9832 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9833 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9834 $out .= '>';
9835 } elseif ($display_type == 'line') {
9836 $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].'">';
9837 }
9838
9839 switch ($mode) {
9840 case "view":
9841 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9842 break;
9843 case "create":
9844 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9845 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9846 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9847 }
9848 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9849 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9850 break;
9851 case "edit":
9852 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9853 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9854 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9855 }
9856 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9857 break;
9858 }
9859
9860 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9861 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9862 $e++;
9863 }
9864 }
9865 $out .= "\n";
9866 // Add code to manage list depending on others
9867 if (!empty($conf->use_javascript_ajax)) {
9868 $out .= $this->getJSListDependancies();
9869 }
9870
9871 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9872
9873 if (empty($nbofextrafieldsshown)) {
9874 $out = '';
9875 }
9876 }
9877 }
9878
9879 $out .= $hookResPrint;
9880
9881 return $out;
9882 }
9883
9888 public function getJSListDependancies($type = '_extra')
9889 {
9890 $out = '
9891 <script nonce="'.getNonce().'">
9892 jQuery(document).ready(function() {
9893 function showOptions'.$type.'(child_list, parent_list, orig_select)
9894 {
9895 var val = $("select[name=\""+parent_list+"\"]").val();
9896 var parentVal = parent_list + ":" + val;
9897 if(typeof val == "string"){
9898 if(val != "") {
9899 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9900 $("select[name=\""+child_list+"\"] option[parent]").remove();
9901 $("select[name=\""+child_list+"\"]").append(options);
9902 } else {
9903 var options = orig_select.find("option[parent]").clone();
9904 $("select[name=\""+child_list+"\"] option[parent]").remove();
9905 $("select[name=\""+child_list+"\"]").append(options);
9906 }
9907 } else if(val > 0) {
9908 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9909 $("select[name=\""+child_list+"\"] option[parent]").remove();
9910 $("select[name=\""+child_list+"\"]").append(options);
9911 } else {
9912 var options = orig_select.find("option[parent]").clone();
9913 $("select[name=\""+child_list+"\"] option[parent]").remove();
9914 $("select[name=\""+child_list+"\"]").append(options);
9915 }
9916 }
9917 function setListDependencies'.$type.'() {
9918 jQuery("select option[parent]").parent().each(function() {
9919 var orig_select = {};
9920 var child_list = $(this).attr("name");
9921 orig_select[child_list] = $(this).clone();
9922 var parent = $(this).find("option[parent]:first").attr("parent");
9923 var infos = parent.split(":");
9924 var parent_list = infos[0];
9925
9926 //Hide daughters lists
9927 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9928 $("#"+child_list).hide();
9929 //Show mother lists
9930 } else if ($("#"+parent_list).val() != 0){
9931 $("#"+parent_list).show();
9932 }
9933 //Show the child list if the parent list value is selected
9934 $("select[name=\""+parent_list+"\"]").click(function() {
9935 if ($(this).val() != 0){
9936 $("#"+child_list).show()
9937 }
9938 });
9939
9940 //When we change parent list
9941 $("select[name=\""+parent_list+"\"]").change(function() {
9942 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9943 //Select the value 0 on child list after a change on the parent list
9944 $("#"+child_list).val(0).trigger("change");
9945 //Hide child lists if the parent value is set to 0
9946 if ($(this).val() == 0){
9947 $("#"+child_list).hide();
9948 }
9949 });
9950 });
9951 }
9952
9953 setListDependencies'.$type.'();
9954 });
9955 </script>'."\n";
9956 return $out;
9957 }
9958
9964 public function getRights()
9965 {
9966 global $user;
9967
9968 $module = empty($this->module) ? '' : $this->module;
9969 $element = $this->element;
9970
9971 if ($element == 'facturerec') {
9972 $element = 'facture';
9973 } elseif ($element == 'invoice_supplier_rec') {
9974 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9975 } elseif ($module && $user->hasRight($module, $element)) {
9976 // for modules built with ModuleBuilder
9977 return $user->hasRight($module, $element);
9978 }
9979
9980 return $user->rights->$element;
9981 }
9982
9995 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9996 {
9997 global $hookmanager;
9998
9999 $parameters = array(
10000 'origin_id' => $origin_id,
10001 'dest_id' => $dest_id,
10002 'tables' => $tables,
10003 );
10004 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
10005 if ($reshook) {
10006 return true; // replacement code
10007 } elseif ($reshook < 0) {
10008 return $ignoreerrors === 1; // failure
10009 } // reshook = 0 => execute normal code
10010
10011 foreach ($tables as $table) {
10012 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
10013
10014 if (!$dbs->query($sql)) {
10015 if ($ignoreerrors) {
10016 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.
10017 }
10018 //$this->errors = $db->lasterror();
10019 return false;
10020 }
10021 }
10022
10023 return true;
10024 }
10025
10038 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10039 {
10040 foreach ($tables as $table) {
10041 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
10042
10043 if (!$dbs->query($sql)) {
10044 if ($ignoreerrors) {
10045 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.
10046 }
10047 //$this->errors = $db->lasterror();
10048 return false;
10049 }
10050 }
10051
10052 return true;
10053 }
10054
10067 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
10068 {
10069 global $conf;
10070
10071 $buyPrice = 0;
10072
10073 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10074 // When ForceBuyingPriceIfNull is set
10075 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10076 } else {
10077 // Get cost price for margin calculation
10078 if (!empty($fk_product) && $fk_product > 0) {
10079 $result = 0;
10080 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10081 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10082 $product = new Product($this->db);
10083 $result = $product->fetch($fk_product);
10084 if ($result <= 0) {
10085 $this->errors[] = 'ErrorProductIdDoesNotExists';
10086 return -1;
10087 }
10088 if ($product->cost_price > 0) {
10089 $buyPrice = $product->cost_price;
10090 } elseif ($product->pmp > 0) {
10091 $buyPrice = $product->pmp;
10092 }
10093 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10094 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10095 $product = new Product($this->db);
10096 $result = $product->fetch($fk_product);
10097 if ($result <= 0) {
10098 $this->errors[] = 'ErrorProductIdDoesNotExists';
10099 return -1;
10100 }
10101 if ($product->pmp > 0) {
10102 $buyPrice = $product->pmp;
10103 }
10104 }
10105
10106 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10107 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10108 $productFournisseur = new ProductFournisseur($this->db);
10109 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
10110 $buyPrice = $productFournisseur->fourn_unitprice;
10111 } elseif ($result < 0) {
10112 $this->errors[] = $productFournisseur->error;
10113 return -2;
10114 }
10115 }
10116 }
10117 }
10118
10119 return (float) $buyPrice;
10120 }
10121
10129 public function getDataToShowPhoto($modulepart, $imagesize)
10130 {
10131 // See getDataToShowPhoto() implemented by Product for example.
10132 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10133 }
10134
10135
10136 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10156 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')
10157 {
10158 // phpcs:enable
10159 global $user, $langs;
10160
10161 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10162 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10163 include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
10164
10165 $sortfield = 'position_name';
10166 $sortorder = 'asc';
10167
10168 $dir = $sdir.'/';
10169 $pdir = '/';
10170
10171 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10172 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10173
10174 // For backward compatibility
10175 if ($modulepart == 'product') {
10176 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10177 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10178 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10179 }
10180 }
10181 if ($modulepart == 'category') {
10182 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10183 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10184 }
10185
10186 // Defined relative dir to DOL_DATA_ROOT
10187 $relativedir = '';
10188 if ($dir) {
10189 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10190 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10191 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10192 }
10193
10194 $dirthumb = $dir.'thumbs/';
10195 $pdirthumb = $pdir.'thumbs/';
10196
10197 $return = '<!-- Photo -->'."\n";
10198 $nbphoto = 0;
10199
10200 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10201
10202 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10203 {
10204 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10205 $filearray=array_merge($filearray, $filearrayold);
10206 }*/
10207
10208 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10209 '@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';
10210
10211 $useLinkPathPhoto = false;
10212
10213 if (getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO')) {
10214 $link = new Link($this->db);
10215 $links = array();
10216 $link->fetchAll($links, 'product', $this->id);
10217
10218 if (count($links) > 0) {
10219 $useLinkPathPhoto = true;
10220 }
10221
10222 if ($useLinkPathPhoto) {
10223 // Start table or div based on nbbyrow
10224 if ($nbbyrow > 0) {
10225 $return .= '<table class="valigntop center centpercent" style="border:0; padding:2px; border-spacing:2px; border-collapse: separate;">';
10226 }
10227
10228 $i = 0;
10229 foreach ($links as $link) {
10230 if (!empty($link->url) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $link->url)) {
10231 $url = dol_escape_htmltag($link->url);
10232 $i++;
10233 $nbphoto++;
10234
10235 if ($nbbyrow > 0 && ($i % $nbbyrow == 1)) {
10236 $return .= '<tr class="center valignmiddle">';
10237 }
10238
10239 if ($nbbyrow > 0) {
10240 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%" class="photo">';
10241 } else {
10242 $return .= '<div class="inline-block">';
10243 }
10244
10245 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.$url.'" title="External image">';
10246
10247 if ($nbbyrow > 0) {
10248 $return .= '</td>';
10249 if ($i % $nbbyrow == 0) {
10250 $return .= '</tr>';
10251 }
10252 } else {
10253 $return .= '</div>';
10254 }
10255
10256 if ($nbmax && $nbphoto >= $nbmax) {
10257 break;
10258 }
10259 }
10260 }
10261
10262 if ($nbbyrow > 0) {
10263 while ($i % $nbbyrow) {
10264 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10265 $i++;
10266 }
10267 if ($nbphoto) {
10268 $return .= '</table>';
10269 }
10270 }
10271 }
10272 }
10273
10274 if (count($filearray)) {
10275 if ($sortfield && $sortorder) {
10276 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10277 }
10278
10279 foreach ($filearray as $key => $val) {
10280 $photo = '';
10281 $file = $val['name'];
10282
10283 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10284 if (image_format_supported($file) >= 0) {
10285 $nbphoto++;
10286 $photo = $file;
10287 $viewfilename = $file;
10288
10289 if ($size == 1 || $size == 'small') { // Format vignette
10290 // Find name of thumb file
10291 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10292 if (!dol_is_file($dirthumb.$photo_vignette)) {
10293 // The thumb does not exists, so we will use the original file
10294 $dirthumb = $dir;
10295 $pdirthumb = $pdir;
10296 $photo_vignette = basename($file);
10297 }
10298
10299 // Get filesize of original file
10300 $imgarray = dol_getImageSize($dir.$photo);
10301
10302 if ($nbbyrow > 0) {
10303 if ($nbphoto == 1) {
10304 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10305 }
10306
10307 if ($nbphoto % $nbbyrow == 1) {
10308 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10309 }
10310 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10311 } elseif ($nbbyrow < 0) {
10312 $return .= '<div class="inline-block">'."\n";
10313 }
10314
10315 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10316 if (empty($nolink)) {
10317 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10318 if ($urladvanced) {
10319 $return .= '<a href="'.$urladvanced.'">';
10320 } else {
10321 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10322 }
10323 }
10324
10325 // Show image (width height=$maxHeight)
10326 // If thumb file available and image source is too large, we use the thumb, otherwise we use the original photo
10327 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10328 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10329 if ($overwritetitle) {
10330 if (is_numeric($overwritetitle)) {
10331 $alt = '';
10332 } else {
10333 $alt = $overwritetitle;
10334 }
10335 }
10336 if (empty($cache) && !empty($val['label'])) {
10337 // label is md5 of file
10338 // use it in url to say we want to cache this version of the file
10339 $cache = $val['label'];
10340 }
10341 if ($usesharelink) {
10342 if (array_key_exists('share', $val) && $val['share']) {
10343 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10344 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10345 $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).'">';
10346 } else {
10347 $return .= '<!-- Show original file -->';
10348 $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).'">';
10349 }
10350 } else {
10351 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10352 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10353 }
10354 } else {
10355 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10356 $return .= '<!-- Show thumb -->';
10357 $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).'">';
10358 } else {
10359 $return .= '<!-- Show original file -->';
10360 $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).'">';
10361 }
10362 }
10363
10364 if (empty($nolink)) {
10365 $return .= '</a>';
10366 }
10367
10368 if ($showfilename) {
10369 $return .= '<br>'.$viewfilename;
10370 }
10371 if ($showaction) {
10372 $return .= '<br>';
10373 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10374 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10375 $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>';
10376 }
10377 // Special case for product
10378 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10379 // Link to resize
10380 $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; ';
10381
10382 // Link to delete
10383 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10384 $return .= img_delete().'</a>';
10385 }
10386 }
10387 $return .= "\n";
10388
10389 if ($nbbyrow > 0) {
10390 $return .= '</td>';
10391 if (($nbphoto % $nbbyrow) == 0) {
10392 $return .= '</tr>';
10393 }
10394 } elseif ($nbbyrow < 0) {
10395 $return .= '</div>'."\n";
10396 }
10397 }
10398
10399 if (empty($size)) { // Format origine
10400 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10401
10402 if ($showfilename) {
10403 $return .= '<br>'.$viewfilename;
10404 }
10405 if ($showaction) {
10406 // Special case for product
10407 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10408 // Link to resize
10409 $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; ';
10410
10411 // Link to delete
10412 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10413 $return .= img_delete().'</a>';
10414 }
10415 }
10416 }
10417
10418 // On continue ou on arrete de boucler ?
10419 if ($nbmax && $nbphoto >= $nbmax) {
10420 break;
10421 }
10422 }
10423 }
10424
10425 if ($size == 1 || $size == 'small') {
10426 if ($nbbyrow > 0) {
10427 // Ferme tableau
10428 while ($nbphoto % $nbbyrow) {
10429 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10430 $nbphoto++;
10431 }
10432
10433 if ($nbphoto) {
10434 $return .= '</table>';
10435 }
10436 }
10437 }
10438 }
10439
10440 $this->nbphoto = $nbphoto;
10441
10442 return $return;
10443 }
10444
10445
10452 protected function isArray($info)
10453 {
10454 if (is_array($info)) {
10455 if (isset($info['type']) && $info['type'] == 'array') {
10456 return true;
10457 } else {
10458 return false;
10459 }
10460 }
10461 return false;
10462 }
10463
10470 public function isDate($info)
10471 {
10472 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10473 return true;
10474 }
10475 return false;
10476 }
10477
10484 public function isDuration($info)
10485 {
10486 if (is_array($info)) {
10487 if (isset($info['type']) && ($info['type'] == 'duration')) {
10488 return true;
10489 } else {
10490 return false;
10491 }
10492 } else {
10493 return false;
10494 }
10495 }
10496
10503 public function isInt($info)
10504 {
10505 if (is_array($info)) {
10506 if (isset($info['type']) && (preg_match('/(^int|int$)/i', $info['type']))) {
10507 return true;
10508 } else {
10509 return false;
10510 }
10511 } else {
10512 return false;
10513 }
10514 }
10515
10522 public function isFloat($info)
10523 {
10524 if (is_array($info)) {
10525 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10526 return true;
10527 } else {
10528 return false;
10529 }
10530 }
10531 return false;
10532 }
10533
10540 public function isText($info)
10541 {
10542 if (is_array($info)) {
10543 if (isset($info['type']) && $info['type'] == 'text') {
10544 return true;
10545 } else {
10546 return false;
10547 }
10548 }
10549 return false;
10550 }
10551
10558 protected function canBeNull($info)
10559 {
10560 if (is_array($info)) {
10561 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10562 return true;
10563 } else {
10564 return false;
10565 }
10566 }
10567 return true;
10568 }
10569
10576 protected function isForcedToNullIfZero($info)
10577 {
10578 if (is_array($info)) {
10579 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10580 return true;
10581 } else {
10582 return false;
10583 }
10584 }
10585 return false;
10586 }
10587
10594 protected function isIndex($info)
10595 {
10596 if (is_array($info)) {
10597 if (array_key_exists('index', $info) && $info['index'] == true) {
10598 return true;
10599 } else {
10600 return false;
10601 }
10602 }
10603 return false;
10604 }
10605
10606
10615 protected function setSaveQuery()
10616 {
10617 global $conf;
10618
10619 $queryarray = array();
10620 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10621 // Depending on field type ('datetime', ...)
10622 if ($this->isDate($info)) {
10623 if (empty($this->{$field})) {
10624 $queryarray[$field] = null;
10625 } else {
10626 $queryarray[$field] = $this->db->idate($this->{$field});
10627 }
10628 } elseif ($this->isDuration($info)) {
10629 // $this->{$field} may be null, '', 0, '0', 123, '123'
10630 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10631 if (!isset($this->{$field})) {
10632 if (!empty($info['default'])) {
10633 $queryarray[$field] = $info['default'];
10634 } else {
10635 $queryarray[$field] = 0;
10636 }
10637 } else {
10638 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10639 }
10640 } else {
10641 $queryarray[$field] = null;
10642 }
10643 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10644 if ($field == 'entity' && is_null($this->{$field})) {
10645 $queryarray[$field] = ((int) $conf->entity);
10646 } else {
10647 // $this->{$field} may be null, '', 0, '0', 123, '123'
10648 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10649 if (!isset($this->{$field})) {
10650 $queryarray[$field] = 0;
10651 } elseif ($this->isInt($info)) {
10652 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10653 } elseif ($this->isFloat($info)) {
10654 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10655 }
10656 } else {
10657 $queryarray[$field] = null;
10658 }
10659 }
10660 } else {
10661 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10662 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10663 $queryarray[$field] = $this->{$field};
10664 }
10665
10666 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10667 unset($queryarray[$field]);
10668 }
10669 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10670 $queryarray[$field] = null; // May force 0 to null
10671 }
10672 }
10673
10674 return $queryarray;
10675 }
10676
10683 public function setVarsFromFetchObj(&$obj)
10684 {
10685 global $db;
10686
10687 foreach ($this->fields as $field => $info) {
10688 if ($this->isDate($info)) {
10689 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') {
10690 $this->$field = '';
10691 } else {
10692 $this->$field = $db->jdate($obj->$field);
10693 }
10694 } elseif ($this->isInt($info)) {
10695 if ($field == 'rowid') {
10696 $this->id = (int) $obj->$field;
10697 } else {
10698 if ($this->isForcedToNullIfZero($info)) {
10699 if (empty($obj->$field)) {
10700 $this->$field = null;
10701 } else {
10702 $this->$field = (int) $obj->$field;
10703 }
10704 } else {
10705 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10706 $this->$field = (int) $obj->$field;
10707 } else {
10708 $this->$field = null;
10709 }
10710 }
10711 }
10712 } elseif ($this->isFloat($info)) {
10713 if ($this->isForcedToNullIfZero($info)) {
10714 if (empty($obj->$field)) {
10715 $this->$field = null;
10716 } else {
10717 $this->$field = (float) $obj->$field;
10718 }
10719 } else {
10720 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10721 $this->$field = (float) $obj->$field;
10722 } else {
10723 $this->$field = null;
10724 }
10725 }
10726 } else {
10727 $this->$field = isset($obj->$field) ? $obj->$field : null;
10728 }
10729 }
10730
10731 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10732 if (!isset($this->fields['ref']) && isset($this->id)) {
10733 $this->ref = (string) $this->id;
10734 }
10735 }
10736
10741 public function emtpyObjectVars()
10742 {
10743 foreach ($this->fields as $field => $arr) {
10744 $this->$field = null;
10745 }
10746 }
10747
10755 public function getFieldList($alias = '', $excludefields = array())
10756 {
10757 $keys = array_keys($this->fields);
10758 if (!empty($alias)) {
10759 $keys_with_alias = array();
10760 foreach ($keys as $fieldname) {
10761 if (!empty($excludefields)) {
10762 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10763 continue;
10764 }
10765 }
10766 $keys_with_alias[] = $this->db->sanitize($alias . '.' . $fieldname);
10767 }
10768 return implode(', ', $keys_with_alias);
10769 } else {
10770 return implode(', ', $keys);
10771 }
10772 }
10773
10781 protected function quote($value, $fieldsentry)
10782 {
10783 if (is_null($value)) {
10784 return 'NULL';
10785 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10786 return price2num((string) $value);
10787 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10788 return (int) $value;
10789 } elseif ($fieldsentry['type'] == 'boolean') {
10790 if ($value) {
10791 return 'true';
10792 } else {
10793 return 'false';
10794 }
10795 } else {
10796 return "'".$this->db->escape($value)."'";
10797 }
10798 }
10799
10800
10808 public function createCommon(User $user, $notrigger = 0)
10809 {
10810 global $conf;
10811 //global $langs; // Should be able to work with $langs loaded
10812
10813 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10814
10815 $error = 0;
10816
10817 $now = dol_now();
10818
10819 $fieldvalues = $this->setSaveQuery();
10820
10821 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10822
10823 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10824 $fieldvalues['date_creation'] = $this->db->idate($now);
10825 $this->date_creation = $this->db->idate($now);
10826 }
10827 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10828 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10829 $fieldvalues['fk_user_creat'] = $user->id;
10830 $this->fk_user_creat = $user->id;
10831 }
10832 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10833 $fieldvalues['user_creation_id'] = $user->id;
10834 $this->user_creation_id = $user->id;
10835 }
10836 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10837 // @phan-suppress-next-line PhanUndeclaredProperty
10838 $tmparray = dol_hash($this->pass, '0', 0, 1);
10839 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10840 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10841 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10842 }
10843 }
10844 if (array_key_exists('ref', $fieldvalues)) {
10845 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10846 }
10847 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
10848 $fieldvalues['tms'] = $this->db->idate($now);
10849 }
10850
10851 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10852
10853 $keys = array();
10854 $values = array(); // Array to store string forged for SQL syntax
10855 foreach ($fieldvalues as $k => $v) {
10856 $keys[$k] = $k;
10857 $value = $this->fields[$k];
10858 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10859 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10860 }
10861
10862 // Clean and check mandatory
10863 foreach ($keys as $key) {
10864 if (!isset($this->fields[$key])) {
10865 continue;
10866 }
10867 $key_fields = $this->fields[$key];
10868
10869 // If field is an implicit foreign key field (so type = 'integer:...')
10870 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10871 $values[$key] = '';
10872 }
10873 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10874 $values[$key] = '';
10875 }
10876
10877 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10878 $error++;
10879 global $langs;
10880 if (empty($langs)) {
10881 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10882 $langs = new Translate('', $conf);
10883 $langs->setDefaultLang();
10884 $langs->load("errors");
10885 }
10886 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10887 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10888 }
10889
10890 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10891 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'])) {
10892 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10893 }
10894
10895 // If field is an implicit foreign key field (so type = 'integer:...')
10896 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10897 if (isset($key_fields['default'])) {
10898 $values[$key] = ((int) $key_fields['default']);
10899 } else {
10900 $values[$key] = 'null';
10901 }
10902 }
10903 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10904 $values[$key] = 'null';
10905 }
10906 }
10907
10908 if ($error) {
10909 return -1;
10910 }
10911
10912 $this->db->begin();
10913
10914 if (!$error) {
10915 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10916 $sql .= " (".implode(", ", $keys).')';
10917 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10918
10919 $res = $this->db->query($sql);
10920 if (!$res) {
10921 $error++;
10922 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10923 $this->errors[] = "ErrorRefAlreadyExists";
10924 } else {
10925 $this->errors[] = $this->db->lasterror();
10926 }
10927 }
10928 }
10929
10930 if (!$error) {
10931 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10932 }
10933
10934 // If we have a field ref with a default value of (PROV)
10935 if (!$error) {
10936 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10937 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)') {
10938 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10939 $resqlupdate = $this->db->query($sql);
10940
10941 if ($resqlupdate === false) {
10942 $error++;
10943 $this->errors[] = $this->db->lasterror();
10944 } else {
10945 $this->ref = '(PROV'.$this->id.')';
10946 }
10947 }
10948 }
10949
10950 // Create extrafields
10951 if (!$error) {
10952 $result = $this->insertExtraFields();
10953 if ($result < 0) {
10954 $error++;
10955 }
10956 }
10957
10958 // Create lines
10959 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10960 foreach ($this->lines as $line) {
10961 $keyforparent = $this->fk_element;
10962 $line->$keyforparent = $this->id;
10963
10964 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10965 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10966 if (!is_object($line)) {
10967 $line = (object) $line;
10968 }
10969
10970 $result = 0;
10971 if (method_exists($line, 'insert')) {
10972 $result = $line->insert($user, 1);
10973 } elseif (method_exists($line, 'create')) {
10974 $result = $line->create($user, 1);
10975 }
10976 if ($result < 0) {
10977 $this->error = $line->error;
10978 $this->db->rollback();
10979 return -1;
10980 }
10981 }
10982 }
10983
10984 // Triggers
10985 if (!$error && !$notrigger) {
10986 // Call triggers
10987 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10988 if ($result < 0) {
10989 $error++;
10990 }
10991 // End call triggers
10992 }
10993
10994 // Commit or rollback
10995 if ($error) {
10996 $this->db->rollback();
10997 return -1;
10998 } else {
10999 $this->db->commit();
11000 return $this->id;
11001 }
11002 }
11003
11004
11014 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
11015 {
11016 if (empty($id) && empty($ref) && empty($morewhere)) {
11017 return -1;
11018 }
11019
11020 $fieldlist = $this->getFieldList('t');
11021 if (empty($fieldlist)) {
11022 return 0;
11023 }
11024
11025 $sql = "SELECT ".$this->db->sanitize($fieldlist, 0, 0, 1);
11026 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
11027
11028 if (!empty($id)) {
11029 $sql .= ' WHERE t.rowid = '.((int) $id);
11030 } elseif (!empty($ref)) {
11031 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
11032 } else {
11033 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
11034 }
11035 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
11036 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
11037 }
11038 if ($morewhere) {
11039 $sql .= $morewhere;
11040 }
11041 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
11042
11043 $res = $this->db->query($sql);
11044 if ($res) {
11045 $obj = $this->db->fetch_object($res);
11046 if ($obj) {
11047 $this->setVarsFromFetchObj($obj);
11048
11049 // Retrieve all extrafield
11050 // fetch optionals attributes and labels
11051 if (empty($noextrafields)) {
11052 $result = $this->fetch_optionals();
11053 if ($result < 0) {
11054 $this->error = $this->db->lasterror();
11055 $this->errors[] = $this->error;
11056 return -4;
11057 }
11058 }
11059
11060 return $this->id;
11061 } else {
11062 return 0;
11063 }
11064 } else {
11065 $this->error = $this->db->lasterror();
11066 $this->errors[] = $this->error;
11067 return -1;
11068 }
11069 }
11070
11078 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
11079 {
11080 $objectlineclassname = get_class($this).'Line';
11081 if (!class_exists($objectlineclassname)) {
11082 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
11083 return -1;
11084 }
11085
11086 $objectline = new $objectlineclassname($this->db);
11087 '@phan-var-force CommonObjectLine $objectline';
11088
11089 $sql = "SELECT ".$objectline->getFieldList('l');
11090 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
11091 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
11092 if ($morewhere) {
11093 $sql .= $morewhere;
11094 }
11095 if (isset($objectline->fields['position'])) {
11096 $sql .= $this->db->order('position', 'ASC');
11097 }
11098
11099 $resql = $this->db->query($sql);
11100 if ($resql) {
11101 $num_rows = $this->db->num_rows($resql);
11102 $i = 0;
11103 $this->lines = array();
11104 while ($i < $num_rows) {
11105 $obj = $this->db->fetch_object($resql);
11106 if ($obj) {
11107 $newline = new $objectlineclassname($this->db);
11108 '@phan-var-force CommonObjectLine $newline';
11109 $newline->setVarsFromFetchObj($obj);
11110
11111 // Load also extrafields for the line
11112 if (empty($noextrafields)) {
11113 $newline->fetch_optionals();
11114 }
11115
11116 $this->lines[] = $newline;
11117 }
11118 $i++;
11119 }
11120
11121 return 1;
11122 } else {
11123 $this->error = $this->db->lasterror();
11124 $this->errors[] = $this->error;
11125 return -1;
11126 }
11127 }
11128
11136 public function updateCommon(User $user, $notrigger = 0)
11137 {
11138 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11139
11140 $error = 0;
11141
11142 $now = dol_now();
11143
11144 // $this->oldcopy should have been set by the caller of update
11145 //if (empty($this->oldcopy)) {
11146 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11147 // $this->oldcopy = dol_clone($this, 2);
11148 //}
11149
11150 $fieldvalues = $this->setSaveQuery();
11151
11152 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11153
11154 if (array_key_exists('date_modification', $fieldvalues)) {
11155 $fieldvalues['date_modification'] = $this->db->idate($now);
11156 }
11157 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
11158 $fieldvalues['tms'] = $this->db->idate($now);
11159 }
11160 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11161 $fieldvalues['fk_user_modif'] = $user->id;
11162 }
11163 if (array_key_exists('user_modification_id', $fieldvalues)) {
11164 $fieldvalues['user_modification_id'] = $user->id;
11165 }
11166 // @phan-suppress-next-line PhanUndeclaredProperty
11167 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11168 // @phan-suppress-next-line PhanUndeclaredProperty
11169 $tmparray = dol_hash($this->pass, '0', 0, 1);
11170 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11171 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11172 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11173 }
11174 }
11175 if (array_key_exists('ref', $fieldvalues)) {
11176 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11177 }
11178
11179 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11180
11181 // Add quotes and escape on fields with type string
11182 $keys = array();
11183 $values = array();
11184 $tmp = array();
11185 foreach ($fieldvalues as $k => $v) {
11186 $keys[$k] = $k;
11187 $value = $this->fields[$k];
11188 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11189 $values[$k] = $this->quote($v, $value);
11190 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11191 // Clean values for text with selectbox
11192 $v = preg_replace('/\s/', ',', $v);
11193 $v = preg_replace('/,+/', ',', $v);
11194 }
11195 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11196 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
11197 }
11198
11199 // Clean and check mandatory fields
11200 foreach ($keys as $key) {
11201 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11202 $values[$key] = ''; // This is an implicit foreign key field
11203 }
11204 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11205 $values[$key] = ''; // This is an explicit foreign key field
11206 }
11207
11208 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11209 /*
11210 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11211 {
11212 $error++;
11213 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11214 }*/
11215 }
11216
11217 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
11218
11219 $this->db->begin();
11220
11221 if (!$error) {
11222 $res = $this->db->query($sql);
11223 if (!$res) {
11224 $error++;
11225 $this->errors[] = $this->db->lasterror();
11226 }
11227 }
11228
11229 // Update extrafield
11230 if (!$error) {
11231 $result = $this->insertExtraFields(); // This update extrafields
11232 if ($result < 0) {
11233 $error++;
11234 }
11235 }
11236
11237 // Triggers
11238 if (!$error && !$notrigger) {
11239 // Call triggers
11240 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11241 if ($result < 0) {
11242 $error++;
11243 } //Do also here what you must do to rollback action if trigger fail
11244 // End call triggers
11245 }
11246
11247 // Commit or rollback
11248 if ($error) {
11249 $this->db->rollback();
11250 return -1;
11251 } else {
11252 $this->db->commit();
11253 return $this->id;
11254 }
11255 }
11256
11265 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11266 {
11267 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11268
11269 $error = 0;
11270
11271 $this->db->begin();
11272
11273 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11274 foreach ($this->childtables as $table) {
11275 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($table)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11276 $resql = $this->db->query($sql);
11277 if (!$resql) {
11278 $this->error = $this->db->lasterror();
11279 $this->errors[] = $this->error;
11280 $this->db->rollback();
11281 return -1;
11282 }
11283 }
11284 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11285 $objectisused = $this->isObjectUsed($this->id);
11286 if (!empty($objectisused)) {
11287 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11288 $this->error = 'ErrorRecordHasChildren';
11289 $this->errors[] = $this->error;
11290 $this->db->rollback();
11291 return 0;
11292 }
11293 }
11294
11295 // Delete cascade first
11296 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11297 foreach ($this->childtablesoncascade as $tabletodelete) {
11298 $deleteFromObject = explode(':', $tabletodelete, 4);
11299 if (count($deleteFromObject) >= 2) {
11300 $className = str_replace('@', '', $deleteFromObject[0]);
11301 $filePath = $deleteFromObject[1];
11302 $columnName = $deleteFromObject[2];
11303 $filter = '';
11304 if (!empty($deleteFromObject[3])) {
11305 $filter = $deleteFromObject[3];
11306 }
11307
11308 if (dol_include_once($filePath)) {
11309 $childObject = new $className($this->db);
11310 if (method_exists($childObject, 'deleteByParentField')) {
11311 '@phan-var-force CommonObject $childObject';
11312 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11313 if ($result < 0) {
11314 $error++;
11315 $this->errors[] = $childObject->error;
11316 break;
11317 }
11318 } else {
11319 $error++;
11320 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11321 break;
11322 }
11323 } else {
11324 $error++;
11325 $this->errors[] = 'Cannot include child class file '.$filePath;
11326 break;
11327 }
11328 } else {
11329 // Delete record in child table
11330 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($tabletodelete)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11331
11332 $resql = $this->db->query($sql);
11333 if (!$resql) {
11334 $error++;
11335 $this->error = $this->db->lasterror();
11336 $this->errors[] = $this->error;
11337 break;
11338 }
11339 }
11340 }
11341 }
11342
11343 if (!$error) {
11344 if (!$notrigger) {
11345 // Call triggers
11346 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11347 if ($result < 0) {
11348 $error++;
11349 } // Do also here what you must do to rollback action if trigger fail
11350 // End call triggers
11351 }
11352 }
11353
11354 // Delete llx_ecm_files
11355 if (!$error) {
11356 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11357 if (!$res) {
11358 $error++;
11359 }
11360 }
11361
11362 if (!$error) {
11363 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11364 // For remove dir
11365 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11366 if (dol_is_dir($dir)) {
11367 if (!dol_delete_dir_recursive($dir)) {
11368 $this->errors[] = 'ErrorFailToDeleteDir';
11369 }
11370 }
11371 }
11372
11373 // Delete linked object
11374 $res = $this->deleteObjectLinked();
11375 if ($res < 0) {
11376 $error++;
11377 }
11378
11379 if (!$error && !empty($this->isextrafieldmanaged)) {
11380 $result = $this->deleteExtraFields();
11381 if ($result < 0) {
11382 $error++;
11383 }
11384 }
11385
11386 if (!$error) {
11387 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11388
11389 $resql = $this->db->query($sql);
11390 if (!$resql) {
11391 $error++;
11392 $this->errors[] = $this->db->lasterror();
11393 }
11394 }
11395
11396 // Commit or rollback
11397 if ($error) {
11398 $this->db->rollback();
11399 return -1;
11400 } else {
11401 $this->db->commit();
11402 return 1;
11403 }
11404 }
11405
11417 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11418 {
11419 global $user;
11420
11421 $error = 0;
11422 $deleted = 0;
11423
11424 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11425
11426 if (!empty($parentId) && !empty($parentField)) {
11427 if (empty($this->table_element)) {
11428 $this->error = 'Property table_element for object is not defined';
11429 $this->errors[] = $this->error;
11430 $error++;
11431 return -1;
11432 }
11433 if (!method_exists($this, 'fetch')) {
11434 $this->error = 'Method fetch for object is not defined';
11435 $this->errors[] = $this->error;
11436 $error++;
11437 return -1;
11438 }
11439 if (!method_exists($this, 'delete')) {
11440 $this->error = 'Method delete for object is not defined';
11441 $this->errors[] = $this->error;
11442 $error++;
11443 return -1;
11444 }
11445
11446 $this->db->begin();
11447
11448 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11449 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11450
11451 // Manage filter
11452 $errormessage = '';
11453 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11454 if ($errormessage) {
11455 $this->errors[] = $errormessage;
11456 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11457 return -1;
11458 }
11459
11460 $resql = $this->db->query($sql);
11461 if (!$resql) {
11462 $this->errors[] = $this->db->lasterror();
11463 $error++;
11464 } else {
11465 while ($obj = $this->db->fetch_object($resql)) {
11466 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11467 if ($result < 0) {
11468 $error++;
11469 $this->errors[] = $this->error;
11470 } else {
11471 $result = $this->delete($user); // @phpstan-ignore-line
11472 if ($result < 0) {
11473 $error++;
11474 $this->errors[] = $this->error;
11475 } else {
11476 $deleted++;
11477 }
11478 }
11479 }
11480 }
11481
11482 if (empty($error)) {
11483 $this->db->commit();
11484 return $deleted;
11485 } else {
11486 $this->error = implode(', ', $this->errors);
11487 $this->db->rollback();
11488 return $error * -1;
11489 }
11490 }
11491
11492 return $deleted;
11493 }
11494
11503 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11504 {
11505 $error = 0;
11506
11507 $tmpforobjectclass = get_class($this);
11508 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11509
11510 $this->db->begin();
11511
11512 // Call trigger
11513 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11514 if ($result < 0) {
11515 $error++;
11516 }
11517 // End call triggers
11518
11519 if (empty($error)) {
11520 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11521 $sql .= " WHERE rowid = ".((int) $idline);
11522
11523 $resql = $this->db->query($sql);
11524 if (!$resql) {
11525 $this->error = "Error ".$this->db->lasterror();
11526 $error++;
11527 }
11528 }
11529
11530 if (empty($error)) {
11531 // Remove extrafields
11532 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11533 '@phan-var-force CommonObjectLine $tmpobjectline';
11534 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11535 $tmpobjectline->id = $idline;
11536 $result = $tmpobjectline->deleteExtraFields();
11537 if ($result < 0) {
11538 $error++;
11539 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11540 }
11541 }
11542 }
11543
11544 if (empty($error)) {
11545 $this->db->commit();
11546 return 1;
11547 } else {
11548 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11549 $this->db->rollback();
11550 return -1;
11551 }
11552 }
11553
11554
11565 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11566 {
11567 $error = 0;
11568
11569 $this->db->begin();
11570
11571 $statusfield = 'status';
11572 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11573 $statusfield = 'fk_statut';
11574 }
11575
11576 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11577 $sql .= " SET ".$this->db->sanitize($statusfield)." = ".((int) $status);
11578 $sql .= " WHERE rowid = ".((int) $this->id);
11579
11580 if ($this->db->query($sql)) {
11581 if (!$error) {
11582 $this->oldcopy = clone $this;
11583 }
11584
11585 if (!$error && !$notrigger) {
11586 // Call trigger
11587 $result = $this->call_trigger($triggercode, $user);
11588 if ($result < 0) {
11589 $error++;
11590 }
11591 }
11592
11593 if (!$error) {
11594 $this->status = $status;
11595 if (property_exists($this, 'statut')) { // For backward compatibility
11596 $this->statut = $status;
11597 }
11598 $this->db->commit();
11599 return 1;
11600 } else {
11601 $this->db->rollback();
11602 return -1;
11603 }
11604 } else {
11605 $this->error = $this->db->error();
11606 $this->db->rollback();
11607 return -1;
11608 }
11609 }
11610
11617 public function initAsSpecimenCommon()
11618 {
11619 global $user;
11620
11621 $this->id = 0;
11622 $this->specimen = 1;
11623 $fields = array(
11624 'label' => 'This is label',
11625 'ref' => 'ABCD1234',
11626 'description' => 'This is a description',
11627 'qty' => 123.12,
11628 'note_public' => 'Public note',
11629 'note_private' => 'Private note',
11630 'date_creation' => (dol_now() - 3600 * 48),
11631 'date_modification' => (dol_now() - 3600 * 24),
11632 'fk_user_creat' => $user->id,
11633 'fk_user_modif' => $user->id,
11634 'date' => dol_now(),
11635 );
11636 foreach ($fields as $key => $value) {
11637 if (array_key_exists($key, $this->fields)) {
11638 $this->{$key} = $value; // @phpstan-ignore-line
11639 }
11640 }
11641
11642 // Force values to default values when known
11643 if (property_exists($this, 'fields')) {
11644 foreach ($this->fields as $key => $value) {
11645 // If fields are already set, do nothing
11646 if (array_key_exists($key, $fields)) {
11647 continue;
11648 }
11649
11650 if (!empty($value['default'])) {
11651 $this->$key = $value['default'];
11652 }
11653 }
11654 }
11655
11656 return 1;
11657 }
11658
11659
11660 /* Part for comments */
11661
11667 public function fetchComments()
11668 {
11669 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11670
11671 $comment = new Comment($this->db);
11672 $result = $comment->fetchAllFor($this->element, $this->id);
11673 if ($result < 0) {
11674 $this->setErrorsFromObject($comment);
11675 return -1;
11676 } else {
11677 $this->comments = $comment->comments;
11678 }
11679 return count($this->comments);
11680 }
11681
11687 public function getNbComments()
11688 {
11689 return count($this->comments);
11690 }
11691
11698 public function trimParameters($parameters)
11699 {
11700 if (!is_array($parameters)) {
11701 return;
11702 }
11703 foreach ($parameters as $parameter) {
11704 if (isset($this->$parameter)) {
11705 $this->$parameter = trim($this->$parameter);
11706 }
11707 }
11708 }
11709
11710 /* Part for categories/tags */
11711
11722 public function getCategoriesCommon($type_categ)
11723 {
11724 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11725
11726 // Get current categories
11727 $c = new Categorie($this->db);
11728 $existing = $c->containing($this->id, $type_categ, 'id');
11729
11730 return $existing;
11731 }
11732
11745 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11746 {
11747 // Handle single category
11748 if (!is_array($categories)) {
11749 $categories = array($categories);
11750 }
11751
11752 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11753
11754 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11755
11756 if (empty($type_categ)) {
11757 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11758 return -1;
11759 }
11760
11761 // Get current categories
11762 $c = new Categorie($this->db);
11763 $existing = $c->containing($this->id, $type_categ, 'id');
11764 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11765 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11766 if (!is_array($existing)) {
11767 $existing = array();
11768 }
11769 if ($remove_existing) {
11770 // Diff
11771 $to_del = array_diff($existing, $categories);
11772 $to_add = array_diff($categories, $existing);
11773 } else {
11774 $to_del = array(); // Nothing to delete
11775 $to_add = array_diff($categories, $existing);
11776 }
11777
11778 $error = 0;
11779 $ok = 0;
11780
11781 // Process
11782 foreach ($to_del as $del) {
11783 if ($c->fetch($del) > 0) {
11784 $result = $c->del_type($this, $type_categ);
11785 if ($result < 0) {
11786 $error++;
11787 $this->error = $c->error;
11788 $this->errors = $c->errors;
11789 break;
11790 } else {
11791 $ok += $result;
11792 }
11793 }
11794 }
11795 foreach ($to_add as $add) {
11796 if ($c->fetch($add) > 0) {
11797 $result = $c->add_type($this, $type_categ);
11798 if ($result < 0) {
11799 $error++;
11800 $this->error = $c->error;
11801 $this->errors = $c->errors;
11802 break;
11803 } else {
11804 $ok += $result;
11805 }
11806 }
11807 }
11808
11809 return $error ? (-1 * $error) : $ok;
11810 }
11811
11820 public function cloneCategories($fromId, $toId, $type = '')
11821 {
11822 $this->db->begin();
11823
11824 if (empty($type)) {
11825 $type = $this->table_element;
11826 }
11827
11828 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11829 $categorystatic = new Categorie($this->db);
11830
11831 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11832 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11833
11834 $sql = "INSERT INTO ".$this->db->sanitize($tablename)." (fk_categorie, ".$this->db->sanitize($fkname).")";
11835 $sql .= " SELECT fk_categorie, ".((int) $toId)." FROM ".$this->db->sanitize($tablename);
11836 $sql .= " WHERE ".$this->db->sanitize($fkname)." = ".((int) $fromId);
11837
11838 if (!$this->db->query($sql)) {
11839 $this->error = $this->db->lasterror();
11840 $this->db->rollback();
11841 return -1;
11842 }
11843
11844 $this->db->commit();
11845 return 1;
11846 }
11847
11854 public function deleteEcmFiles($mode = 0)
11855 {
11856 global $conf;
11857
11858 $this->db->begin();
11859
11860 // Delete in database with mode 0
11861 if ($mode == 0) {
11862 switch ($this->element) {
11863 case 'propal':
11864 $element = 'propale';
11865 break;
11866 case 'product':
11867 $element = 'produit';
11868 break;
11869 case 'order_supplier':
11870 $element = 'fournisseur/commande';
11871 break;
11872 case 'invoice_supplier':
11873 // Special cases that need to use get_exdir to get real dir of object
11874 // In future, all object should use this to define path of documents.
11875 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11876 break;
11877 case 'shipping':
11878 $element = 'expedition/sending';
11879 break;
11880 case 'task':
11881 case 'project_task':
11882 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11883
11884 $project_result = $this->fetchProject();
11885 if ($project_result >= 0) {
11886 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11887 }
11888 // no break
11889 case 'contrat':
11890 $element = 'contract';
11891 break;
11892 default:
11893 $element = $this->element;
11894 }
11895 '@phan-var-force string $element';
11896
11897 // Delete ecm_files_extrafields with mode 0 (using name)
11898 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11899 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11900 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11901 $sql .= ")";
11902
11903 if (!$this->db->query($sql)) {
11904 $this->error = $this->db->lasterror();
11905 $this->db->rollback();
11906 return false;
11907 }
11908
11909 // Delete ecm_files with mode 0 (using name)
11910 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11911 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11912 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11913
11914 if (!$this->db->query($sql)) {
11915 $this->error = $this->db->lasterror();
11916 $this->db->rollback();
11917 return false;
11918 }
11919 }
11920
11921 // Delete in database with mode 1
11922 if ($mode == 1) {
11923 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11924 $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).")";
11925 $resql = $this->db->query($sql);
11926 if (!$resql) {
11927 $this->error = $this->db->lasterror();
11928 $this->db->rollback();
11929 return false;
11930 }
11931
11932 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11933 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11934 $resql = $this->db->query($sql);
11935 if (!$resql) {
11936 $this->error = $this->db->lasterror();
11937 $this->db->rollback();
11938 return false;
11939 }
11940 }
11941
11942 $this->db->commit();
11943 return true;
11944 }
11945
11954 public function checkActiveProductInLines($status = 'onsale')
11955 {
11956 global $langs;
11957
11958 if (isModEnabled('product') || isModEnabled('service')) {
11959 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
11960
11961 $ret = true;
11962 $tmpproduct = new Product($this->db);
11963 foreach ($this->lines as $line) {
11964 if ($line->fk_product > 0) {
11965 $tmpproduct->fetch($line->fk_product);
11966 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
11967 if (!$tmpproduct->$statustotest) {
11968 $langs->load('products');
11969 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
11970 $ret = false;
11971 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
11972 break;
11973 }
11974 }
11975 }
11976 if (!$ret) {
11977 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
11978 }
11979 return $ret;
11980 } else {
11981 return true;
11982 }
11983 }
11984
11991 public function setErrorWithoutLog($message)
11992 {
11993 $this->error = $message;
11994 $this->errors[] = $message;
11995 }
11996
12004 public function setErrorWithLog($message, $loglevel = LOG_ERR)
12005 {
12006 global $dolibarr_main_document_root;
12007
12008 $this->setErrorWithoutLog($message);
12009 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
12010 $file = str_replace($dolibarr_main_document_root, '', $trace[0]['file'] ?? 'file unknown');
12011 $line = $trace[0]['line'] ?? 'line unknown';
12012 $syslogMessage = sprintf('%s:%s %s', $file, $line, $message);
12013 dol_syslog($syslogMessage, $loglevel);
12014 }
12015}
$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...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
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.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
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.
getDictionaryValue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
Return the value of a filed into a dictionary for the record $id.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png, bmp and webp)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $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.