dolibarr 25.0.0-alpha
commonobject.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2015 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
5 * Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
6 * Copyright (C) 2011-2022 Philippe Grand <philippe.grand@atoo-net.com>
7 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
8 * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
10 * Copyright (C) 2015-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
11 * Copyright (C) 2016 Bahfir abbes <bafbes@gmail.com>
12 * Copyright (C) 2017 ATM Consulting <support@atm-consulting.fr>
13 * Copyright (C) 2017-2026 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2017 Rui Strecht <rui.strecht@aliartalentos.com>
15 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
16 * Copyright (C) 2018 Josep Lluís Amador <joseplluis@lliuretic.cat>
17 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
18 * Copyright (C) 2021 Grégory Blémand <gregory.blemand@atm-consulting.fr>
19 * Copyright (C) 2023 Lenin Rivas <lenin.rivas777@gmail.com>
20 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
21 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
22 * Copyright (C) 2025 Alexandre Janniaux <alexandre.janniaux@gmail.com>
23 * Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
24 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
25*
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->id > 0 && !$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 (".((int) $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 $sanitized_order = $order; // @phan-suppress-current-line SqlInjection
1755
1756 if (empty($sanitized_order)) {
1757 $sanitized_order = 'position';
1758 }
1759 if ($sanitized_order == 'position') {
1760 $sanitized_order .= ',code';
1761 }
1762
1763 $tab = array();
1764
1765 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1766 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1767 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1768 if ($activeonly == 1) {
1769 $sql .= " AND tc.active = 1"; // only the active types
1770 }
1771 if (!empty($source) && $source != 'all') {
1772 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1773 }
1774 if (!empty($code)) {
1775 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1776 }
1777 $sql .= $this->db->order($sanitized_order, 'ASC');
1778
1779 //print "sql=".$sql;
1780 $resql = $this->db->query($sql);
1781 if (!$resql) {
1782 $this->error = $this->db->lasterror();
1783 //dol_print_error($this->db);
1784 return null;
1785 }
1786
1787 $num = $this->db->num_rows($resql);
1788 $i = 0;
1789 while ($i < $num) {
1790 $obj = $this->db->fetch_object($resql);
1791
1792 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1793 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1794 if (empty($option)) {
1795 $tab[$obj->rowid] = $libelle_type;
1796 } elseif ($option == 1) {
1797 $tab[$obj->code] = $libelle_type;
1798 } else {
1799 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1800 }
1801 $i++;
1802 }
1803
1804 return $tab;
1805 }
1806
1818 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1819 {
1820 global $langs;
1821
1822 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1823
1824 $tab = array();
1825
1826 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1827 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1828
1829 $sqlWhere = array();
1830 if (!empty($element)) {
1831 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1832 }
1833 if (!empty($excludeelement)) {
1834 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1835 }
1836
1837 if ($activeonly == 1) {
1838 $sqlWhere[] = " tc.active=1"; // only the active types
1839 }
1840
1841 if (!empty($source) && $source != 'all') {
1842 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1843 }
1844
1845 if (!empty($code)) {
1846 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1847 }
1848
1849 if (count($sqlWhere) > 0) {
1850 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1851 }
1852
1853 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1854
1855 dol_syslog(__METHOD__, LOG_DEBUG);
1856 $resql = $this->db->query($sql);
1857 if ($resql) {
1858 $num = $this->db->num_rows($resql);
1859 if ($num > 0) {
1860 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1861
1862 while ($obj = $this->db->fetch_object($resql)) {
1863 $modulename = $obj->module ?? $obj->element;
1864 if (strpos($obj->element, 'project') !== false) {
1865 $modulename = 'projet';
1866 } elseif ($obj->element == 'contrat') {
1867 $element = 'contract';
1868 } elseif ($obj->element == 'action') {
1869 $modulename = 'agenda';
1870 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1871 $modulename = 'fournisseur';
1872 }
1873 if (isModEnabled($modulename)) {
1874 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1875 $tmpelement = $obj->element;
1876 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1877 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1878 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1879 }
1880 }
1881 }
1882 return $tab;
1883 } else {
1884 $this->error = $this->db->lasterror();
1885 return null;
1886 }
1887 }
1888
1900 public function getIdContact($source, $code, $status = 0)
1901 {
1902 $result = array();
1903 $i = 0;
1904 // Particular case for shipping
1905 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1906 $id = $this->origin_id;
1907 $element = 'commande';
1908 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1909 $id = $this->origin_id;
1910 $element = 'order_supplier';
1911 } else {
1912 $id = $this->id;
1913 $element = $this->element;
1914 }
1915
1916 $sql = "SELECT ec.fk_socpeople";
1917 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1918 if ($source == 'internal') {
1919 $sql .= " ".$this->db->prefix()."user as c,";
1920 }
1921 if ($source == 'external') {
1922 $sql .= " ".$this->db->prefix()."socpeople as c,";
1923 }
1924 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1925 $sql .= " WHERE ec.element_id = ".((int) $id);
1926 $sql .= " AND ec.fk_socpeople = c.rowid";
1927 if ($source == 'internal') {
1928 $sql .= " AND c.entity IN (".getEntity('user').")";
1929 }
1930 if ($source == 'external') {
1931 $sql .= " AND c.entity IN (".getEntity('societe').")";
1932 }
1933 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1934 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1935 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1936 if ($code) {
1937 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1938 }
1939 $sql .= " AND tc.active = 1";
1940 if ($status) {
1941 $sql .= " AND ec.statut = ".((int) $status);
1942 }
1943
1944 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1945 $resql = $this->db->query($sql);
1946 if ($resql) {
1947 while ($obj = $this->db->fetch_object($resql)) {
1948 $result[$i] = $obj->fk_socpeople;
1949 $i++;
1950 }
1951 } else {
1952 $this->error = $this->db->error();
1953 return null;
1954 }
1955
1956 return $result;
1957 }
1958
1959 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1966 public function fetch_contact($contactid = null)
1967 {
1968 // phpcs:enable
1969 if (empty($contactid)) {
1970 $contactid = $this->contact_id;
1971 }
1972
1973 if (empty($contactid)) {
1974 return 0;
1975 }
1976
1977 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1978 $contact = new Contact($this->db);
1979 $result = $contact->fetch($contactid);
1980 $this->contact = $contact;
1981 return $result;
1982 }
1983
1984 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1992 public function fetch_thirdparty($force_thirdparty_id = 0)
1993 {
1994 // phpcs:enable
1995 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1996 return 0;
1997 }
1998
1999 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
2000
2001 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2002 if ($force_thirdparty_id) {
2003 $idtofetch = $force_thirdparty_id;
2004 }
2005
2006 if ($idtofetch) {
2007 $thirdparty = new Societe($this->db);
2008 $result = $thirdparty->fetch($idtofetch);
2009 if ($result < 0) {
2010 $this->errors = array_merge($this->errors, $thirdparty->errors);
2011 }
2012 $this->thirdparty = $thirdparty;
2013
2014 // Use first price level if level not defined for third party
2015 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
2016 $this->thirdparty->price_level = 1;
2017 }
2018
2019 return $result;
2020 } else {
2021 return -1;
2022 }
2023 }
2024
2025
2033 public function fetchOneLike($ref)
2034 {
2035 if (!$this->table_ref_field) {
2036 return 0;
2037 }
2038
2039 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
2040 $sql .= " WHERE ".$this->db->sanitize($this->table_ref_field)." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
2041 $sql .= " LIMIT 1";
2042
2043 $query = $this->db->query($sql);
2044
2045 if (!$this->db->num_rows($query)) {
2046 return 0;
2047 }
2048
2049 $result = $this->db->fetch_object($query);
2050
2051 if (method_exists($this, 'fetch')) {
2052 return $this->fetch($result->rowid);
2053 } else {
2054 $this->error = 'Fetch method not implemented on '.get_class($this);
2055 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2056 array_push($this->errors, $this->error);
2057 return -1;
2058 }
2059 }
2060
2061 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2070 public function fetch_barcode()
2071 {
2072 // phpcs:enable
2073 return $this->fetchBarCode();
2074 }
2075
2083 public function fetchBarCode()
2084 {
2085 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2086
2087 $idtype = $this->barcode_type;
2088 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
2089 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2090 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2091 } elseif ($this->element == 'societe') {
2092 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2093 } else {
2094 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2095 }
2096 }
2097
2098 if ($idtype > 0) {
2099 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
2100 $sql = "SELECT rowid, code, libelle as label, coder";
2101 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2102 $sql .= " WHERE rowid = ".((int) $idtype);
2103
2104 $resql = $this->db->query($sql);
2105 if ($resql) {
2106 $obj = $this->db->fetch_object($resql);
2107
2108 $this->barcode_type = $obj->rowid;
2109 $this->barcode_type_code = $obj->code;
2110 $this->barcode_type_label = $obj->label;
2111 $this->barcode_type_coder = $obj->coder;
2112 return 1;
2113 } else {
2114 dol_print_error($this->db);
2115 return -1;
2116 }
2117 }
2118 }
2119 return 0;
2120 }
2121
2127 public function fetchProject()
2128 {
2129 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2130
2131 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2132 $this->fk_project = $this->fk_projet; // For backward compatibility
2133 }
2134 if (empty($this->fk_project)) {
2135 return 0;
2136 }
2137
2138 $project = new Project($this->db);
2139 $result = $project->fetch($this->fk_project);
2140
2141 $this->project = $project;
2142
2143 return $result;
2144 }
2145
2146 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2154 public function fetch_project()
2155 {
2156 // phpcs:enable
2157 return $this->fetchProject();
2158 }
2159
2160 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2168 public function fetch_projet()
2169 {
2170 // phpcs:enable
2171 return $this->fetchProject();
2172 }
2173
2174 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2180 public function fetch_product()
2181 {
2182 // phpcs:enable
2183 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2184
2185 // @phan-suppress-next-line PhanUndeclaredProperty
2186 if (empty($this->fk_product)) {
2187 return 0;
2188 }
2189
2190 $product = new Product($this->db);
2191 // @phan-suppress-next-line PhanUndeclaredProperty
2192 $result = $product->fetch($this->fk_product);
2193
2194 $this->product = $product;
2195 return $result;
2196 }
2197
2198 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2205 public function fetch_user($userid)
2206 {
2207 // phpcs:enable
2208 $user = new User($this->db);
2209 $result = $user->fetch($userid);
2210 $this->user = $user;
2211 return $result;
2212 }
2213
2214 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2221 public function fetch_origin()
2222 {
2223 // phpcs:enable
2224 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2225
2226 // Manage classes with non standard name
2227 if ($tmpclassname == 'shipping') {
2228 $tmpclassname = 'Expedition';
2229 }
2230 if ($tmpclassname == 'delivery') {
2231 $tmpclassname = 'Livraison';
2232 }
2233 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2234 $tmpclassname = 'CommandeFournisseur';
2235 }
2236
2237 $classname = ucfirst($tmpclassname);
2238
2239 $this->origin_object = new $classname($this->db);
2240 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2241 $this->origin_object->fetch($this->origin_id);
2242 }
2243
2253 public function fetchObjectFrom($table, $field, $key, $element = null)
2254 {
2255 global $conf;
2256
2257 $result = false;
2258
2259 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2260 $sql .= " WHERE ".$this->db->sanitize($field)." = '".$this->db->escape($key)."'";
2261 if (!empty($element)) {
2262 $sql .= " AND entity IN (".getEntity($element).")";
2263 } else {
2264 $sql .= " AND entity = ".((int) $conf->entity);
2265 }
2266
2267 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2268 $resql = $this->db->query($sql);
2269 if ($resql) {
2270 $obj = $this->db->fetch_object($resql);
2271 // Test for avoid error -1
2272 if ($obj) {
2273 if (method_exists($this, 'fetch')) {
2274 $result = $this->fetch($obj->rowid);
2275 } else {
2276 $this->error = 'fetch() method not implemented on '.get_class($this);
2277 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2278 array_push($this->errors, $this->error);
2279 $result = -1;
2280 }
2281 }
2282 }
2283
2284 return $result;
2285 }
2286
2295 public function getValueFrom($table, $id, $field)
2296 {
2297 $result = false;
2298 if (!empty($id) && !empty($field) && !empty($table)) {
2299 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2300 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2301 return -1;
2302 }
2303 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2304 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2305 return -1;
2306 }
2307
2308 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2309 $sql .= " WHERE rowid = ".((int) $id);
2310
2311 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2312 $resql = $this->db->query($sql);
2313 if ($resql) {
2314 $row = $this->db->fetch_row($resql);
2315 $result = $row[0];
2316 }
2317 }
2318 return $result;
2319 }
2320
2337 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2338 {
2339 global $user;
2340
2341 if (empty($table)) {
2342 $table = $this->table_element;
2343 }
2344 if (empty($id)) {
2345 $id = $this->id;
2346 }
2347 if (empty($format)) {
2348 $format = 'text';
2349 }
2350 if (empty($id_field)) {
2351 $id_field = 'rowid';
2352 }
2353
2354 $propfield = $field;
2355
2356 // Special case
2357 if ($table == 'product') {
2358 if ($field == 'note_private') {
2359 $field = 'note';
2360 $propfield = 'note_private';
2361 } elseif ($field == 'status') {
2362 $field = 'tosell';
2363 $propfield = 'status';
2364 } elseif ($field == 'status_buy') {
2365 $field = 'tobuy';
2366 $propfield = 'status_buy';
2367 } elseif ($field == 'status_batch') {
2368 $field = 'tobatch';
2369 $propfield = 'status_batch';
2370 }
2371 }
2372
2373 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2374 $fk_user_field = 'fk_user_mod';
2375 }
2376 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2377 $fk_user_field = '';
2378 }
2379
2380 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2381 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2382 return -1;
2383 }
2384 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2385 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2386 return -1;
2387 }
2388
2389 $oldvalue = null;
2390 if ($trigkey) {
2391 $sql = "SELECT " . $this->db->sanitize($field);
2392 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2393 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2394
2395 $resql = $this->db->query($sql);
2396 if ($resql) {
2397 if ($obj = $this->db->fetch_object($resql)) {
2398 if ($format == 'date') {
2399 $oldvalue = $this->db->jdate($obj->$field);
2400 } else {
2401 $oldvalue = $obj->$field;
2402 }
2403 }
2404 } else {
2405 $this->error = $this->db->lasterror();
2406 return -1;
2407 }
2408 }
2409
2410 $error = 0;
2411
2412 dol_syslog(__METHOD__, LOG_DEBUG);
2413
2414 $this->db->begin();
2415
2416 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2417
2418 if ($format == 'text') {
2419 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2420 } elseif ($format == 'int') {
2421 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2422 } elseif ($format == 'date') {
2423 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2424 } elseif ($format == 'dategmt') {
2425 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2426 }
2427
2428 if ($fk_user_field) {
2429 if (!empty($fuser) && is_object($fuser)) {
2430 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2431 } elseif (empty($fuser) || $fuser != 'none') {
2432 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2433 }
2434 }
2435
2436 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2437
2438 $resql = $this->db->query($sql);
2439 if ($resql) {
2440 if ($trigkey) {
2441 // call trigger with updated object values
2442 if (method_exists($this, 'fetch')) {
2443 $result = $this->fetch($id);
2444 } else {
2445 $result = $this->fetchCommon($id);
2446 }
2447
2448 $this->oldcopy = clone $this;
2449 if (property_exists($this->oldcopy, $field)) {
2450 $this->oldcopy->$field = $oldvalue;
2451 }
2452 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2453 $this->oldcopy->$propfield = $oldvalue;
2454 }
2455
2456 if (empty($this->context['actionmsgmore'])) {
2457 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2458 }
2459
2460 if ($result >= 0) {
2461 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2462 }
2463 if ($result < 0) {
2464 $error++;
2465 }
2466 } else {
2467 if (property_exists($this, $field)) {
2468 $this->$field = $value;
2469 }
2470 if ($propfield != $field && property_exists($this, $propfield)) {
2471 $this->$propfield = $value;
2472 }
2473 }
2474
2475 if (!$error) {
2476 $this->db->commit();
2477 return 1;
2478 } else {
2479 if (property_exists($this, $field)) {
2480 $this->$field = $oldvalue;
2481 }
2482 if ($propfield != $field && property_exists($this, $propfield)) {
2483 $this->$propfield = $oldvalue;
2484 }
2485 $this->db->rollback();
2486 return -2;
2487 }
2488 } else {
2489 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2490 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2491 } else {
2492 $this->error = $this->db->lasterror();
2493 }
2494 $this->db->rollback();
2495 return -1;
2496 }
2497 }
2498
2499 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2510 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2511 {
2512 // phpcs:enable
2513 global $conf, $user;
2514
2515 if (!$this->table_element) {
2516 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2517 return -1;
2518 }
2519 if ($fieldid == 'none') {
2520 return 1;
2521 }
2522
2523 // For backward compatibility
2524 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2525 $fieldid = 'titre';
2526 }
2527
2528 // Security on socid
2529 $socid = 0;
2530 if ($user->socid > 0) {
2531 $socid = $user->socid;
2532 }
2533
2534 // this->ismultientitymanaged contains
2535 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2536 $aliastablesociete = 's';
2537 if ($this->element == 'societe') {
2538 $aliastablesociete = 'te'; // te as table_element
2539 }
2540 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2541 $sql = "SELECT MAX(te.".$this->db->sanitize($fieldid).")";
2542 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2543 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2544 $tmparray = explode('@', $this->ismultientitymanaged);
2545 $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
2546 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2547 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2548 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2549 $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
2550 }
2551 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2552 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2553 }
2554 if ($fieldid == 'rowid') {
2555 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < ".((int) $this->id);
2556 } elseif ($fieldid == 'label') {
2557 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." < '".$this->db->escape((string) $this->label)."'";
2558 } else { // Should be 'ref' or any other string field
2559 $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)
2560 }
2561 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2562 $sql .= " AND sc.fk_user = ".((int) $user->id);
2563 }
2564 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2565 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2566 }
2567
2568 $filtermax = $filter;
2569
2570 // Manage filter
2571 $errormessage = '';
2572 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2573 if ($errormessage) {
2574 if (!preg_match('/^\s*AND/i', $filtermax)) {
2575 $sql .= " AND ";
2576 }
2577 $sql .= $filtermax; // @phan-suppress-current-line SqlInjection
2578 } else {
2579 $sql .= $tmpsql;
2580 }
2581
2582 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2583 $tmparray = explode('@', $this->ismultientitymanaged);
2584 $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
2585 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2586 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2587 }
2588 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2589 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2590 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2591 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2592 } else {
2593 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2594 }
2595 } else {
2596 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2597 }
2598 }
2599 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2600 $tmparray = explode('@', $this->ismultientitymanaged);
2601 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2602 }
2603 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2604 $sql .= ' AND te.fk_soc = '.((int) $socid);
2605 }
2606 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2607 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2608 }
2609 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2610 $sql .= ' AND te.rowid = '.((int) $socid);
2611 }
2612 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2613
2614 $result = $this->db->query($sql);
2615 if (!$result) {
2616 $this->error = $this->db->lasterror();
2617 return -1;
2618 }
2619 $row = $this->db->fetch_row($result);
2620 $this->ref_previous = $row[0];
2621
2622 $sql = "SELECT MIN(te.".$this->db->sanitize($fieldid).")";
2623 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2624 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2625 $tmparray = explode('@', $this->ismultientitymanaged);
2626 $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
2627 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2628 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2629 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2630 $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
2631 }
2632 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2633 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2634 }
2635 if ($fieldid == 'rowid') {
2636 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > ".((int) $this->id);
2637 } elseif ($fieldid == 'label') {
2638 $sql .= " WHERE te.".$this->db->sanitize($fieldid)." > '".$this->db->escape((string) $this->label)."'";
2639 } else { // Should be 'ref' or any other string field
2640 $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)
2641 }
2642 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2643 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2644 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2645 $userschilds = $user->getAllChildIds();
2646 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2647 }
2648 $sql .= ')';
2649 }
2650 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2651 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2652 }
2653
2654 $filtermin = $filter;
2655
2656 // Manage filter
2657 $errormessage = '';
2658 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2659 if ($errormessage) {
2660 if (!preg_match('/^\s*AND/i', $filtermin)) {
2661 $sql .= " AND ";
2662 }
2663 $sql .= $filtermin; // @phan-suppress-current-line SqlInjection
2664
2665 $filtermin = '';
2666 } else {
2667 $sql .= $tmpsql;
2668 }
2669
2670 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2671 $tmparray = explode('@', $this->ismultientitymanaged);
2672 $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
2673 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2674 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2675 }
2676 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2677 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2678 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2679 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2680 } else {
2681 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2682 }
2683 } else {
2684 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2685 }
2686 }
2687 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2688 $tmparray = explode('@', $this->ismultientitymanaged);
2689 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2690 }
2691 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2692 $sql .= ' AND te.fk_soc = '.((int) $socid);
2693 }
2694 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2695 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2696 }
2697 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2698 $sql .= ' AND te.rowid = '.((int) $socid);
2699 }
2700 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2701 // 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
2702
2703 $result = $this->db->query($sql);
2704 if (!$result) {
2705 $this->error = $this->db->lasterror();
2706 return -2;
2707 }
2708 $row = $this->db->fetch_row($result);
2709 $this->ref_next = $row[0];
2710
2711 return 1;
2712 }
2713
2714
2722 public function getListContactId($source = 'external')
2723 {
2724 $contactAlreadySelected = array();
2725 $tab = $this->liste_contact(-1, $source);
2726 $num = count($tab);
2727 $i = 0;
2728 while ($i < $num) {
2729 if ($source == 'thirdparty') {
2730 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2731 } else {
2732 $contactAlreadySelected[$i] = $tab[$i]['id'];
2733 }
2734 $i++;
2735 }
2736 return $contactAlreadySelected;
2737 }
2738
2739
2747 public function setProject($projectid, $notrigger = 0)
2748 {
2749 global $user;
2750 $error = 0;
2751
2752 if (!$this->table_element) {
2753 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2754 return -1;
2755 }
2756
2757 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2758 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2759 if ($projectid) {
2760 $sql .= " SET fk_project = ".((int) $projectid);
2761 } else {
2762 $sql .= " SET fk_project = NULL";
2763 }
2764 $sql .= ' WHERE id = '.((int) $this->id);
2765 // @phan-suppress-next-line PhanTypeMismatchProperty
2766 } elseif (!empty($this->fields['fk_project'])) { // Common case
2767 if ($projectid) {
2768 $sql .= " SET fk_project = ".((int) $projectid);
2769 } else {
2770 $sql .= " SET fk_project = NULL";
2771 }
2772 $sql .= ' WHERE rowid = '.((int) $this->id);
2773 } else { // Special case for old architecture objects
2774 if ($projectid) {
2775 $sql .= ' SET fk_projet = '.((int) $projectid);
2776 } else {
2777 $sql .= ' SET fk_projet = NULL';
2778 }
2779 $sql .= " WHERE rowid = ".((int) $this->id);
2780 }
2781
2782 $this->db->begin();
2783
2784 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2785 if ($this->db->query($sql)) {
2786 $this->fk_project = ((int) $projectid);
2787 } else {
2788 dol_print_error($this->db);
2789 $error++;
2790 }
2791
2792 // Triggers
2793 if (!$error && !$notrigger) {
2794 // Call triggers
2795 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2796 if ($result < 0) {
2797 $error++;
2798 } //Do also here what you must do to rollback action if trigger fail
2799 // End call triggers
2800 }
2801
2802 // Commit or rollback
2803 if ($error) {
2804 $this->db->rollback();
2805 return -1;
2806 } else {
2807 $this->db->commit();
2808 return 1;
2809 }
2810 }
2811
2818 public function setPaymentMethods($id)
2819 {
2820 global $user;
2821
2822 $error = 0;
2823 $notrigger = 0;
2824
2825 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2826
2827 if ($this->status >= 0 || $this->element == 'societe') {
2828 $this->oldcopy = dol_clone($this, 2); // @phan-suppress-current-line PhanTypeMismatchProperty
2829
2830 // TODO uniformize field name
2831 $fieldname = 'fk_mode_reglement';
2832 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2833 if ($this->element == 'societe') {
2834 $fieldname = 'mode_reglement';
2835 $triggerName = 'COMPANY';
2836 }
2837 if (get_class($this) == 'Facture') {
2838 $triggerName = 'BILL';
2839 }
2840 if (get_class($this) == 'Fournisseur') {
2841 $fieldname = 'mode_reglement_supplier';
2842 $triggerName = 'BILL_SUPPLIER';
2843 }
2844 if (get_class($this) == 'Tva') {
2845 $fieldname = 'fk_typepayment';
2846 $triggerName = 'VAT';
2847 }
2848 if (get_class($this) == 'Salary') {
2849 $fieldname = 'fk_typepayment';
2850 }
2851
2852 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2853 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2854 $sql .= ' WHERE rowid = '.((int) $this->id);
2855
2856 if ($this->db->query($sql)) {
2857 $this->mode_reglement_id = $id;
2858 // for supplier
2859 if (get_class($this) == 'Fournisseur') {
2860 $this->mode_reglement_supplier_id = $id;
2861 }
2862
2863 // Triggers
2864 if (!$error && !$notrigger) {
2865 // Call triggers
2866 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2867 if ($result < 0) {
2868 $error++;
2869 }
2870 // End call triggers
2871 }
2872 return 1;
2873 } else {
2874 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2875 $this->error = $this->db->error();
2876 return -1;
2877 }
2878 } else {
2879 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2880 $this->error = 'Status of the object is incompatible '.$this->status;
2881 return -2;
2882 }
2883 }
2884
2891 public function setMulticurrencyCode($code)
2892 {
2893 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2894 if ($this->status >= 0 || $this->element == 'societe') {
2895 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2896 $sql .= " SET multicurrency_code = '".$this->db->escape($code)."'";
2897 $sql .= ' WHERE rowid='.((int) $this->id);
2898
2899 if ($this->db->query($sql)) {
2900 $this->multicurrency_code = $code;
2901
2902 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2903 if ($rate) {
2904 $this->setMulticurrencyRate($rate, 2);
2905 }
2906
2907 return 1;
2908 } else {
2909 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2910 $this->error = $this->db->error();
2911 return -1;
2912 }
2913 } else {
2914 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2915 $this->error = 'Status of the object is incompatible '.$this->status;
2916 return -2;
2917 }
2918 }
2919
2927 public function setMulticurrencyRate($rate, $mode = 1)
2928 {
2929 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2930 if ($this->status >= 0 || $this->element == 'societe') {
2931 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2932 $sql .= " SET multicurrency_tx = ".((float) $rate);
2933 $sql .= ' WHERE rowid='.((int) $this->id);
2934
2935 if ($this->db->query($sql)) {
2936 $this->multicurrency_tx = $rate;
2937
2938 // Update line price
2939 if (!empty($this->lines)) {
2940 foreach ($this->lines as &$line) {
2941 // Amounts in company currency will be recalculated
2942 if ($mode == 1) {
2943 $line->subprice = 0;
2944 }
2945
2946 // Amounts in foreign currency will be recalculated
2947 if ($mode == 2) {
2948 $line->multicurrency_subprice = 0;
2949 }
2950
2951 switch ($this->element) {
2952 case 'propal':
2955 '@phan-var-force Propal $this';
2956 '@phan-var-force PropaleLigne $line';
2957 $this->updateline(
2958 $line->id,
2959 $line->subprice,
2960 $line->qty,
2961 $line->remise_percent,
2962 $line->tva_tx,
2963 $line->localtax1_tx,
2964 $line->localtax2_tx,
2965 ($line->description ? $line->description : $line->desc),
2966 'HT',
2967 $line->info_bits,
2968 $line->special_code,
2969 $line->fk_parent_line,
2970 $line->skip_update_total,
2971 $line->fk_fournprice,
2972 $line->pa_ht,
2973 $line->label,
2974 $line->product_type,
2975 $line->date_start,
2976 $line->date_end,
2977 $line->array_options,
2978 $line->fk_unit,
2979 $line->multicurrency_subprice
2980 );
2981 break;
2982 case 'commande':
2985 '@phan-var-force Commande $this';
2986 '@phan-var-force OrderLine $line';
2987 $this->updateline(
2988 $line->id,
2989 ($line->description ? $line->description : $line->desc),
2990 $line->subprice,
2991 $line->qty,
2992 $line->remise_percent,
2993 $line->tva_tx,
2994 $line->localtax1_tx,
2995 $line->localtax2_tx,
2996 'HT',
2997 $line->info_bits,
2998 $line->date_start,
2999 $line->date_end,
3000 $line->product_type,
3001 $line->fk_parent_line,
3002 $line->skip_update_total,
3003 $line->fk_fournprice,
3004 $line->pa_ht,
3005 $line->label,
3006 $line->special_code,
3007 $line->array_options,
3008 $line->fk_unit,
3009 $line->multicurrency_subprice
3010 );
3011 break;
3012 case 'facture':
3015 '@phan-var-force Facture $this';
3016 '@phan-var-force FactureLigne $line';
3017 $this->updateline(
3018 $line->id,
3019 ($line->description ? $line->description : $line->desc),
3020 $line->subprice,
3021 $line->qty,
3022 $line->remise_percent,
3023 $line->date_start,
3024 $line->date_end,
3025 $line->tva_tx,
3026 $line->localtax1_tx,
3027 $line->localtax2_tx,
3028 'HT',
3029 $line->info_bits,
3030 $line->product_type,
3031 $line->fk_parent_line,
3032 $line->skip_update_total,
3033 $line->fk_fournprice,
3034 $line->pa_ht,
3035 $line->label,
3036 $line->special_code,
3037 $line->array_options,
3038 $line->situation_percent,
3039 $line->fk_unit,
3040 $line->multicurrency_subprice
3041 );
3042 break;
3043 case 'facturerec':
3046 '@phan-var-force FactureRec $this';
3047 '@phan-var-force FactureLigneRec $line';
3048 $this->updateline(
3049 $line->id,
3050 ($line->description ? $line->description : $line->desc),
3051 $line->subprice,
3052 $line->qty,
3053 $line->tva_tx,
3054 $line->localtax1_tx,
3055 $line->localtax2_tx,
3056 $line->fk_product,
3057 $line->remise_percent,
3058 'HT',
3059 $line->info_bits,
3060 0,
3061 0,
3062 $line->product_type,
3063 $line->rang,
3064 $line->special_code,
3065 $line->label,
3066 $line->fk_unit,
3067 $line->multicurrency_subprice,
3068 0,
3069 $line->date_start,
3070 $line->date_end,
3071 $line->fk_fournprice,
3072 $line->pa_ht,
3073 $line->fk_parent_line
3074 );
3075 break;
3076 case 'supplier_proposal':
3079 '@phan-var-force SupplierProposal $this';
3080 '@phan-var-force SupplierProposalLine $line';
3081 $this->updateline(
3082 $line->id,
3083 $line->subprice,
3084 $line->qty,
3085 $line->remise_percent,
3086 $line->tva_tx,
3087 $line->localtax1_tx,
3088 $line->localtax2_tx,
3089 ($line->description ? $line->description : $line->desc),
3090 'HT',
3091 $line->info_bits,
3092 $line->special_code,
3093 $line->fk_parent_line,
3094 $line->skip_update_total,
3095 $line->fk_fournprice,
3096 $line->pa_ht,
3097 $line->label,
3098 $line->product_type,
3099 $line->array_options,
3100 $line->ref_fourn,
3101 (int) $line->fk_unit,
3102 $line->multicurrency_subprice
3103 );
3104 break;
3105 case 'order_supplier':
3108 '@phan-var-force CommandeFournisseur $this';
3109 '@phan-var-force CommandeFournisseurLigne $line';
3110 $this->updateline(
3111 $line->id,
3112 ($line->description ? $line->description : $line->desc),
3113 $line->subprice,
3114 $line->qty,
3115 $line->remise_percent,
3116 $line->tva_tx,
3117 $line->localtax1_tx,
3118 $line->localtax2_tx,
3119 'HT',
3120 $line->info_bits,
3121 $line->product_type,
3122 0,
3123 $line->date_start,
3124 $line->date_end,
3125 $line->array_options,
3126 $line->fk_unit,
3127 $line->multicurrency_subprice,
3128 $line->ref_supplier
3129 );
3130 break;
3131 case 'invoice_supplier':
3134 '@phan-var-force FactureFournisseur $this';
3135 '@phan-var-force SupplierInvoiceLIne $line';
3136 $this->updateline(
3137 $line->id,
3138 ($line->description ? $line->description : $line->desc),
3139 $line->subprice,
3140 $line->tva_tx,
3141 $line->localtax1_tx,
3142 $line->localtax2_tx,
3143 $line->qty,
3144 0,
3145 'HT',
3146 $line->info_bits,
3147 $line->product_type,
3148 $line->remise_percent,
3149 0,
3150 $line->date_start,
3151 $line->date_end,
3152 $line->array_options,
3153 $line->fk_unit,
3154 $line->multicurrency_subprice,
3155 $line->ref_supplier
3156 );
3157 break;
3158 default:
3159 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3160 break;
3161 }
3162 }
3163 }
3164
3165 return 1;
3166 } else {
3167 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3168 $this->error = $this->db->error();
3169 return -1;
3170 }
3171 } else {
3172 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3173 $this->error = 'Status of the object is incompatible '.$this->status;
3174 return -2;
3175 }
3176 }
3177
3185 public function setPaymentTerms($id, $deposit_percent = null)
3186 {
3187 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.formatLogObject($deposit_percent).')');
3188 if ($this->status >= 0 || $this->element == 'societe') {
3189 // TODO uniformize field name
3190 $fieldname = 'fk_cond_reglement';
3191 if ($this->element == 'societe') {
3192 $fieldname = 'cond_reglement';
3193 }
3194 if (get_class($this) == 'Fournisseur') {
3195 $fieldname = 'cond_reglement_supplier';
3196 }
3197
3198 if (empty($deposit_percent) || $deposit_percent < 0) {
3199 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3200 }
3201
3202 if ($deposit_percent > 100) {
3203 $deposit_percent = 100;
3204 }
3205
3206 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3207 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3208 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3209 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3210 }
3211 $sql .= ' WHERE rowid = '.((int) $this->id);
3212
3213 if ($this->db->query($sql)) {
3214 $this->cond_reglement_id = $id;
3215 // for supplier
3216 if (get_class($this) == 'Fournisseur') {
3217 $this->cond_reglement_supplier_id = $id;
3218 }
3219 $this->deposit_percent = $deposit_percent;
3220 return 1;
3221 } else {
3222 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3223 $this->error = $this->db->error();
3224 return -1;
3225 }
3226 } else {
3227 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3228 $this->error = 'Status of the object is incompatible '.$this->status;
3229 return -2;
3230 }
3231 }
3232
3239 public function setTransportMode($id)
3240 {
3241 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3242 if ($this->status >= 0 || $this->element == 'societe') {
3243 $fieldname = 'fk_transport_mode';
3244 if ($this->element == 'societe') {
3245 $fieldname = 'transport_mode';
3246 }
3247 if (get_class($this) == 'Fournisseur') {
3248 $fieldname = 'transport_mode_supplier';
3249 }
3250
3251 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3252 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3253 $sql .= ' WHERE rowid='.((int) $this->id);
3254
3255 if ($this->db->query($sql)) {
3256 $this->transport_mode_id = $id;
3257 // for supplier
3258 if (get_class($this) == 'Fournisseur') {
3259 $this->transport_mode_supplier_id = $id;
3260 }
3261 return 1;
3262 } else {
3263 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3264 $this->error = $this->db->error();
3265 return -1;
3266 }
3267 } else {
3268 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3269 $this->error = 'Status of the object is incompatible '.$this->status;
3270 return -2;
3271 }
3272 }
3273
3281 public function setDeliveryAddress($id)
3282 {
3283 $fieldname = 'fk_delivery_address';
3284 if ($this->element == 'delivery' || $this->element == 'shipping') {
3285 $fieldname = 'fk_address';
3286 }
3287
3288 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$this->db->sanitize($fieldname)." = ".((int) $id);
3289 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3290
3291 if ($this->db->query($sql)) {
3292 $this->fk_delivery_address = $id;
3293 return 1;
3294 } else {
3295 $this->error = $this->db->error();
3296 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3297 return -1;
3298 }
3299 }
3300
3301
3310 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3311 {
3312 global $user;
3313
3314 if (empty($userused)) {
3315 $userused = $user;
3316 }
3317
3318 $error = 0;
3319
3320 if (!$this->table_element) {
3321 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3322 return -1;
3323 }
3324
3325 $this->db->begin();
3326
3327 if ($shipping_method_id < 0) {
3328 $shipping_method_id = 'NULL';
3329 }
3330 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3331
3332 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3333 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3334 $sql .= " WHERE rowid=".((int) $this->id);
3335 $resql = $this->db->query($sql);
3336 if (!$resql) {
3337 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3338 $this->error = $this->db->lasterror();
3339 $error++;
3340 } else {
3341 if (!$notrigger) {
3342 // Call trigger
3343 $this->context = array('shippingmethodupdate' => 1);
3344 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3345 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3346 if ($result < 0) {
3347 $error++;
3348 }
3349 // End call trigger
3350 }
3351 }
3352 if ($error) {
3353 $this->db->rollback();
3354 return -1;
3355 } else {
3356 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3357 $this->db->commit();
3358 return 1;
3359 }
3360 }
3361
3362
3369 public function setWarehouse($warehouse_id)
3370 {
3371 if (!$this->table_element) {
3372 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3373 return -1;
3374 }
3375 if ($warehouse_id < 0) {
3376 $warehouse_id = 'NULL';
3377 }
3378 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3379
3380 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3381 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3382 $sql .= " WHERE rowid=".((int) $this->id);
3383
3384 if ($this->db->query($sql)) {
3385 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3386 return 1;
3387 } else {
3388 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3389 $this->error = $this->db->error();
3390 return 0;
3391 }
3392 }
3393
3394
3402 public function setDocModel($user, $modelpdf)
3403 {
3404 if (!$this->table_element) {
3405 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3406 return -1;
3407 }
3408
3409 $newmodelpdf = dol_trunc($modelpdf, 255);
3410
3411 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3412 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3413 $sql .= " WHERE rowid = ".((int) $this->id);
3414
3415 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3416 $resql = $this->db->query($sql);
3417 if ($resql) {
3418 $this->model_pdf = $modelpdf;
3419 return 1;
3420 } else {
3421 dol_print_error($this->db);
3422 return 0;
3423 }
3424 }
3425
3426
3435 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3436 {
3437 global $user;
3438
3439 if (empty($userused)) {
3440 $userused = $user;
3441 }
3442
3443 $error = 0;
3444
3445 if (!$this->table_element) {
3446 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3447 return -1;
3448 }
3449 $this->db->begin();
3450
3451 if ($fk_account < 0) {
3452 $fk_account = 'NULL';
3453 }
3454 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3455
3456 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3457 $sql .= " SET fk_account = ".((int) $fk_account);
3458 $sql .= " WHERE rowid=".((int) $this->id);
3459
3460 $resql = $this->db->query($sql);
3461 if (!$resql) {
3462 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3463 $this->error = $this->db->lasterror();
3464 $error++;
3465 } else {
3466 if (!$notrigger) {
3467 // Call trigger
3468 $this->context['bankaccountupdate'] = 1;
3469
3470 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3471 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3472 if ($result < 0) {
3473 $error++;
3474 }
3475 // End call trigger
3476 }
3477 }
3478 if ($error) {
3479 $this->db->rollback();
3480 return -1;
3481 } else {
3482 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3483 $this->db->commit();
3484 return 1;
3485 }
3486 }
3487
3488
3489 // TODO: Move line related operations to CommonObjectLine?
3490
3491 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3501 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3502 {
3503 // phpcs:enable
3504 if (!$this->table_element_line) {
3505 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3506 return -1;
3507 }
3508 if (!$this->fk_element) {
3509 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3510 return -1;
3511 }
3512
3513 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3514 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3515 $fieldposition = 'position';
3516 }
3517
3518 // Count number of lines to reorder (according to choice $renum)
3519 $nl = 0;
3520 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3521 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3522 if (!$renum) {
3523 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = 0";
3524 }
3525 if ($renum) {
3526 $sql .= " AND " . $this->db->sanitize($fieldposition) . " <> 0";
3527 }
3528
3529 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3530 $resql = $this->db->query($sql);
3531 if ($resql) {
3532 $row = $this->db->fetch_row($resql);
3533 $nl = $row[0];
3534 } else {
3535 dol_print_error($this->db);
3536 }
3537 if ($nl > 0) {
3538 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3539 $rows = array();
3540
3541 // We first search all lines that are parent lines (for multilevel details lines)
3542 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3543 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3544 if ($fk_parent_line) {
3545 $sql .= ' AND fk_parent_line IS NULL';
3546 }
3547 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC, rowid " . $this->db->sanitize($rowidorder);
3548
3549 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3550 $resql = $this->db->query($sql);
3551 if ($resql) {
3552 $i = 0;
3553 $num = $this->db->num_rows($resql);
3554 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3555 while ($i < $num) {
3556 $row = $this->db->fetch_row($resql);
3557 $rows[] = $row[0]; // Add parent line into array rows
3558 if ($fk_parent_line) {
3559 $children = $this->getChildrenOfLine($row[0], $grandchild);
3560 }
3561 if (!empty($children)) {
3562 foreach ($children as $child) {
3563 array_push($rows, $child);
3564 }
3565 }
3566 $i++;
3567 }
3568
3569 // Now we set a new number for each lines (parent and children with children included into parent tree)
3570 if (!empty($rows)) {
3571 foreach ($rows as $key => $row) {
3572 $this->updateRangOfLine($row, ($key + 1));
3573 }
3574 }
3575 } else {
3576 dol_print_error($this->db);
3577 }
3578 }
3579 return 1;
3580 }
3581
3589 public function getChildrenOfLine($id, $includealltree = 0)
3590 {
3591 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3592 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3593 $fieldposition = 'position';
3594 }
3595
3596 $rows = array();
3597
3598 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3599 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3600 $sql .= ' AND fk_parent_line = '.((int) $id);
3601 $sql .= " ORDER BY " . $this->db->sanitize($fieldposition) . " ASC";
3602
3603 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3604
3605 $resql = $this->db->query($sql);
3606 if (!$resql || $this->db->num_rows($resql) <= 0) {
3607 return array();
3608 }
3609
3610 while ($row = $this->db->fetch_row($resql)) {
3611 $rows[] = $row[0];
3612 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3613 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3614 }
3615 }
3616 return $rows;
3617 }
3618
3619 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3627 public function line_up($rowid, $fk_parent_line = true)
3628 {
3629 // phpcs:enable
3630 $this->line_order(false, 'ASC', $fk_parent_line);
3631
3632 // Get rang of line
3633 $rang = $this->getRangOfLine($rowid);
3634
3635 // Update position of line
3636 $this->updateLineUp($rowid, $rang);
3637 }
3638
3639 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3647 public function line_down($rowid, $fk_parent_line = true)
3648 {
3649 // phpcs:enable
3650 $this->line_order(false, 'ASC', $fk_parent_line);
3651
3652 // Get rang of line
3653 $rang = $this->getRangOfLine($rowid);
3654
3655 // Get max value for rang
3656 $max = $this->line_max();
3657
3658 // Update position of line
3659 $this->updateLineDown($rowid, $rang, $max);
3660 }
3661
3669 public function updateRangOfLine($rowid, $rang)
3670 {
3671 global $hookmanager;
3672 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3673 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3674 $fieldposition = 'position';
3675 }
3676
3677 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3678 $sql .= ' WHERE rowid = '.((int) $rowid);
3679
3680 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3681 if (!$this->db->query($sql)) {
3682 dol_print_error($this->db);
3683 return -1;
3684 }
3685
3686 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3687 $action = '';
3688 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3689 return 1;
3690 }
3691
3692 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3699 public function line_ajaxorder($rows)
3700 {
3701 // phpcs:enable
3702 $num = count($rows);
3703 for ($i = 0; $i < $num; $i++) {
3704 $this->updateRangOfLine($rows[$i], ($i + 1));
3705 }
3706 }
3707
3715 public function updateLineUp($rowid, $rang)
3716 {
3717 if ($rang <= 1) {
3718 return -1;
3719 }
3720
3721 $fieldposition = 'rang';
3722 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3723 $fieldposition = 'position';
3724 }
3725
3726 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3727 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3728 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang - 1));
3729 if (!$this->db->query($sql)) {
3730 $this->error = $this->db->lasterror();
3731 return -1;
3732 }
3733
3734 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang - 1));
3735 $sql .= ' WHERE rowid = '.((int) $rowid);
3736 if (!$this->db->query($sql)) {
3737 $this->error = $this->db->lasterror();
3738 return -1;
3739 }
3740
3741 return 1;
3742 }
3743
3752 public function updateLineDown($rowid, $rang, $max)
3753 {
3754 if ($rang >= $max) {
3755 return -1;
3756 }
3757
3758 $fieldposition = 'rang';
3759 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3760 $fieldposition = 'position';
3761 }
3762
3763 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($this->table_element_line)." SET ".$this->db->sanitize($fieldposition)." = ".((int) $rang);
3764 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3765 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = " . ((int) ($rang + 1));
3766 if (!$this->db->query($sql)) {
3767 $this->error = $this->db->lasterror();
3768 return -1;
3769 }
3770
3771 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$this->db->sanitize($fieldposition)." = ".((int) ($rang + 1));
3772 $sql .= ' WHERE rowid = '.((int) $rowid);
3773 if (!$this->db->query($sql)) {
3774 $this->error = $this->db->lasterror();
3775 return -1;
3776 }
3777
3778 return 1;
3779 }
3780
3787 public function getRangOfLine($rowid)
3788 {
3789 $fieldposition = 'rang';
3790 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3791 $fieldposition = 'position';
3792 }
3793
3794 $sql = "SELECT " . $this->db->sanitize($fieldposition) . " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3795 $sql .= " WHERE rowid = ".((int) $rowid);
3796
3797 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3798 $resql = $this->db->query($sql);
3799 if (!$resql) {
3800 return 0;
3801 }
3802
3803 $row = $this->db->fetch_row($resql);
3804 return $row[0];
3805 }
3806
3813 public function getIdOfLine($rang)
3814 {
3815 $fieldposition = 'rang';
3816 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3817 $fieldposition = 'position';
3818 }
3819
3820 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3821 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3822 $sql .= " AND " . $this->db->sanitize($fieldposition) . " = ".((int) $rang);
3823 $resql = $this->db->query($sql);
3824 if (!$resql) {
3825 return 0;
3826 }
3827
3828 $row = $this->db->fetch_row($resql);
3829 return $row[0];
3830 }
3831
3832 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3839 public function line_max($fk_parent_line = 0)
3840 {
3841 // phpcs:enable
3842 $positionfield = 'rang';
3843 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3844 $positionfield = 'position';
3845 }
3846
3847 $sql = "SELECT max(".$this->db->sanitize($positionfield).") FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
3848 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3849
3850 // Search the last rang with fk_parent_line
3851 if ($fk_parent_line) {
3852 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3853 }
3854
3855 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3856 $resql = $this->db->query($sql);
3857 if ($resql) {
3858 $row = $this->db->fetch_row($resql);
3859
3860 if ($fk_parent_line) {
3861 if (!empty($row[0])) {
3862 return $row[0];
3863 } else {
3864 return $this->getRangOfLine($fk_parent_line);
3865 }
3866 } else {
3867 return $row[0];
3868 }
3869 }
3870
3871 return 0;
3872 }
3873
3874 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3881 public function update_ref_ext($ref_ext)
3882 {
3883 // phpcs:enable
3884 if (!$this->table_element) {
3885 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3886 return -1;
3887 }
3888
3889 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3890 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3891 // @phan-suppress-next-line PhanUndeclaredProperty
3892 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3893
3894 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3895 if ($this->db->query($sql)) {
3896 $this->ref_ext = $ref_ext;
3897 return 1;
3898 } else {
3899 $this->error = $this->db->error();
3900 return -1;
3901 }
3902 }
3903
3904 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3913 public function update_note($note, $suffix = '', $notrigger = 0)
3914 {
3915 // phpcs:enable
3916 global $user;
3917
3918 if (!$this->table_element) {
3919 $this->error = 'update_note was called on object with property table_element not defined';
3920 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3921 return -1;
3922 }
3923 if (!in_array($suffix, array('', '_public', '_private'))) {
3924 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3925 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3926 return -2;
3927 }
3928
3929 $newsuffix = $suffix;
3930
3931 // Special case
3932 if ($this->table_element == 'product' && $newsuffix == '_private') {
3933 $newsuffix = '';
3934 }
3935 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3936 $fieldusermod = "fk_user_mod";
3937 } elseif ($this->table_element == 'ecm_files') {
3938 $fieldusermod = "fk_user_m";
3939 } else {
3940 $fieldusermod = "fk_user_modif";
3941 }
3942 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3943 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3944 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3945 $sql .= " WHERE rowid = ".((int) $this->id);
3946
3947 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3948 if ($this->db->query($sql)) {
3949 if ($suffix == '_public') {
3950 $this->note_public = $note;
3951 } elseif ($suffix == '_private') {
3952 $this->note_private = $note;
3953 } else {
3954 $this->note = $note; // deprecated
3955 $this->note_private = $note;
3956 }
3957 if (empty($notrigger)) {
3958 // TODO Use the $this->TRIGGER_PREFIX when implemented
3959 switch ($this->element) {
3960 case 'societe':
3961 $triggerName = 'COMPANY_MODIFY';
3962 break;
3963 case 'facture':
3964 $triggerName = 'BILL_MODIFY';
3965 break;
3966 case 'invoice_supplier':
3967 $triggerName = 'BILL_SUPPLIER_MODIFY';
3968 break;
3969 case 'facturerec':
3970 $triggerName = 'BILLREC_MODIFIY';
3971 break;
3972 case 'expensereport':
3973 $triggerName = 'EXPENSE_REPORT_MODIFY';
3974 break;
3975 default:
3976 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
3977 }
3978 $ret = $this->call_trigger($triggerName, $user);
3979 if ($ret < 0) {
3980 return -1;
3981 }
3982 }
3983 return 1;
3984 } else {
3985 $this->error = $this->db->lasterror();
3986 return -1;
3987 }
3988 }
3989
3990 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3999 public function update_note_public($note)
4000 {
4001 // phpcs:enable
4002 return $this->update_note($note, '_public');
4003 }
4004
4005 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4016 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4017 {
4018 // phpcs:enable
4019 global $conf, $hookmanager, $action;
4020
4021 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4022 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4023 if ($reshook > 0) {
4024 return 1; // replacement code
4025 } elseif ($reshook < 0) {
4026 return -1; // failure
4027 } // reshook = 0 => execute normal code
4028
4029 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4030 $isElementForSupplier = false;
4031 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4032 $MODULE = "";
4033 if ($this->element == 'propal') {
4034 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4035 } elseif ($this->element == 'commande' || $this->element == 'order') {
4036 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4037 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4038 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4039 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4040 $isElementForSupplier = true;
4041 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4042 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4043 $isElementForSupplier = true;
4044 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4045 } elseif ($this->element == 'supplier_proposal') {
4046 $isElementForSupplier = true;
4047 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4048 }
4049 if ($isElementForSupplier) {
4050 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4051 }
4052
4053 if (!empty($MODULE)) {
4054 if (getDolGlobalString($MODULE)) {
4055 $modsactivated = explode(',', getDolGlobalString($MODULE));
4056 foreach ($modsactivated as $mod) {
4057 if (isModEnabled($mod)) {
4058 return 1; // update was disabled by specific setup
4059 }
4060 }
4061 }
4062 }
4063
4064 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4065
4066 $forcedroundingmode = $roundingadjust;
4067 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4068 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4069 } elseif ($forcedroundingmode == 'auto') {
4070 $forcedroundingmode = '0';
4071 }
4072
4073 $error = 0;
4074
4075 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4076
4077 // Define constants to find lines to sum (field name into the table_element_line not into table_element)
4078 $fieldtva = 'total_tva';
4079 $fieldlocaltax1 = 'total_localtax1';
4080 $fieldlocaltax2 = 'total_localtax2';
4081 $fieldup = 'subprice';
4082 $base_price_type = 'HT';
4083 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4084 $fieldtva = 'tva';
4085 $fieldup = 'pu_ht';
4086 }
4087 if ($this->element == 'invoice_supplier_rec') {
4088 $fieldup = 'pu_ht';
4089 }
4090 if ($this->element == 'expensereport') {
4091 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4092 // 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
4093 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4094 $forcedroundingmode = '0';
4095 $fieldup = 'value_unit';
4096 $base_price_type = 'TTC';
4097 }
4098
4099 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4100 $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,";
4101 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4102 $sql .= ' info_bits, product_type,';
4103 if ($this->element == 'expensereport') {
4104 $sql .= ' comments as description';
4105 } else {
4106 $sql .= ' description';
4107 }
4108 if ($this->table_element_line == 'facturedet') {
4109 $sql .= ', situation_percent';
4110 }
4111 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4112 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
4113 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
4114 if ($exclspec) {
4115 $product_field = 'product_type';
4116 if ($this->table_element_line == 'contratdet') {
4117 $product_field = ''; // contratdet table has no product_type field
4118 }
4119 if ($product_field) {
4120 $sql .= " AND ".$this->db->sanitize($product_field)." <> 9";
4121 }
4122 }
4123 $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
4124
4125 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4126
4127 $resql = $this->db->query($sql);
4128 if ($resql) {
4129 $this->total_ht = 0;
4130 $this->total_tva = 0;
4131 $this->total_localtax1 = 0;
4132 $this->total_localtax2 = 0;
4133 $this->total_ttc = 0;
4134 $total_ht_by_vats = array();
4135 $total_tva_by_vats = array();
4136 $total_ttc_by_vats = array();
4137 $this->multicurrency_total_ht = 0;
4138 $this->multicurrency_total_tva = 0;
4139 $this->multicurrency_total_ttc = 0;
4140
4141 $this->db->begin();
4142
4143 $num = $this->db->num_rows($resql);
4144 $i = 0;
4145 while ($i < $num) {
4146 $obj = $this->db->fetch_object($resql);
4147
4148 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4149 $parameters = array('fk_element' => $obj->rowid);
4150 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4151
4152 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'
4153 // This part of code is to fix data. We should not call it too often.
4154 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4155 $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);
4156
4157 $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.
4158 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4159 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4160 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4161
4162 if ($diff_on_current_total) {
4163 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4164 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4165 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4166 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4167 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4168 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);
4169 $resqlfix = $this->db->query($sqlfix);
4170 if (!$resqlfix) {
4171 dol_print_error($this->db, 'Failed to update line');
4172 }
4173 $obj->total_tva = $tmpcal[1];
4174 $obj->total_ttc = $tmpcal[2];
4175 $obj->multicurrency_total_tva = $tmpcal[17];
4176 $obj->multicurrency_total_ttc = $tmpcal[18];
4177 } elseif ($diff_when_using_price_ht) {
4178 // 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
4179 // 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),
4180 // so we continue only if price HT are same.
4181 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4182 // 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
4183 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4184 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4185 // 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,
4186 // 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.
4187 // 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.
4188 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4189 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4190 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4191 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4192
4193 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);
4194
4195 $resqlfix = $this->db->query($sqlfix);
4196 if (!$resqlfix) {
4197 dol_print_error($this->db, 'Failed to update line');
4198 }
4199 $obj->total_tva = $tmpcal[1];
4200 $obj->total_ttc = $tmpcal[2];
4201 $obj->multicurrency_total_tva = $tmpcal[17];
4202 $obj->multicurrency_total_ttc = $tmpcal[18];
4203 }
4204 }
4205 }
4206 }
4207
4208 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4209 $this->total_tva += $obj->total_tva;
4210 $this->total_localtax1 += $obj->total_localtax1;
4211 $this->total_localtax2 += $obj->total_localtax2;
4212 $this->total_ttc += $obj->total_ttc;
4213 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4214 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4215 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4216
4217 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4218 $total_ht_by_vats[$obj->vatrate] = 0;
4219 }
4220 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4221 $total_tva_by_vats[$obj->vatrate] = 0;
4222 }
4223 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4224 $total_ttc_by_vats[$obj->vatrate] = 0;
4225 }
4226 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4227 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4228 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4229
4230 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4231 if ($base_price_type == 'TTC') {
4232 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4233 } else {
4234 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4235 }
4236 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4237 //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";
4238 if ($diff) {
4239 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4240 if (abs((float) $diff) > $maxdiff) {
4241 // If error is more than 10 times the accuracy of rounding. This should not happen.
4242 $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.';
4243 dol_syslog($errmsg, LOG_WARNING);
4244 $this->error = $errmsg;
4245 $error++;
4246 break;
4247 }
4248
4249 if ($base_price_type == 'TTC') {
4250 $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);
4251 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);
4252 } else {
4253 $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);
4254 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);
4255 }
4256
4257 $resqlfix = $this->db->query($sqlfix);
4258
4259 if (!$resqlfix) {
4260 dol_print_error($this->db, 'Failed to update line');
4261 }
4262
4263 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4264 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4265 if ($base_price_type == 'TTC') {
4266 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4267 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4268 } else {
4269 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4270 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4271 }
4272 }
4273 }
4274
4275 $i++;
4276 }
4277
4278 // Add revenue stamp to total
4279 // @phan-suppress-next-line PhanUndeclaredProperty
4280 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4281 // @phan-suppress-next-line PhanUndeclaredProperty
4282 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4283
4284 // Situations totals
4285 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
4286 '@phan-var-force Facture $this';
4287 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
4288 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4289 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4290 $prev_sits = $this->get_prev_sits();
4291
4292 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4293 $this->total_ht -= $sit->total_ht;
4294 $this->total_tva -= $sit->total_tva;
4295 $this->total_localtax1 -= $sit->total_localtax1;
4296 $this->total_localtax2 -= $sit->total_localtax2;
4297 $this->total_ttc -= $sit->total_ttc;
4298 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4299 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4300 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4301 }
4302 }
4303 }
4304 }
4305
4306 // Clean total
4307 $this->total_ht = (float) price2num($this->total_ht);
4308 $this->total_tva = (float) price2num($this->total_tva);
4309 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4310 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4311 $this->total_ttc = (float) price2num($this->total_ttc);
4312
4313 $this->db->free($resql);
4314
4315 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4316 $fieldht = 'total_ht';
4317 $fieldtva = 'tva';
4318 $fieldlocaltax1 = 'localtax1';
4319 $fieldlocaltax2 = 'localtax2';
4320 $fieldttc = 'total_ttc';
4321 // Specific code for backward compatibility with old field names
4322 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4323 $fieldtva = 'total_tva';
4324 }
4325
4326 if (!$error && empty($nodatabaseupdate)) {
4327 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4328 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4329 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4330 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4331 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4332 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4333 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4334 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4335 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4336 $sql .= " WHERE rowid = ".((int) $this->id);
4337
4338 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4339 $resql = $this->db->query($sql);
4340
4341 if (!$resql) {
4342 $error++;
4343 $this->error = $this->db->lasterror();
4344 $this->errors[] = $this->db->lasterror();
4345 }
4346 }
4347
4348 if (!$error) {
4349 $this->db->commit();
4350 return 1;
4351 } else {
4352 $this->db->rollback();
4353 return -1;
4354 }
4355 } else {
4356 dol_print_error($this->db, 'Bad request in update_price');
4357 return -1;
4358 }
4359 }
4360
4361 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4372 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4373 {
4374 // phpcs:enable
4375 global $user, $hookmanager, $action;
4376 dol_syslog(__METHOD__, LOG_DEBUG);
4377
4378 if (empty($this->origin_type) && !empty($this->origin)) {
4379 $this->origin_type = $this->origin;
4380 }
4381 $origin = (!empty($origin) ? $origin : $this->origin_type);
4382 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4383 $f_user = isset($f_user) ? $f_user : $user;
4384
4385 // Special case
4386 if ($origin == 'order') {
4387 $origin = 'commande';
4388 }
4389 if ($origin == 'invoice') {
4390 $origin = 'facture';
4391 }
4392 if ($origin == 'invoice_template') {
4393 $origin = 'facturerec';
4394 }
4395 if ($origin == 'supplierorder') {
4396 $origin = 'order_supplier';
4397 }
4398
4399 // Add module part to target type
4400 $targettype = $this->getElementType();
4401
4402 $parameters = array('targettype' => $targettype);
4403 // Hook for explicitly set the targettype if it must be different than $this->element
4404 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4405 if ($reshook > 0) {
4406 if (!empty($hookmanager->resArray['targettype'])) {
4407 $targettype = $hookmanager->resArray['targettype'];
4408 }
4409 }
4410
4411 $this->db->begin();
4412 $error = 0;
4413
4414 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4415 $sql .= "fk_source";
4416 $sql .= ", sourcetype";
4417 $sql .= ", fk_target";
4418 $sql .= ", targettype";
4419 $sql .= ") VALUES (";
4420 $sql .= ((int) $origin_id);
4421 $sql .= ", '" . $this->db->escape($origin) . "'";
4422 $sql .= ", " . ((int) $this->id);
4423 $sql .= ", '" . $this->db->escape($targettype) . "'";
4424 $sql .= ")";
4425
4426 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4427 if ($this->db->query($sql)) {
4428 // If we link a supplier order to a supplier invoice that already uses discounts coming from other supplier invoices
4429 // (deposit invoice / credit note used as payment), also link those source invoices to the same supplier order.
4430 // This helps keep "linked elements" consistent when totals are impacted by such discounts.
4431 if ($origin === 'order_supplier' && $targettype === 'invoice_supplier' && $this->element === 'invoice_supplier' && (int) $origin_id > 0 && (int) $this->id > 0) {
4432 $sourceinvoices = array();
4433
4434 $sqlsources = "SELECT DISTINCT rc.fk_invoice_supplier_source as rowid";
4435 $sqlsources .= " FROM ".$this->db->prefix()."societe_remise_except as rc";
4436 $sqlsources .= " WHERE rc.fk_invoice_supplier = ".((int) $this->id);
4437 $sqlsources .= " AND rc.fk_invoice_supplier_source IS NOT NULL";
4438
4439 $resqlsources = $this->db->query($sqlsources);
4440 if ($resqlsources) {
4441 while ($objsrc = $this->db->fetch_object($resqlsources)) {
4442 $srcid = (int) $objsrc->rowid;
4443 if ($srcid > 0 && $srcid !== (int) $this->id) {
4444 $sourceinvoices[$srcid] = $srcid;
4445 }
4446 }
4447 } else {
4448 dol_syslog(get_class($this)."::add_object_linked Failed to read supplier invoice discounts sources", LOG_WARNING);
4449 }
4450
4451 if (!empty($sourceinvoices)) {
4452 $existing = array();
4453 $sourcelist = implode(',', $sourceinvoices);
4454
4455 $sqlexists = "SELECT fk_target";
4456 $sqlexists .= " FROM ".$this->db->prefix()."element_element";
4457 $sqlexists .= " WHERE fk_source = ".((int) $origin_id);
4458 $sqlexists .= " AND sourcetype = '".$this->db->escape($origin)."'";
4459 $sqlexists .= " AND targettype = '".$this->db->escape($targettype)."'";
4460 $sqlexists .= " AND fk_target IN (".$this->db->sanitize($sourcelist).")";
4461
4462 $resqlexists = $this->db->query($sqlexists);
4463 if ($resqlexists) {
4464 while ($objexist = $this->db->fetch_object($resqlexists)) {
4465 $existing[(int) $objexist->fk_target] = 1;
4466 }
4467 } else {
4468 dol_syslog(get_class($this)."::add_object_linked Failed to read existing linked supplier invoices", LOG_WARNING);
4469 }
4470
4471 foreach ($sourceinvoices as $srcid) {
4472 if (!empty($existing[(int) $srcid])) {
4473 continue;
4474 }
4475
4476 $sqladd = "INSERT INTO " . $this->db->prefix() . "element_element (fk_source, sourcetype, fk_target, targettype)";
4477 $sqladd .= " VALUES (".((int) $origin_id).", '".$this->db->escape($origin)."', ".((int) $srcid).", '".$this->db->escape($targettype)."')";
4478 $this->db->query($sqladd); // Best-effort: do not fail original link action
4479 }
4480 }
4481 }
4482
4483 if (!$notrigger) {
4484 // Call trigger
4485 $this->context['link_origin'] = $origin;
4486 $this->context['link_origin_id'] = $origin_id;
4487
4488 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4489 if ($result < 0) {
4490 $error++;
4491 }
4492 // End call triggers
4493 }
4494 } else {
4495 $this->error = $this->db->lasterror();
4496 $error++;
4497 }
4498
4499 if (!$error) {
4500 $this->db->commit();
4501 return 1;
4502 } else {
4503 $this->db->rollback();
4504 return 0;
4505 }
4506 }
4507
4513 public function getElementType()
4514 {
4515 // 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.
4516 // 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).
4517 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4518 // Add module part to target type if object has $module property and isn't in core modules.
4519 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4520 }
4521
4522
4545 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4546 {
4547 global $hookmanager, $action;
4548
4549 // Important for pdf generation time reduction
4550 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4551 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4552 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4553 return 1;
4554 }
4555
4556 $this->linkedObjectsIds = array();
4557 $this->linkedObjects = array();
4558
4559 // Hook for allowing modules to completely alter the behavior of the method
4560 $parameters = array(
4561 'sourceid' => $sourceid,
4562 'sourcetype' => $sourcetype,
4563 'targetid' => $targetid,
4564 'targettype' => $targettype,
4565 'clause' => $clause,
4566 'alsosametype' => $alsosametype,
4567 'orderby' => $orderby,
4568 'loadalsoobjects' => $loadalsoobjects
4569 );
4570 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4571 if ($reshook > 0) {
4572 return $reshook;
4573 }
4574
4575 $justsource = false;
4576 $justtarget = false;
4577 $withtargettype = false;
4578 $withsourcetype = false;
4579
4580 // Hook for explicitly set the targettype if it must be differtent than $this->element
4581 if (is_object($hookmanager)) {
4582 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4583 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4584 if ($reshook > 0) {
4585 if (!empty($hookmanager->resArray['sourcetype'])) {
4586 $sourcetype = $hookmanager->resArray['sourcetype'];
4587 }
4588 if (!empty($hookmanager->resArray['sourceid'])) {
4589 $sourceid = $hookmanager->resArray['sourceid'];
4590 }
4591 if (!empty($hookmanager->resArray['targettype'])) {
4592 $targettype = $hookmanager->resArray['targettype'];
4593 }
4594 if (!empty($hookmanager->resArray['targetid'])) {
4595 $targetid = $hookmanager->resArray['targetid'];
4596 }
4597 }
4598 }
4599
4600 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4601 $justsource = true; // the source (id and type) is a search criteria
4602 if (!empty($targettype)) {
4603 $withtargettype = true;
4604 }
4605 }
4606 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4607 $justtarget = true; // the target (id and type) is a search criteria
4608 if (!empty($sourcetype)) {
4609 $withsourcetype = true;
4610 }
4611 }
4612
4613 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4614 $targetid = (!empty($targetid) ? $targetid : $this->id);
4615 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4616 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4617
4618 /*if (empty($sourceid) && empty($targetid))
4619 {
4620 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4621 return -1;
4622 }*/
4623
4624 // Links between objects are stored in table element_element
4625 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4626 $sql .= " FROM ".$this->db->prefix()."element_element";
4627 $sql .= " WHERE ";
4628 if ($justsource || $justtarget) {
4629 if ($justsource) {
4630 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4631 if ($withtargettype) {
4632 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4633 }
4634 } elseif ($justtarget) {
4635 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4636 if ($withsourcetype) {
4637 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4638 }
4639 }
4640 } else {
4641 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4642 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4643 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4644 $this->linkedObjectsFullLoaded[$this->id] = true;
4645 }
4646 }
4647 $sql .= $this->db->order($orderby);
4648
4649 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4650
4651 $resql = $this->db->query($sql);
4652 if ($resql) {
4653 $num = $this->db->num_rows($resql);
4654 $i = 0;
4655 while ($i < $num) {
4656 $obj = $this->db->fetch_object($resql);
4657 if ($justsource || $justtarget) {
4658 if ($justsource) {
4659 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4660 } elseif ($justtarget) {
4661 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4662 }
4663 } else {
4664 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4665 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4666 }
4667 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4668 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4669 }
4670 }
4671 $i++;
4672 }
4673
4674 if (!empty($this->linkedObjectsIds)) {
4675 $tmparray = $this->linkedObjectsIds;
4676 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4677 $element_properties = getElementProperties($objecttype);
4678 $element = $element_properties['element'];
4679 $classPath = $element_properties['classpath'];
4680 $classFile = $element_properties['classfile'];
4681 $className = $element_properties['classname'];
4682 $module = $element_properties['module'];
4683
4684 // Here $module, $classFile and $className are set, we can use them.
4685 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4686 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4687 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4688 if (class_exists($className)) {
4689 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4690 $object = new $className($this->db);
4691 '@phan-var-force CommonObject $object';
4692 $ret = $object->fetch($objectid);
4693 if ($ret >= 0) {
4694 $this->linkedObjects[$objecttype][$i] = $object;
4695 }
4696 }
4697 }
4698 }
4699 } else {
4700 unset($this->linkedObjectsIds[$objecttype]);
4701 }
4702 }
4703 }
4704 return 1;
4705 } else {
4706 dol_print_error($this->db);
4707 return -1;
4708 }
4709 }
4710
4717 public function clearObjectLinkedCache()
4718 {
4719 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4720 unset($this->linkedObjectsFullLoaded[$this->id]);
4721 }
4722
4723 return 1;
4724 }
4725
4738 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4739 {
4740 global $user;
4741 $updatesource = false;
4742 $updatetarget = false;
4743 $f_user = isset($f_user) ? $f_user : $user;
4744 dol_syslog(__METHOD__, LOG_DEBUG);
4745
4746 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4747 $updatesource = true;
4748 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4749 $updatetarget = true;
4750 }
4751
4752 $this->db->begin();
4753 $error = 0;
4754
4755 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4756 if ($updatesource) {
4757 $sql .= "fk_source = " . ((int) $sourceid);
4758 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4759 $sql .= " WHERE fk_target = " . ((int) $this->id);
4760 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4761 } elseif ($updatetarget) {
4762 $sql .= "fk_target = " . ((int) $targetid);
4763 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4764 $sql .= " WHERE fk_source = " . ((int) $this->id);
4765 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4766 }
4767
4768 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4769 if ($this->db->query($sql)) {
4770 if (!$notrigger) {
4771 // Call trigger
4772 $this->context['link_source_id'] = $sourceid;
4773 $this->context['link_source_type'] = $sourcetype;
4774 $this->context['link_target_id'] = $targetid;
4775 $this->context['link_target_type'] = $targettype;
4776
4777 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4778 if ($result < 0) {
4779 $error++;
4780 }
4781 // End call triggers
4782 }
4783 } else {
4784 $this->error = $this->db->lasterror();
4785 $error++;
4786 }
4787
4788 if (!$error) {
4789 $this->db->commit();
4790 return 1;
4791 } else {
4792 $this->db->rollback();
4793 return -1;
4794 }
4795 }
4796
4810 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4811 {
4812 global $user;
4813 $deletesource = false;
4814 $deletetarget = false;
4815 $f_user = isset($f_user) ? $f_user : $user;
4816
4817 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4818 $deletesource = true;
4819 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4820 $deletetarget = true;
4821 }
4822
4823 $element = $this->getElementType();
4824 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4825 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4826 $targetid = (!empty($targetid) ? $targetid : $this->id);
4827 $targettype = (!empty($targettype) ? $targettype : $element);
4828 $this->db->begin();
4829 $error = 0;
4830
4831 if (!$notrigger) {
4832 // Call trigger
4833 $this->context['link_id'] = $rowid;
4834 $this->context['link_source_id'] = $sourceid;
4835 $this->context['link_source_type'] = $sourcetype;
4836 $this->context['link_target_id'] = $targetid;
4837 $this->context['link_target_type'] = $targettype;
4838
4839 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4840 if ($result < 0) {
4841 $error++;
4842 }
4843 // End call triggers
4844 }
4845
4846 if (!$error) {
4847 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4848 $sql .= " WHERE";
4849 if ($rowid > 0) {
4850 $sql .= " rowid = " . ((int) $rowid);
4851 } else {
4852 if ($deletesource) {
4853 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4854 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4855 } elseif ($deletetarget) {
4856 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4857 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4858 } else {
4859 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4860 $sql .= " OR";
4861 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4862 }
4863 }
4864
4865 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4866 if (!$this->db->query($sql)) {
4867 $this->error = $this->db->lasterror();
4868 $this->errors[] = $this->error;
4869 $error++;
4870 }
4871 }
4872
4873 if (!$error) {
4874 $this->db->commit();
4875 return 1;
4876 } else {
4877 $this->db->rollback();
4878 return 0;
4879 }
4880 }
4881
4891 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4892 {
4893 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4894 return -1;
4895 }
4896 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4897 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4898 }
4899
4900 global $db;
4901
4902 $sql = "SELECT ".$db->sanitize($field_select)." FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4903 $resql = $db->query($sql);
4904
4905 $TRes = array();
4906 if (!empty($resql)) {
4907 while ($res = $db->fetch_object($resql)) {
4908 $TRes[] = $res->{$field_select};
4909 }
4910 }
4911
4912 return $TRes;
4913 }
4914
4923 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4924 {
4925 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4926 return -1;
4927 }
4928
4929 global $db;
4930
4931 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$db->sanitize($table_element)." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4932 $resql = $db->query($sql);
4933 $n = 0;
4934 if ($resql) {
4935 $res = $db->fetch_object($resql);
4936 if ($res) {
4937 $n = $res->nb;
4938 }
4939 }
4940
4941 return $n;
4942 }
4943
4952 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4953 {
4954 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4955 return -1;
4956 }
4957
4958 global $db;
4959
4960 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$db->sanitize($field_where)." = ".((int) $fk_object_where);
4961 $resql = $db->query($sql);
4962
4963 if (empty($resql)) {
4964 return 0;
4965 }
4966
4967 return 1;
4968 }
4969
4981 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
4982 {
4983 global $user;
4984
4985 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4986
4987 $elementId = (!empty($elementId) ? $elementId : $this->id);
4988 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4989
4990 $this->db->begin();
4991
4992 if (empty($fieldstatus)) {
4993 if ($elementTable == 'facture_rec') {
4994 $fieldstatus = "suspended";
4995 }
4996 if ($elementTable == 'mailing') {
4997 $fieldstatus = "statut";
4998 }
4999 if ($elementTable == 'cronjob') {
5000 $fieldstatus = "status";
5001 }
5002 if ($elementTable == 'user') {
5003 $fieldstatus = "statut";
5004 }
5005 if ($elementTable == 'expensereport') {
5006 $fieldstatus = "fk_statut";
5007 }
5008 if ($elementTable == 'receptiondet_batch') {
5009 $fieldstatus = "status";
5010 }
5011 if ($elementTable == 'prelevement_bons') {
5012 $fieldstatus = "statut";
5013 }
5014 if ($elementTable == 'bank_account') {
5015 $fieldstatus = "clos";
5016 }
5017 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
5018 $fieldstatus = 'status';
5019 }
5020 // If still empty
5021 if (empty($fieldstatus)) {
5022 $fieldstatus = 'fk_statut';
5023 }
5024 }
5025
5026 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
5027 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
5028 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
5029 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
5030 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
5031 $sql .= ", fk_user_valid = ".((int) $user->id);
5032 }
5033 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
5034 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
5035 }
5036 if ($status == 1 && in_array($elementTable, array('inventory'))) {
5037 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
5038 }
5039 $sql .= " WHERE rowid = ".((int) $elementId);
5040 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
5041
5042 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
5043 $resql = $this->db->query($sql);
5044 if ($resql) {
5045 $error = 0;
5046
5047 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
5048
5049 if ($nb_rows_affected > 0) {
5050 if (empty($trigkey)) {
5051 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5052 if ($this->element == 'supplier_proposal' && $status == 2) {
5053 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5054 }
5055 if ($this->element == 'supplier_proposal' && $status == 3) {
5056 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5057 }
5058 if ($this->element == 'supplier_proposal' && $status == 4) {
5059 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5060 }
5061 if ($this->element == 'fichinter' && $status == 3) {
5062 $trigkey = 'FICHINTER_CLASSIFY_DONE';
5063 }
5064 if ($this->element == 'fichinter' && $status == 2) {
5065 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5066 }
5067 if ($this->element == 'fichinter' && $status == 1) {
5068 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5069 }
5070 }
5071
5072 $this->context = array_merge($this->context, array('newstatus' => $status));
5073
5074 if ($trigkey && $trigkey != 'none') {
5075 $this->oldcopy = dol_clone($this);
5076
5077 // Call trigger
5078 $result = $this->call_trigger($trigkey, $user);
5079 if ($result < 0) {
5080 $error++;
5081 }
5082 // End call triggers
5083 }
5084 } else {
5085 // The status was probably already good. We do nothing more, no triggers.
5086 }
5087
5088 if (!$error) {
5089 $this->db->commit();
5090
5091 if (empty($savElementId)) {
5092 // If the element we update is $this (so $elementId was provided as null)
5093 if ($fieldstatus == 'dispute_status') {
5094 $this->dispute_status = $status;
5095 } elseif ($fieldstatus == 'tosell') {
5096 $this->status = $status;
5097 } elseif ($fieldstatus == 'tobuy') {
5098 $this->status_buy = $status; // @phpstan-ignore-line
5099 } elseif ($fieldstatus == 'tobatch') {
5100 $this->status_batch = $status; // @phpstan-ignore-line
5101 } else {
5102 $this->status = $status;
5103 }
5104 }
5105
5106 return 1;
5107 } else {
5108 $this->db->rollback();
5109 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5110 return -1;
5111 }
5112 } else {
5113 $this->error = $this->db->lasterror();
5114 $this->db->rollback();
5115 return -1;
5116 }
5117 }
5118
5119
5127 public function getCanvas($id = 0, $ref = '')
5128 {
5129 if (empty($id) && empty($ref)) {
5130 return 0;
5131 }
5132 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5133 return 0; // To increase speed. Not enabled by default.
5134 }
5135
5136 // Clean parameters
5137 $ref = trim($ref);
5138
5139 $sql = "SELECT rowid, canvas";
5140 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5141 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5142 if (!empty($id)) {
5143 $sql .= " AND rowid = ".((int) $id);
5144 }
5145 if (!empty($ref)) {
5146 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5147 }
5148
5149 $resql = $this->db->query($sql);
5150 if ($resql) {
5151 $obj = $this->db->fetch_object($resql);
5152 if ($obj) {
5153 $this->canvas = $obj->canvas;
5154 return 1;
5155 } else {
5156 return 0;
5157 }
5158 } else {
5159 dol_print_error($this->db);
5160 return -1;
5161 }
5162 }
5163
5164
5171 public function getSpecialCode($lineid)
5172 {
5173 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
5174 $sql .= " WHERE rowid = ".((int) $lineid);
5175 $resql = $this->db->query($sql);
5176 if ($resql) {
5177 $row = $this->db->fetch_row($resql);
5178 return (!empty($row[0]) ? $row[0] : 0);
5179 }
5180
5181 return 0;
5182 }
5183
5192 public function isObjectUsed($id = 0, $entity = 0)
5193 {
5194 global $langs;
5195
5196 if (empty($id)) {
5197 $id = $this->id;
5198 }
5199
5200 // Check parameters
5201 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5202 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5203 return -1;
5204 }
5205
5206 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5207 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5208 $tmparray = array_keys($this->childtables);
5209 if (is_numeric($tmparray[0])) {
5210 $arraytoscan = array_flip($this->childtables);
5211 }
5212
5213 // Test on all child tables to scan if child exists
5214 $haschild = 0;
5215 foreach ($arraytoscan as $table => $element) {
5216 //print $id.'-'.$table.'-'.$elementname.'<br>';
5217
5218 // Check if module is enabled (to avoid error if tables of module not created)
5219 if (isset($element['enabled']) && !empty($element['enabled'])) {
5220 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5221 if (empty($enabled)) {
5222 continue;
5223 }
5224 }
5225
5226 // Check if element can be deleted
5227 $sql = "SELECT COUNT(*) as nb";
5228 $sql .= " FROM ".$this->db->prefix().$this->db->sanitize($table)." as c";
5229 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5230 $sql .= ", ".$this->db->prefix().$this->db->sanitize($element['parent'])." as p";
5231 }
5232 if (!empty($element['fk_element'])) {
5233 $sql .= " WHERE c.".$this->db->sanitize($element['fk_element'])." = ".((int) $id);
5234 } else {
5235 $sql .= " WHERE c.".$this->db->sanitize($this->fk_element)." = ".((int) $id);
5236 }
5237 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5238 $sql .= " AND c.".$this->db->sanitize($element['parentkey'])." = p.rowid";
5239 }
5240 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5241 $sql .= " AND c.".$this->db->sanitize($element['parenttypefield'])." = '".$this->db->escape($element['parenttypevalue'])."'";
5242 }
5243 if (!empty($entity)) {
5244 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5245 $sql .= " AND p.entity = ".((int) $entity);
5246 } else {
5247 $sql .= " AND c.entity = ".((int) $entity);
5248 }
5249 }
5250 //var_dump($table, $element, $sql);
5251
5252 $resql = $this->db->query($sql);
5253 if ($resql) {
5254 $obj = $this->db->fetch_object($resql);
5255 if ($obj->nb > 0) {
5256 $langs->load("errors");
5257 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5258 $haschild += (int) $obj->nb;
5259 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5260 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5261 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5262 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5263 } else { // new usage: $element['name']=Translation key
5264 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5265 }
5266 break; // We found at least one, we stop here
5267 }
5268 } else {
5269 $this->errors[] = $this->db->lasterror();
5270 return -1;
5271 }
5272 }
5273
5274 if ($haschild > 0) {
5275 $this->errors[] = "ErrorRecordHasChildren";
5276 return $haschild;
5277 } else {
5278 return 0;
5279 }
5280 }
5281
5288 public function hasProductsOrServices($predefined = -1)
5289 {
5290 $nb = 0;
5291
5292 foreach ($this->lines as $key => $val) {
5293 $qualified = 0;
5294 if ($predefined == -1) {
5295 $qualified = 1;
5296 }
5297 if ($predefined == 1 && $val->fk_product > 0) {
5298 $qualified = 1;
5299 }
5300 if ($predefined == 0 && $val->fk_product <= 0) {
5301 $qualified = 1;
5302 }
5303 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5304 $qualified = 1;
5305 }
5306 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5307 $qualified = 1;
5308 }
5309 if ($qualified) {
5310 $nb++;
5311 }
5312 }
5313 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5314 return $nb;
5315 }
5316
5322 public function getTotalDiscount()
5323 {
5324 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5325 $total_discount = 0.00;
5326
5327 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5328 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5329 $sql .= " WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
5330
5331 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5332 $resql = $this->db->query($sql);
5333 if ($resql) {
5334 $num = $this->db->num_rows($resql);
5335 $i = 0;
5336 while ($i < $num) {
5337 $obj = $this->db->fetch_object($resql);
5338
5339 $pu_ht = $obj->pu_ht;
5340 $qty = $obj->qty;
5341 $total_ht = $obj->total_ht;
5342
5343 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5344 $total_discount += $total_discount_line;
5345
5346 $i++;
5347 }
5348 }
5349
5350 //print $total_discount; exit;
5351 return (float) price2num($total_discount);
5352 }
5353
5354 return null;
5355 }
5356
5357
5364 public function getTotalWeightVolume()
5365 {
5366 $totalWeight = 0;
5367 $totalVolume = 0;
5368 // defined for shipment only
5369 $totalOrdered = 0;
5370 // defined for shipment only
5371 $totalToShip = 0;
5372
5373 if (empty($this->lines)) {
5374 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5375 }
5376
5377 foreach ($this->lines as $line) {
5378 if (isset($line->qty_asked)) {
5379 $totalOrdered += $line->qty_asked; // defined for shipment only
5380 }
5381 if (isset($line->qty_shipped)) {
5382 $totalToShip += $line->qty_shipped; // defined for shipment only
5383 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5384 if (empty($totalToShip)) {
5385 $totalToShip = 0;
5386 }
5387 $totalToShip += $line->qty; // defined for reception only
5388 }
5389
5390 // Define qty, weight, volume, weight_units, volume_units
5391 if ($this->element == 'shipping') {
5392 // for shipments
5393 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5394 } else {
5395 $qty = $line->qty ? $line->qty : 0;
5396 }
5397
5398 $weight = !empty($line->weight) ? $line->weight : 0;
5399 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5400 $volume = !empty($line->volume) ? $line->volume : 0;
5401 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5402
5403 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5404 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5405 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5406 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5407
5408 $weightUnit = 0;
5409 $volumeUnit = 0;
5410 if (!empty($weight_units)) {
5411 $weightUnit = $weight_units;
5412 }
5413 if (!empty($volume_units)) {
5414 $volumeUnit = $volume_units;
5415 }
5416
5417 if (empty($totalWeight)) {
5418 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5419 }
5420 if (empty($totalVolume)) {
5421 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5422 }
5423
5424 //var_dump($line->volume_units);
5425 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5426 $trueWeightUnit = pow(10, $weightUnit);
5427 $totalWeight += $weight * $qty * $trueWeightUnit;
5428 } else {
5429 if ($weight_units == 99) {
5430 // conversion 1 Pound = 0.45359237 KG
5431 $trueWeightUnit = 0.45359237;
5432 $totalWeight += $weight * $qty * $trueWeightUnit;
5433 } elseif ($weight_units == 98) {
5434 // conversion 1 Ounce = 0.0283495 KG
5435 $trueWeightUnit = 0.0283495;
5436 $totalWeight += $weight * $qty * $trueWeightUnit;
5437 } else {
5438 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5439 }
5440 }
5441 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5442 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5443 $trueVolumeUnit = pow(10, $volumeUnit);
5444 //print $line->volume;
5445 $totalVolume += $volume * $qty * $trueVolumeUnit;
5446 } else {
5447 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5448 }
5449 }
5450
5451 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5452 }
5453
5454
5460 public function setExtraParameters()
5461 {
5462 $this->db->begin();
5463
5464 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5465 $extraparams = dol_trunc($extraparams, 250);
5466
5467 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5468 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5469 $sql .= " WHERE rowid = ".((int) $this->id);
5470
5471 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5472 $resql = $this->db->query($sql);
5473 if (!$resql) {
5474 $this->error = $this->db->lasterror();
5475 $this->db->rollback();
5476 return -1;
5477 } else {
5478 $this->db->commit();
5479 return 1;
5480 }
5481 }
5482
5483
5484 // --------------------
5485 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5486 // --------------------
5487
5488 /* This is to show add lines */
5489
5499 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5500 {
5501 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5502
5503 // Line extrafield
5504 if (!is_object($extrafields)) {
5505 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5506 $extrafields = new ExtraFields($this->db);
5507 }
5508 $extrafields->fetch_name_optionals_label($this->table_element_line);
5509
5510 // Output template part (modules that overwrite templates must declare this into descriptor)
5511 // Use global variables + $dateSelector + $seller and $buyer
5512 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5513 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5514 foreach ($dirtpls as $module => $reldir) {
5515 if (!empty($module)) {
5516 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5517 } else {
5518 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5519 }
5520
5521 if (empty($conf->file->strict_mode)) {
5522 $res = @include $tpl;
5523 } else {
5524 $res = include $tpl; // for debug
5525 }
5526 if ($res) {
5527 break;
5528 }
5529 }
5530 }
5531
5532
5533
5534 /* This is to show array of line of details */
5535
5536
5551 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5552 {
5553 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5554 // TODO We should not use global var for this
5555 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5556
5557 // Define $usemargins (used by objectline_xxx.tpl.php files)
5558 $usemargins = 0;
5559 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5560 $usemargins = 1;
5561 }
5562
5563 $num = count($this->lines);
5564
5565 // Line extrafield
5566 if (!is_object($extrafields)) {
5567 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5568 $extrafields = new ExtraFields($this->db);
5569 }
5570 $extrafields->fetch_name_optionals_label($this->table_element_line);
5571
5572 if (method_exists($this, 'loadExpeditions')) {
5573 // 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.
5574 $this->loadExpeditions();
5575 }
5576
5577 $parameters = array();
5578 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5579 if (empty($reshook)) {
5580 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5581 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5582 if (empty($reshook)) {
5583 // Output template part (modules that overwrite templates must declare this into descriptor)
5584 // Use global variables + $dateSelector + $seller and $buyer
5585 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5586 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5587 foreach ($dirtpls as $module => $reldir) {
5588 $res = 0;
5589 if (!empty($module)) {
5590 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5591 } else {
5592 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5593 }
5594 if (file_exists($tpl)) {
5595 if (empty($conf->file->strict_mode)) {
5596 $res = @include $tpl;
5597 } else {
5598 $res = include $tpl; // for debug
5599 }
5600 }
5601 if ($res) {
5602 break;
5603 }
5604 }
5605 }
5606
5607 $i = 0;
5608
5609 print "<!-- begin printObjectLines() -->\n";
5610 foreach ($this->lines as $line) {
5611 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5612 //$line->fetch_optionals();
5613
5614 if (is_object($hookmanager)) {
5615 if (empty($line->fk_parent_line)) {
5616 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5617 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5618 } else {
5619 $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);
5620 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5621 }
5622 }
5623 if (empty($reshook)) {
5624 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5625 }
5626
5627 $i++;
5628 }
5629 print "<!-- end printObjectLines() -->\n";
5630 }
5631 }
5632
5650 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5651 {
5652 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5653 global $form;
5654 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5655
5656 // var used into tpl
5657 $text = '';
5658 $description = '';
5659
5660 // Line in view mode
5661 if ($action != 'editline' || $selected != $line->id) {
5662 // Product
5663 if (!empty($line->fk_product) && $line->fk_product > 0) {
5664 $product_static = new Product($this->db);
5665 $product_static->fetch($line->fk_product);
5666
5667 $product_static->ref = (string) $line->ref; //can change ref in hook
5668 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5669
5670 $text = $product_static->getNomUrl(1);
5671
5672 // Define output language and label
5673 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5674 // @phan-suppress-next-line PhanUndeclaredProperty
5675 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5676 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5677 return;
5678 }
5679
5680 $prod = new Product($this->db);
5681 $prod->fetch($line->fk_product);
5682
5683 $outputlangs = $langs;
5684 $newlang = '';
5685 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5686 $newlang = GETPOST('lang_id', 'aZ09');
5687 }
5688 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5689 $newlang = $this->thirdparty->default_lang; // To use language of customer
5690 }
5691 if (!empty($newlang)) {
5692 $outputlangs = new Translate("", $conf);
5693 $outputlangs->setDefaultLang($newlang);
5694 }
5695
5696 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5697 } else {
5698 $label = $line->product_label;
5699 }
5700
5701 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5702 $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.
5703 }
5704
5705 // Recalculate unit price with tax if not defined
5706 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)
5707 // So we calculate an estimated value just to show something on screen.
5708 // Not: the unit price is always for 100% of line, it is not a prorata of situation invoice (when total is)
5709 if ($line->remise_percent != 100) {
5710 $line->subprice_ttc = (float) price2num($line->total_ttc * ($line->situation_percent ? 100 / $line->situation_percent : 1) / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5711 } else {
5712 // Other method is less accurate
5713 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5714 }
5715 }
5716 $line->pu_ttc = $line->subprice_ttc; // deprecated
5717
5718 // Output template part (modules that overwrite templates must declare this into descriptor)
5719 // Use global variables + $dateSelector + $seller and $buyer
5720 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5721
5722 $qty_shipped = 0;
5723 if (isset($this->expeditions[$line->id])) {
5724 $qty_shipped = $this->expeditions[$line->id];
5725 }
5726 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5727
5728 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5729 foreach ($dirtpls as $module => $reldir) {
5730 $res = 0;
5731 if (!empty($module)) {
5732 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5733 } else {
5734 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5735 }
5736 //var_dump($tpl);
5737 if (file_exists($tpl)) {
5738 if (empty($conf->file->strict_mode)) {
5739 $res = @include $tpl;
5740 } else {
5741 $res = include $tpl; // for debug
5742 }
5743 }
5744 if ($res) {
5745 break;
5746 }
5747 }
5748 }
5749
5750 // Line in update mode
5751 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5752 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5753
5754 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5755 $line->pu_ttc = $line->subprice_ttc; // deprecated
5756
5757 // Output template part (modules that overwrite templates must declare this into descriptor)
5758 // Use global variables + $dateSelector + $seller and $buyer
5759 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5760 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5761 foreach ($dirtpls as $module => $reldir) {
5762 if (!empty($module)) {
5763 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5764 } else {
5765 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5766 }
5767
5768 if (empty($conf->file->strict_mode)) {
5769 $res = @include $tpl;
5770 } else {
5771 $res = include $tpl; // for debug
5772 }
5773 if ($res) {
5774 break;
5775 }
5776 }
5777 }
5778 }
5779
5780
5781 /* This is to show array of line of details of source object */
5782
5783
5794 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5795 {
5796 global $langs, $hookmanager, $form, $action;
5797
5798 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5799 print '<tr class="liste_titre">';
5800 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5801 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5802 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5803 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5804 if (isModEnabled("multicurrency")) {
5805 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5806 }
5807 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5808 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5809 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5810 }
5811 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5812 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5813 print '<td class="center">';
5814 print $form->showCheckAddButtons('checkforselect', 1);
5815 print '</td>';
5816 print '</tr>';
5817 $i = 0;
5818
5819 if (!empty($this->lines)) {
5820 foreach ($this->lines as $line) {
5821 $reshook = 0;
5822 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5823 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5824 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5825 if (!empty($line->fk_parent_line)) {
5826 $parameters['fk_parent_line'] = $line->fk_parent_line;
5827 }
5828 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5829 }
5830 if (empty($reshook)) {
5831 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5832 }
5833
5834 $i++;
5835 }
5836 }
5837 }
5838
5852 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5853 {
5854 global $langs, $conf;
5855
5856 //var_dump($line);
5857 if (!empty($line->date_start)) {
5858 // @phan-suppress-next-line PhanUndeclaredProperty
5859 $date_start = $line->date_start;
5860 } else {
5861 $date_start = $line->date_debut_prevue;
5862 if ($line->date_debut_reel) {
5863 $date_start = $line->date_debut_reel;
5864 }
5865 }
5866 if (!empty($line->date_end)) {
5867 // @phan-suppress-next-line PhanUndeclaredProperty
5868 $date_end = $line->date_end;
5869 } else {
5870 $date_end = $line->date_fin_prevue;
5871 if ($line->date_fin_reel) {
5872 $date_end = $line->date_fin_reel;
5873 }
5874 }
5875
5876 // Set thevalue into ->tpl[] array.
5877 $this->tpl['id'] = $line->id;
5878
5879 $this->tpl['label'] = '';
5880 if (!empty($line->fk_parent_line)) {
5881 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5882 }
5883
5884 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5885 $discount = new DiscountAbsolute($this->db);
5886 if (property_exists($this, 'socid')) {
5887 $discount->fk_soc = $this->socid;
5888 $discount->socid = $this->socid;
5889 }
5890 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5891 } elseif (!empty($line->fk_product)) {
5892 $productstatic = new Product($this->db);
5893 $productstatic->id = $line->fk_product;
5894 $productstatic->ref = (string) $line->ref;
5895 $productstatic->type = $line->fk_product_type;
5896 if (empty($productstatic->ref)) {
5897 $line->fetch_product();
5898 $productstatic = $line->product;
5899 }
5900
5901 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5902 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5903 // Dates
5904 if ($line->product_type == 1 && ($date_start || $date_end)) {
5905 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5906 }
5907 } else {
5908 $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"')));
5909 if (!empty($line->desc)) {
5910 $this->tpl['label'] .= $line->desc;
5911 } else {
5912 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5913 }
5914
5915 // Dates
5916 if ($line->product_type == 1 && ($date_start || $date_end)) {
5917 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5918 }
5919 }
5920
5921 if (!empty($line->desc)) {
5922 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5923 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5924 $discount = new DiscountAbsolute($this->db);
5925 $discount->fetch($line->fk_remise_except);
5926 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5927 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5928 $discount = new DiscountAbsolute($this->db);
5929 $discount->fetch($line->fk_remise_except);
5930 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5931 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5932 $discount = new DiscountAbsolute($this->db);
5933 $discount->fetch($line->fk_remise_except);
5934 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5935 } elseif ($line->desc == '(EXCESS PAID)') {
5936 $discount = new DiscountAbsolute($this->db);
5937 $discount->fetch($line->fk_remise_except);
5938 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5939 } else {
5940 $this->tpl['description'] = dol_trunc($line->desc, 60);
5941 }
5942 } else {
5943 $this->tpl['description'] = '&nbsp;';
5944 }
5945
5946 // VAT Rate
5947 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5948 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
5949 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5950 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5951 }
5952
5953 $this->tpl['price'] = price($line->subprice);
5954 $this->tpl['total_ht'] = price($line->total_ht);
5955 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5956 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5957 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5958 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
5959 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
5960 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
5961 }
5962 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
5963
5964 // Is the line strike or not
5965 $this->tpl['strike'] = 0;
5966 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5967 $this->tpl['strike'] = 1;
5968 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
5969 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
5970 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
5971 // Without the guard, printing origin lines from a PO into a vendor
5972 // invoice fatals (#37663). Other tpl files already follow this pattern.
5973 $this->tpl['strike'] = 1;
5974 }
5975
5976 // Output template part (modules that overwrite templates must declare this into descriptor)
5977 // Use global variables + $dateSelector + $seller and $buyer
5978 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5979 foreach ($dirtpls as $module => $reldir) {
5980 if (!empty($module)) {
5981 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5982 } else {
5983 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5984 }
5985
5986 if (empty($conf->file->strict_mode)) {
5987 $res = @include $tpl;
5988 } else {
5989 $res = include $tpl; // for debug
5990 }
5991 if ($res) {
5992 break;
5993 }
5994 }
5995 }
5996
5997
5998 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6010 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
6011 {
6012 // phpcs:enable
6013 global $user;
6014 $this->db->begin();
6015
6016 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
6017 $sql .= "resource_id";
6018 $sql .= ", resource_type";
6019 $sql .= ", element_id";
6020 $sql .= ", element_type";
6021 $sql .= ", busy";
6022 $sql .= ", mandatory";
6023 $sql .= ") VALUES (";
6024 $sql .= ((int) $resource_id);
6025 $sql .= ", '".$this->db->escape($resource_type)."'";
6026 $sql .= ", '".$this->db->escape((string) $this->id)."'";
6027 $sql .= ", '".$this->db->escape($this->element)."'";
6028 $sql .= ", '".$this->db->escape((string) $busy)."'";
6029 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
6030 $sql .= ")";
6031
6032 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
6033 if ($this->db->query($sql)) {
6034 if (!$notrigger) {
6035 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
6036 if ($result < 0) {
6037 $this->db->rollback();
6038 return -1;
6039 }
6040 }
6041 $this->db->commit();
6042 return 1;
6043 } else {
6044 $this->error = $this->db->lasterror();
6045 $this->db->rollback();
6046 return 0;
6047 }
6048 }
6049
6050 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6059 public function delete_resource($rowid, $element = '', $notrigger = 0)
6060 {
6061 // phpcs:enable
6062 global $user;
6063
6064 $this->db->begin();
6065
6066 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6067 $sql .= " WHERE rowid = ".((int) $rowid);
6068
6069 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6070
6071 $resql = $this->db->query($sql);
6072 if (!$resql) {
6073 $this->error = $this->db->lasterror();
6074 $this->db->rollback();
6075 return -1;
6076 } else {
6077 if (!$notrigger) {
6078 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6079 if ($result < 0) {
6080 $this->db->rollback();
6081 return -1;
6082 }
6083 }
6084 $this->db->commit();
6085 return 1;
6086 }
6087 }
6088
6089
6095 public function __clone()
6096 {
6097 // Force a copy of this->lines, otherwise it will point to same object.
6098 if (isset($this->lines) && is_array($this->lines)) {
6099 $nboflines = count($this->lines);
6100 for ($i = 0; $i < $nboflines; $i++) {
6101 if (is_object($this->lines[$i])) {
6102 $this->lines[$i] = clone $this->lines[$i];
6103 }
6104 }
6105 }
6106 }
6107
6121 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6122 {
6123 global $conf, $langs, $hookmanager, $action;
6124
6125 $srctemplatepath = '';
6126
6127 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6128 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6129
6130 if (!empty($reshook)) {
6131 return $reshook;
6132 }
6133
6134 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6135
6136 if (empty($modele)) {
6137 $this->error = 'BadValueForParameterModele';
6138 return -1;
6139 }
6140
6141 // Increase limit for PDF build
6142 $err = error_reporting();
6143 error_reporting(0);
6144 @set_time_limit(120);
6145 error_reporting($err);
6146
6147 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6148 $tmp = explode(':', $modele, 2);
6149 $saved_model = $modele;
6150 if (!empty($tmp[1])) {
6151 $modele = $tmp[0];
6152 $srctemplatepath = $tmp[1];
6153 }
6154
6155 // Search template files
6156 $file = '';
6157 $classname = '';
6158 $filefound = '';
6159 $dirmodels = array('/');
6160 if (is_array($conf->modules_parts['models'])) {
6161 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6162 }
6163 foreach ($dirmodels as $reldir) {
6164 foreach (array('doc', 'pdf') as $prefix) {
6165 if (in_array(get_class($this), array('Adherent'))) {
6166 // Member module use prefix_modele.class.php
6167 $file = $prefix."_".$modele.".class.php";
6168 } else {
6169 // Other module use prefix_modele.modules.php
6170 $file = $prefix."_".$modele.".modules.php";
6171 }
6172
6173 $file = dol_sanitizeFileName($file);
6174
6175 // We check if the file exists
6176 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6177 if (file_exists($file)) {
6178 $filefound = $file;
6179 $classname = $prefix.'_'.$modele;
6180 break;
6181 }
6182 }
6183 if ($filefound) {
6184 break;
6185 }
6186 }
6187
6188 if ($filefound === '' || $classname === '') {
6189 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6190 $this->errors[] = $this->error;
6191 dol_syslog($this->error, LOG_ERR);
6192 return -1;
6193 }
6194
6195 // Sanitize $filefound
6196 $filefound = dol_sanitizePathName($filefound);
6197
6198 // If generator was found
6199 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6200
6201 require_once $filefound;
6202
6203 $obj = new $classname($this->db);
6204
6205 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6206 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6207 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6208 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6209 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6210 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6211 // ModelePDFStock|ModelePDFStockTransfer|
6212 // ModeleDon|ModelePDFTask|
6213 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6214 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6215 '@phan-var-force ModelePDFMember $obj';
6216 // '@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';
6217
6218 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6219 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6220 $varfortemplatedir = $obj->scandir;
6221 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6222 $dirtoscan = getDolGlobalString($varfortemplatedir);
6223
6224 $listoffiles = array();
6225
6226 // Now we add first model found in directories scanned
6227 $listofdir = explode(',', $dirtoscan);
6228 foreach ($listofdir as $key => $tmpdir) {
6229 $tmpdir = trim($tmpdir);
6230 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6231 if (!$tmpdir) {
6232 unset($listofdir[$key]);
6233 continue;
6234 }
6235 if (is_dir($tmpdir)) {
6236 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6237 if (count($tmpfiles)) {
6238 $listoffiles = array_merge($listoffiles, $tmpfiles);
6239 }
6240 }
6241 }
6242
6243 if (count($listoffiles)) {
6244 foreach ($listoffiles as $record) {
6245 $srctemplatepath = $record['fullname'];
6246 break;
6247 }
6248 }
6249 }
6250
6251 if (empty($srctemplatepath)) {
6252 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6253 return -1;
6254 }
6255 }
6256
6257 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6258 if (!dol_is_file($srctemplatepath)) {
6259 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6260 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6261 return -1;
6262 }
6263 }
6264
6265 // We save charset_output to restore it because write_file can change it if needed for
6266 // output format that does not support UTF8.
6267 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6268
6269 // update model_pdf in object
6270 $this->model_pdf = $saved_model;
6271
6272 try {
6273 if ($obj instanceof ModelePDFMember) {
6274 if ($this instanceof Adherent) {
6275 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6276 } else {
6277 $resultwritefile = -1;
6278 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6279 }
6280 } elseif ($obj instanceof ModeleDon) {
6281 // Only 3 arguments
6282 if ($this instanceof Don) {
6283 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6284 } else {
6285 $resultwritefile = -1;
6286 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6287 }
6288 } else {
6289 // TODO: Try to set type above again
6290 '@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';
6291 $this->context['moreparams'] = $moreparams;
6292 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref); // @phan-suppress-current-line PhanTypeMismatchArgument
6293 }
6294 } catch (\Throwable $e) {
6295 $resultwritefile = -1;
6296 $this->error = $e->getMessage();
6297 dol_syslog("Exception in write_file() for ".get_class($this).": ".$e->getMessage(), LOG_ERR);
6298 }
6299 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6300
6301
6302 if ($resultwritefile > 0) {
6303 $outputlangs->charset_output = $sav_charset_output;
6304
6305 // We delete old preview
6306 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6307 dol_delete_preview($this);
6308
6309 // Index file in database
6310 if (!empty($obj->result['fullpath'])) {
6311 $destfull = $obj->result['fullpath'];
6312
6313 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6314 $update_main_doc_field = 0;
6315 if (!empty($obj->update_main_doc_field)) {
6316 $update_main_doc_field = 1;
6317 }
6318
6319 // Check that the file exists, before indexing it.
6320 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6321 if (dol_is_file($destfull)) {
6322 $this->indexFile($destfull, $update_main_doc_field);
6323 }
6324 } else {
6325 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);
6326 }
6327
6328 // Success in building document. We build meta file.
6329 dol_meta_create($this);
6330
6331 $this->warnings = $obj->warnings;
6332
6333 return 1;
6334 } else {
6335 $outputlangs->charset_output = $sav_charset_output;
6336 if (empty($this->error)) {
6337 $this->error = $obj->error;
6338 }
6339 if (empty($this->errors)) {
6340 $this->errors = $obj->errors;
6341 }
6342 $this->warnings = $obj->warnings;
6343 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$this->error, LOG_ERR);
6344 return -1;
6345 }
6346 }
6347
6357 public function indexFile($destfull, $update_main_doc_field)
6358 {
6359 global $conf, $user;
6360
6361 $upload_dir = dirname($destfull);
6362 $destfile = basename($destfull);
6363 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6364
6365 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6366 $filename = basename($destfile);
6367 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6368 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6369
6370 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6371 $ecmfile = new EcmFiles($this->db);
6372 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6373
6374 // Set the public "share" key
6375 $setsharekey = false;
6376 if (
6377 !empty($this->TRIGGER_PREFIX)
6378 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6379 ) {
6380 $setsharekey = true;
6381 }
6382 // TODO Remove case covered by trigger prefix
6383 if ($this->element == 'propal' || $this->element == 'proposal') {
6384 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6385 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6386 }
6387 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6388 $setsharekey = true;
6389 }
6390 }
6391 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6392 $setsharekey = true;
6393 }
6394 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6395 $setsharekey = true;
6396 }
6397 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6398 $setsharekey = true;
6399 }
6400 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6401 $setsharekey = true;
6402 }
6403 if ($this->element == 'order_supplier' && getDolGlobalInt("SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6404 $setsharekey = true;
6405 }
6406 if ($this->element == 'invoice_supplier' && getDolGlobalInt("SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6407 $setsharekey = true;
6408 }
6409 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6410 $setsharekey = true;
6411 }
6412
6413 if ($setsharekey) {
6414 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6415 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6416 $ecmfile->share = getRandomPassword(true);
6417 }
6418 }
6419
6420 if ($result > 0) {
6421 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6422 $ecmfile->fullpath_orig = '';
6423 $ecmfile->gen_or_uploaded = 'generated';
6424 $ecmfile->description = ''; // indexed content
6425 $ecmfile->keywords = ''; // keyword content
6426 $ecmfile->src_object_type = $this->table_element;
6427 $ecmfile->src_object_id = $this->id;
6428 $result = $ecmfile->update($user);
6429 if ($result < 0) {
6430 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6431 return -1;
6432 }
6433 } else {
6434 $ecmfile->entity = $conf->entity;
6435 $ecmfile->filepath = $rel_dir;
6436 $ecmfile->filename = $filename;
6437 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6438 $ecmfile->fullpath_orig = '';
6439 $ecmfile->gen_or_uploaded = 'generated';
6440 $ecmfile->description = ''; // indexed content
6441 $ecmfile->keywords = ''; // keyword content
6442 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6443 $ecmfile->src_object_id = $this->id;
6444
6445 $result = $ecmfile->create($user);
6446 if ($result < 0) {
6447 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6448 return -1;
6449 }
6450 }
6451
6452 /*$this->result['fullname']=$destfull;
6453 $this->result['filepath']=$ecmfile->filepath;
6454 $this->result['filename']=$ecmfile->filename;*/
6455 //var_dump($obj->update_main_doc_field);exit;
6456
6457 if ($update_main_doc_field && !empty($this->table_element)) {
6458 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6459 $sql .= " WHERE rowid = ".((int) $this->id);
6460
6461 $resql = $this->db->query($sql);
6462 if (!$resql) {
6463 dol_print_error($this->db);
6464 return -1;
6465 } else {
6466 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6467 }
6468 }
6469 }
6470
6471 return 1;
6472 }
6473
6482 public function addThumbs($file, $quality = 50)
6483 {
6484 $file_osencoded = dol_osencode($file);
6485
6486 if (file_exists($file_osencoded)) {
6487 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6488
6489 $tmparraysize = getDefaultImageSizes();
6490 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6491 $maxheightsmall = $tmparraysize['maxheightsmall'];
6492 $maxwidthmini = $tmparraysize['maxwidthmini'];
6493 $maxheightmini = $tmparraysize['maxheightmini'];
6494 //$quality = $tmparraysize['quality'];
6495
6496 // Create small thumbs for company (Ratio is near 16/9)
6497 // Used on logon for example
6498 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6499
6500 // Create mini thumbs for company (Ratio is near 16/9)
6501 // Used on menu or for setup page for example
6502 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6503 }
6504 }
6505
6513 public function delThumbs($file)
6514 {
6515 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6516 dol_delete_file($imgThumbName);
6517 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6518 dol_delete_file($imgThumbName);
6519 }
6520
6521
6522 /* Functions common to commonobject and commonobjectline */
6523
6524 /* For default values */
6525
6539 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6540 {
6541 /* TODO Remove this. Must use now something like:
6542 $note_private = GETPOST('note_private', 'restricthtml');
6543 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6544 $note_private = $objectsrc->note_private;
6545 }
6546 */
6547
6548 // If param here has been posted, we use this value first.
6549 if (GETPOSTISSET($fieldname)) {
6550 return GETPOST($fieldname, $type, 3);
6551 }
6552
6553 if (isset($alternatevalue)) {
6554 return $alternatevalue;
6555 }
6556
6557 $newelement = $this->element;
6558 if ($newelement == 'facture') {
6559 $newelement = 'invoice';
6560 }
6561 if ($newelement == 'commande') {
6562 $newelement = 'order';
6563 }
6564 if (empty($newelement)) {
6565 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6566 return '';
6567 }
6568
6569 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6570 //var_dump($keyforfieldname);
6571 if (getDolGlobalString($keyforfieldname)) {
6572 return getDolGlobalString($keyforfieldname);
6573 }
6574
6575 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6576 // store content into $conf->cache['overwrite_default']
6577
6578 return '';
6579 }
6580
6581
6582 /* Functions for data in other language */
6583
6584
6594 {
6595 // To avoid SQL errors. Probably not the better solution though
6596 if (!$this->element) {
6597 return 0;
6598 }
6599 if (!($this->id > 0)) {
6600 return 0;
6601 }
6602 if (is_array($this->array_languages)) {
6603 return 1;
6604 }
6605
6606 $this->array_languages = array();
6607
6608 $element = $this->element;
6609 if ($element == 'categorie') {
6610 $element = 'categories'; // For compatibility
6611 }
6612
6613 // Request to get translation values for object
6614 $sql = "SELECT rowid, property, lang , value";
6615 $sql .= " FROM ".$this->db->prefix()."object_lang";
6616 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6617 $sql .= " AND fk_object = ".((int) $this->id);
6618
6619 $resql = $this->db->query($sql);
6620 if ($resql) {
6621 $numrows = $this->db->num_rows($resql);
6622 if ($numrows) {
6623 $i = 0;
6624 while ($i < $numrows) {
6625 $obj = $this->db->fetch_object($resql);
6626 $key = $obj->property;
6627 $value = $obj->value;
6628 $codelang = $obj->lang;
6629 $type = $this->fields[$key]['type'];
6630
6631 // we can add this attribute to object
6632 if (preg_match('/date/', $type)) {
6633 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6634 } else {
6635 $this->array_languages[$key][$codelang] = $value;
6636 }
6637
6638 $i++;
6639 }
6640 }
6641
6642 $this->db->free($resql);
6643
6644 if ($numrows) {
6645 return $numrows;
6646 } else {
6647 return 0;
6648 }
6649 } else {
6650 dol_print_error($this->db);
6651 return -1;
6652 }
6653 }
6654
6662 public function setValuesForExtraLanguages($onlykey = '')
6663 {
6664 // Get extra fields
6665 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6666 $tmparray = explode('-', $postfieldkey);
6667 if ($tmparray[0] != 'field') {
6668 continue;
6669 }
6670
6671 $element = $tmparray[1];
6672 $key = $tmparray[2];
6673 $codelang = $tmparray[3];
6674 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6675
6676 if (!empty($onlykey) && $key != $onlykey) {
6677 continue;
6678 }
6679 if ($element != $this->element) {
6680 continue;
6681 }
6682
6683 $key_type = $this->fields[$key]['type'];
6684
6685 $enabled = 1;
6686 if (isset($this->fields[$key]['enabled'])) {
6687 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6688 }
6689 /*$perms = 1;
6690 if (isset($this->fields[$key]['perms']))
6691 {
6692 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6693 }*/
6694 if (empty($enabled)) {
6695 continue;
6696 }
6697 //if (empty($perms)) continue;
6698
6699 if (in_array($key_type, array('date'))) {
6700 // Clean parameters
6701 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6702 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6703 } elseif (in_array($key_type, array('datetime'))) {
6704 // Clean parameters
6705 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6706 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6707 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6708 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6709 if (!empty($value_arr)) {
6710 $value_key = implode(',', $value_arr);
6711 } else {
6712 $value_key = '';
6713 }
6714 } elseif (in_array($key_type, array('price', 'double'))) {
6715 $value_arr = GETPOST($postfieldkey, 'alpha');
6716 $value_key = price2num($value_arr);
6717 } else {
6718 $value_key = GETPOST($postfieldkey);
6719 if (in_array($key_type, array('link')) && $value_key == '-1') {
6720 $value_key = '';
6721 }
6722 }
6723
6724 $this->array_languages[$key][$codelang] = $value_key;
6725
6726 /*if ($nofillrequired) {
6727 $langs->load('errors');
6728 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6729 return -1;
6730 }*/
6731 }
6732
6733 return 1;
6734 }
6735
6736
6737 /* Functions for extrafields */
6738
6745 public function fetchNoCompute($id)
6746 {
6747 global $conf;
6748
6749 $savDisableCompute = $conf->disable_compute;
6750 $conf->disable_compute = 1;
6751
6752 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6753
6754 $conf->disable_compute = $savDisableCompute;
6755
6756 return $ret;
6757 }
6758
6759 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6769 public function fetch_optionals($rowid = null, $optionsArray = null)
6770 {
6771 // phpcs:enable
6772 global $conf, $extrafields;
6773
6774 if (empty($rowid)) {
6775 $rowid = $this->id;
6776 }
6777 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6778 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6779 }
6780
6781 // To avoid SQL errors. Probably not the better solution though
6782 if (!$this->table_element) {
6783 return 0;
6784 }
6785 $this->array_options = array();
6786
6787 if (!is_array($optionsArray)) {
6788 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6789 if (!isset($extrafields) || !is_object($extrafields)) {
6790 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6791 $extrafields = new ExtraFields($this->db);
6792 }
6793
6794 // Load array of extrafields for elementype = $this->table_element
6795 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6796 $extrafields->fetch_name_optionals_label($this->table_element);
6797 }
6798 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6799 } else {
6800 global $extrafields;
6801 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6802 }
6803
6804 $table_element = $this->table_element;
6805 if ($table_element == 'categorie') {
6806 $table_element = 'categories'; // For compatibility
6807 }
6808
6809 // Request to get complementary values
6810 if (is_array($optionsArray) && count($optionsArray) > 0) {
6811 $sql = "SELECT rowid";
6812 foreach ($optionsArray as $name => $label) {
6813 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6814 $sql .= ", ".$this->db->sanitize($name);
6815 }
6816 // use geo sql function to read as text
6817 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6818 // TODO Add an abstraction method in the database driver
6819 $sql .= ", ST_AsWKT(".$this->db->sanitize($name).") as ".$this->db->sanitize($name);
6820 }
6821 }
6822 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6823 $sql .= " WHERE fk_object = ".((int) $rowid);
6824
6825 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6826 $resql = $this->db->query($sql);
6827 if ($resql) {
6828 $numrows = $this->db->num_rows($resql);
6829 if ($numrows) {
6830 $tab = $this->db->fetch_array($resql);
6831 foreach ($tab as $key => $value) {
6832 // 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)
6833 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6834 // we can add this attribute to object
6835 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6836 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6837 $this->array_options["options_".$key] = $this->db->jdate($value);
6838 } else {
6839 $this->array_options["options_".$key] = $value;
6840 }
6841
6842 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6843 }
6844 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6845 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6846 $this->array_options["options_".$key] = dolDecrypt($value);
6847 }
6848 }
6849 }
6850 } else {
6855 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6856 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6857 $this->array_options['options_' . $key] = null;
6858 }
6859 }
6860 }
6861
6862 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6863 // in the element's extrafields table)
6864 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6865 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6866 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6867 //var_dump($conf->disable_compute);
6868 if (empty($conf->disable_compute)) {
6869 // We set a global variable to $objectoffield so we can use it inside computed formula
6870 $objectoffield = dol_clone($this, 2);
6871 global $objectoffield;
6872 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6873 }
6874 }
6875 }
6876 }
6877
6878 $this->db->free($resql);
6879
6880 if ($numrows) {
6881 return $numrows;
6882 } else {
6883 return 0;
6884 }
6885 } else {
6886 $this->errors[] = $this->db->lasterror();
6887 return -1;
6888 }
6889 }
6890 return 0;
6891 }
6892
6899 public function deleteExtraFields()
6900 {
6901 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6902 return 0;
6903 }
6904
6905 $this->db->begin();
6906
6907 $table_element = $this->table_element;
6908 if ($table_element == 'categorie') {
6909 $table_element = 'categories'; // For compatibility
6910 }
6911
6912 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6913
6914 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6915
6916 $resql = $this->db->query($sql_del);
6917 if (!$resql) {
6918 $this->error = $this->db->lasterror();
6919 $this->db->rollback();
6920 return -1;
6921 } else {
6922 $this->db->commit();
6923 return 1;
6924 }
6925 }
6926
6937 public function insertExtraFields($trigger = '', $userused = null)
6938 {
6939 global $langs, $extrafields, $user;
6940
6941 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6942 return 0;
6943 }
6944
6945 if (empty($userused)) {
6946 $userused = $user;
6947 }
6948
6949 $error = 0;
6950
6951 // Check parameters
6952 $langs->load('admin');
6953 if (!isset($extrafields) || !is_object($extrafields)) {
6954 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6955 $extrafields = new ExtraFields($this->db);
6956 }
6957 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6958
6959 // Eliminate copied source object extra fields that do not exist in target object
6960 $new_array_options = array();
6961 foreach ($this->array_options as $key => $value) {
6962 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6963 $new_array_options[$key] = $value;
6964 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6965 $new_array_options['options_'.$key] = $value;
6966 }
6967 }
6968
6969 foreach ($new_array_options as $key => $value) {
6970 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6971 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6972 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6973 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6974 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6975 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6976 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6977 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
6978 /*
6979 $attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
6980 $enabled = 0;
6981 if (!empty($extrafields->attributes[$this->table_element]['enabled'][$key])) {
6982 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
6983 }
6984 */
6985
6986 // If we clone, we have to clean unique extrafields to prevent duplicates.
6987 // If we clone, we have to clean extrafields having "empty on clone" option on.
6988 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6989 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
6990 $new_array_options[$key] = null;
6991 continue;
6992 }
6993
6994 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6995 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6996 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6997 $new_array_options[$key] = null;
6998 continue;
6999 }
7000
7001 // Similar code than into insertExtraFields
7002 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'.
7003 $v = $this->array_options[$key];
7004 if (ExtraFields::isEmptyValue($v, $attributeType)) {
7005 $langs->load("errors");
7006 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
7007 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7008 return -1;
7009 }
7010 }
7011
7012 if (!empty($attrfieldcomputed)) {
7013 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7014 global $objectoffield; // We set a global variable to $objectoffield so
7015 $objectoffield = $this; // we can use it inside the computed formula
7016 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7017 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7018 $new_array_options[$key] = $value;
7019 } else {
7020 $new_array_options[$key] = null;
7021 }
7022 continue;
7023 }
7024
7025 switch ($attributeType) {
7026 case 'int':
7027 case 'duration':
7028 case 'stars':
7029 if (!is_numeric($value) && $value != '') {
7030 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7031 return -1;
7032 } elseif ($value === '') {
7033 $new_array_options[$key] = null;
7034 }
7035 break;
7036 case 'boolean':
7037 if ($value === '' || $value === false || $value === null) {
7038 $new_array_options[$key] = null;
7039 }
7040 break;
7041 case 'price':
7042 case 'double':
7043 $value = price2num($value);
7044 if (!is_numeric($value) && $value != '') {
7045 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7046 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7047 return -1;
7048 } elseif ($value == '') {
7049 $value = null;
7050 }
7051 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7052 $new_array_options[$key] = $value;
7053 break;
7054 /*case 'select': // Not required, we chose value='0' for undefined values
7055 if ($value=='-1')
7056 {
7057 $this->array_options[$key] = null;
7058 }
7059 break;*/
7060 case 'password':
7061 $algo = '';
7062 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7063 // If there is an encryption choice, we use it to encrypt data before insert
7064 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7065 $algo = reset($tmparrays);
7066 if ($algo != '') {
7067 //global $action; // $action may be 'create', 'update', 'update_extras'...
7068 //var_dump($action);
7069 //var_dump($this->oldcopy);exit;
7070 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
7071 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7072 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7073 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7074 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7075 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7076 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7077 } else {
7078 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7079 }
7080 } else {
7081 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7082 }
7083 } else {
7084 // If value has changed
7085 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7086 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7087 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7088 } else {
7089 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7090 }
7091 } else {
7092 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7093 }
7094 }
7095 } else {
7096 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7097 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7098 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7099 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7100 } else {
7101 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7102 }
7103 }
7104 } else {
7105 // No encryption
7106 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7107 }
7108 } else { // Common usage
7109 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7110 }
7111 break;
7112 case 'date':
7113 case 'datetime':
7114 // If data is a string instead of a timestamp, we convert it
7115 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7116 $this->array_options[$key] = strtotime($this->array_options[$key]);
7117 }
7118 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7119 break;
7120 case 'datetimegmt':
7121 // If data is a string instead of a timestamp, we convert it
7122 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7123 $this->array_options[$key] = strtotime($this->array_options[$key]);
7124 }
7125 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7126 break;
7127 case 'link':
7128 $param_list = array_keys($attributeParam['options']);
7129 // 0 : ObjectName
7130 // 1 : classPath
7131 $InfoFieldList = explode(":", $param_list[0]);
7132 dol_include_once($InfoFieldList[1]);
7133 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7134 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7135 $new_array_options[$key] = '';
7136 } elseif ($value) {
7137 $object = new $InfoFieldList[0]($this->db);
7138 '@phan-var-force CommonObject $object';
7139
7140 $objectId = 0;
7141
7142 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7143 if (is_numeric($value)) {
7144 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7145 } else {
7146 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7147 }
7148
7149 $obj = $this->db->getRow($sqlFetchObject);
7150
7151 if ($obj !== false) {
7152 $objectId = $obj->rowid;
7153 $res = 1;
7154 } else {
7155 $res = -1;
7156 }
7157
7158 if ($res > 0) {
7159 $new_array_options[$key] = $objectId;
7160 } else {
7161 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7162 return -1;
7163 }
7164 }
7165 } else {
7166 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7167 }
7168 break;
7169 case 'checkbox':
7170 case 'chkbxlst':
7171 if (is_array($this->array_options[$key])) {
7172 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7173 } else {
7174 $new_array_options[$key] = $this->array_options[$key];
7175 }
7176 break;
7177 }
7178 }
7179
7180 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7181 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7182
7183 foreach ($new_array_options as $key => $newValue) {
7184 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7185 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7186 if ($attributeType === 'separate') {
7187 // separator extrafields don't have data per object so they don't have a comlumn in the database
7188 continue;
7189 }
7190 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7191 // Add field of attribute
7192 if (! $geoDataType) {
7193 // not a geodata type
7194 if ($newValue != '') {
7195 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7196 } else {
7197 $sqlColumnValues[$attributeKey] = 'null';
7198 }
7199 continue;
7200 }
7201 if (empty($newValue)) {
7202 $sqlColumnValues[$attributeKey] = 'null';
7203 continue;
7204 }
7205 if (preg_match('/error/i', $newValue)) {
7206 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7207 $sqlColumnValues[$attributeKey] = 'null';
7208 continue;
7209 }
7210
7211 // Geodata type: Text must be a WKT string. Examples:
7212 // - point => "POINT(15 20)"
7213 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7214 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7215 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7216 $sqlColumnValues[$attributeKey] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')"; // @phan-suppress-current-line SqlInjection
7217 }
7218
7219 $this->db->begin();
7220
7221 $table_element = $this->table_element;
7222 if ($table_element == 'categorie') {
7223 $table_element = 'categories'; // For compatibility
7224 }
7225 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7226
7227 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7228
7229 $linealreadyfound = 0;
7230 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7231 $resql = $this->db->query($sql);
7232 if ($resql) {
7233 $tmpobj = $this->db->fetch_object($resql);
7234 if ($tmpobj) {
7235 $linealreadyfound = $tmpobj->nb;
7236 }
7237 }
7238
7239 // if the extrafields row already exists for the object, we update it
7240 if ($linealreadyfound) {
7241 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7242 if (empty($sqlColumnValues)) {
7243 // No column of the target element to update. This happens when the object was created from
7244 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7245 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7246 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7247 // that would produce an invalid SQL statement.
7248 $this->db->commit();
7249 return 1;
7250 }
7251 $sqlColumnValueString = implode(
7252 ',',
7257 array_map(function ($key) use ($sqlColumnValues) {
7258 return "{$key} = {$sqlColumnValues[$key]}";
7259 }, array_keys($sqlColumnValues))
7260 );
7261 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7262 } else {
7263 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7264 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7265 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7266 if (isset($sqlColumnValues[$key])) {
7267 // extrafield value already provided: no need to add it
7268 continue;
7269 }
7270 // default value: empty string, except for 'int', 'double' and 'price'
7271 $sqlColumnValues[$key] = "''";
7272 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7273 $sqlColumnValues[$key] = 0;
7274 }
7275 }
7276 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7277 $sqlValues = implode(',', array_values($sqlColumnValues));
7278 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7279 }
7280
7281 $resql = $this->db->query($sql);
7282 if (!$resql) {
7283 $this->error = $this->db->lasterror();
7284 $error++;
7285 }
7286
7287 // Update also the user of last modification in parent table
7288 if (!$error && !empty($this->fields['fk_user_modif'])) { // @phan-suppress-current-line PhanTypeMismatchProperty
7289 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7290 $sql .= " SET fk_user_modif = ".(int) $user->id;
7291 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7292 $this->db->query($sql);
7293 }
7294
7295 if (!$error && $trigger) {
7296 // Call trigger
7297 $this->context = array('extrafieldaddupdate' => 1);
7298 $result = $this->call_trigger($trigger, $userused);
7299 if ($result < 0) {
7300 $error++;
7301 }
7302 // End call trigger
7303 }
7304
7305 if ($error) {
7306 $this->db->rollback();
7307 return -1;
7308 } else {
7309 $this->db->commit();
7310 return 1;
7311 }
7312 }
7313
7325 public function insertExtraLanguages($trigger = '', $userused = null)
7326 {
7327 global $langs, $user;
7328
7329 if (empty($userused)) {
7330 $userused = $user;
7331 }
7332
7333 $error = 0;
7334
7335 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7336 return 0; // For avoid conflicts if trigger used
7337 }
7338
7339 if (is_array($this->array_languages)) {
7340 $new_array_languages = $this->array_languages;
7341
7342 foreach ($new_array_languages as $key => $value) {
7343 $attributeKey = $key;
7344 $attributeType = $this->fields[$attributeKey]['type'];
7345 $attributeLabel = $this->fields[$attributeKey]['label'];
7346
7347 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7348 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7349
7350 switch ($attributeType) {
7351 case 'int':
7352 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7353 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7354 return -1;
7355 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7356 $new_array_languages[$key] = null;
7357 }
7358 break;
7359 case 'double':
7360 $value = price2num((string) $value);
7361 if (!is_numeric($value) && $value != '') {
7362 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7363 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7364 return -1;
7365 } elseif ($value == '') {
7366 $new_array_languages[$key] = null;
7367 } else {
7368 $new_array_languages[$key] = $value;
7369 }
7370 break;
7371 /*case 'select': // Not required, we chose value='0' for undefined values
7372 if ($value=='-1')
7373 {
7374 $this->array_options[$key] = null;
7375 }
7376 break;*/
7377 }
7378 }
7379
7380 $this->db->begin();
7381
7382 $table_element = $this->table_element;
7383 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7384 $table_element = 'categories'; // For compatibility
7385 }
7386
7387 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7388
7389 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7390 foreach ($langcodearray as $langcode => $value) {
7391 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7392 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7393 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7394 $this->db->query($sql_del);
7395
7396 if ($value !== '') {
7397 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7398 $sql .= ") VALUES (".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7399 $sql .= ")";
7400
7401 $resql = $this->db->query($sql);
7402 if (!$resql) {
7403 $this->error = $this->db->lasterror();
7404 $error++;
7405 break;
7406 }
7407 }
7408 }
7409 }
7410
7411 if (!$error && $trigger) {
7412 // Call trigger
7413 $this->context = array('extralanguagesaddupdate' => 1);
7414 $result = $this->call_trigger($trigger, $userused);
7415 if ($result < 0) {
7416 $error++;
7417 }
7418 // End call trigger
7419 }
7420
7421 if ($error) {
7422 $this->db->rollback();
7423 return -1;
7424 } else {
7425 $this->db->commit();
7426 return 1;
7427 }
7428 } else {
7429 return 0;
7430 }
7431 }
7432
7443 public function updateExtraField($key, $trigger = null, $userused = null)
7444 {
7445 global $langs, $user, $extrafields, $hookmanager;
7446
7447 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7448 return 0;
7449 }
7450
7451 if (empty($userused)) {
7452 $userused = $user;
7453 }
7454
7455 $error = 0;
7456
7457 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7458 // Check parameters
7459 $langs->load('admin');
7460 if (!isset($extrafields) || !is_object($extrafields)) {
7461 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7462 $extrafields = new ExtraFields($this->db);
7463 }
7464 $extrafields->fetch_name_optionals_label($this->table_element);
7465 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7466 // Extrafield not defined for this object type: nothing to update
7467 return 0;
7468 }
7469 $value = $this->array_options["options_".$key];
7470
7471 $attributeKey = $key;
7472 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7473 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7474 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7475 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7476 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7477 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7478 //$attributeEnabled = $extrafields->attributes[$this->table_element]['enabled'][$attributeKey];
7479
7480 // Similar code than into insertExtraFields
7481 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'.
7482 $mandatorypb = false;
7483 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7484 $mandatorypb = true;
7485 }
7486 if ($this->array_options["options_".$key] === '') {
7487 $mandatorypb = true;
7488 }
7489 if ($mandatorypb) {
7490 $langs->load("errors");
7491 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7492 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7493 return -1;
7494 }
7495 }
7496
7497 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7498 $new_array_options = $this->array_options;
7499
7500 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7501 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7502 if (!empty($attrfieldcomputed)) {
7503 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7504 global $objectoffield; // We set a global variable to $objectoffield so
7505 $objectoffield = $this; // we can use it inside the computed formula
7506 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7507 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7508
7509 $new_array_options["options_".$key] = $value;
7510
7511 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7512 } else {
7513 $new_array_options["options_".$key] = null;
7514
7515 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7516 }
7517 }
7518
7519 switch ($attributeType) {
7520 case 'int':
7521 if (!is_numeric($value) && $value != '') {
7522 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7523 return -1;
7524 } elseif ($value === '') {
7525 $new_array_options["options_".$key] = null;
7526
7527 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7528 }
7529 break;
7530 case 'price':
7531 case 'double':
7532 $value = price2num($value);
7533 if (!is_numeric($value) && $value != '') {
7534 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7535 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7536 return -1;
7537 } elseif ($value === '') {
7538 $value = null;
7539 }
7540 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7541 $new_array_options["options_".$key] = $value;
7542
7543 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7544 break;
7545 /*case 'select': // Not required, we chose value='0' for undefined values
7546 if ($value=='-1')
7547 {
7548 $new_array_options["options_".$key] = $value;
7549
7550 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7551 }
7552 break;*/
7553 case 'password':
7554 $algo = '';
7555 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7556 // If there is an encryption choice, we use it to encrypt data before insert
7557 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7558 $algo = reset($tmparrays);
7559 if ($algo != '') {
7560 //global $action; // $action may be 'create', 'update', 'update_extras'...
7561 //var_dump($action);
7562 //var_dump($this->oldcopy);exit;
7563 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7564 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
7565 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7566 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.
7567 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7568 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7569 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7570 } else {
7571 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7572 }
7573 } else {
7574 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7575 }
7576 } else {
7577 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7578 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7579 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7580 } else {
7581 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7582 }
7583 } else {
7584 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7585 }
7586 }
7587 } else {
7588 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7589 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7590 } else {
7591 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7592 }
7593 }
7594 } else {
7595 // No encryption
7596 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7597 }
7598 } else { // Common usage
7599 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7600 }
7601
7602 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7603 break;
7604 case 'date':
7605 case 'datetime':
7606 if (empty($this->array_options["options_".$key])) {
7607 $new_array_options["options_".$key] = null;
7608
7609 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7610 } else {
7611 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7612 }
7613 break;
7614 case 'datetimegmt':
7615 if (empty($this->array_options["options_".$key])) {
7616 $new_array_options["options_".$key] = null;
7617
7618 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7619 } else {
7620 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7621 }
7622 break;
7623 case 'boolean':
7624 if (empty($this->array_options["options_".$key])) {
7625 $new_array_options["options_".$key] = null;
7626
7627 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7628 }
7629 break;
7630 case 'link':
7631 if ($this->array_options["options_".$key] === '') {
7632 $new_array_options["options_".$key] = null;
7633
7634 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7635 }
7636 break;
7637 /*
7638 case 'link':
7639 $param_list = array_keys($attributeParam['options']);
7640 // 0 : ObjectName
7641 // 1 : classPath
7642 $InfoFieldList = explode(":", $param_list[0]);
7643 dol_include_once($InfoFieldList[1]);
7644 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7645 {
7646 if ($value == '-1') // -1 is key for no defined in combo list of objects
7647 {
7648 $new_array_options[$key] = '';
7649 } elseif ($value) {
7650 $object = new $InfoFieldList[0]($this->db);
7651 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7652 else $res = $object->fetch(0, $value); // For compatibility
7653
7654 if ($res > 0) $new_array_options[$key] = $object->id;
7655 else {
7656 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7657 $this->db->rollback();
7658 return -1;
7659 }
7660 }
7661 } else {
7662 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7663 }
7664 break;
7665 */
7666 case 'checkbox':
7667 case 'chkbxlst':
7668 $new_array_options = array();
7669 if (is_array($this->array_options["options_".$key])) {
7670 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7671 } else {
7672 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7673 }
7674
7675 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7676 break;
7677 }
7678
7679 $this->db->begin();
7680
7681 $linealreadyfound = 0;
7682
7683 // 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)
7684 $table_element = $this->table_element;
7685 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7686 $table_element = 'categories'; // For compatibility
7687 }
7688
7689 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7690 $resql = $this->db->query($sql);
7691 if ($resql) {
7692 $tmpobj = $this->db->fetch_object($resql);
7693 if ($tmpobj) {
7694 $linealreadyfound = $tmpobj->nb;
7695 }
7696 }
7697
7698 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7699 if ($linealreadyfound) {
7700 $sanitizedGeoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null; // @phan-suppress-current-line SqlInjection
7701 if ($this->array_options["options_".$key] === null) {
7702 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7703 $sql .= " SET ".$this->db->sanitize($key)." = null";
7704 } elseif (!empty($sanitizedGeoDataType['ST_Function'])) {
7705 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7706 $sql .= " SET ".$this->db->sanitize($key)." = ".$this->db->sanitize($sanitizedGeoDataType['ST_Function'])."('".$this->db->escape($this->array_options["options_".$key])."')";
7707 } else {
7708 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7709 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($table_element)."_extrafields";
7710 $sql .= " SET ".$this->db->sanitize($key)." = '".$this->db->escape($new_array_options["options_".$key])."'";
7711 }
7712 $sql .= " WHERE fk_object = ".((int) $this->id);
7713
7714 $resql = $this->db->query($sql);
7715 if (!$resql) {
7716 $error++;
7717 $this->error = $this->db->lasterror();
7718 }
7719 } else {
7720 $result = $this->insertExtraFields('', $user);
7721 if ($result < 0) {
7722 $error++;
7723 }
7724 }
7725
7726 // Update also the user of last modification in parent table
7727 if (!$error && !empty($this->fields['fk_user_modif'])) {
7728 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7729 $sql .= " SET fk_user_modif = ".(int) $user->id;
7730 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7731 $this->db->query($sql);
7732 }
7733
7734 if (!$error) {
7735 $parameters = array('key' => $key);
7736 global $action;
7737 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7738 if ($reshook < 0) {
7739 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7740 }
7741 }
7742
7743 if (!$error && $trigger) {
7744 // Call trigger
7745 $this->context = array('extrafieldupdate' => 1);
7746 $result = $this->call_trigger($trigger, $userused);
7747 if ($result < 0) {
7748 $error++;
7749 }
7750 // End call trigger
7751 }
7752
7753 if ($error) {
7754 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7755 $this->db->rollback();
7756 return -1;
7757 } else {
7758 $this->db->commit();
7759 return 1;
7760 }
7761 } else {
7762 return 0;
7763 }
7764 }
7765
7772 public function getExtraField($key)
7773 {
7774 return $this->array_options['options_'.$key] ?? null;
7775 }
7776
7784 public function setExtraField($key, $value)
7785 {
7786 $this->array_options['options_'.$key] = $value;
7787 }
7788
7799 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7800 {
7801 global $user;
7802
7803 if (empty($userused)) {
7804 $userused = $user;
7805 }
7806
7807 //$error = 0;
7808
7809 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7810 return 0; // For avoid conflicts if trigger used
7811 }
7812
7813 return 0;
7814 }
7815
7816
7831 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7832 {
7833 global $conf, $langs, $form;
7834
7835
7836 // 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...)
7837
7838 if (!is_object($form)) {
7839 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7840 $form = new Form($this->db);
7841 }
7842
7843 if (!empty($this->fields)) {
7844 $val = $this->fields[$key];
7845 }
7846
7847 // Validation tests and output
7848 $fieldValidationErrorMsg = '';
7849 $validationClass = '';
7850 $fieldValidationErrorMsg = $this->getFieldError($key);
7851 if (!empty($fieldValidationErrorMsg)) {
7852 $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
7853 } else {
7854 $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
7855 }
7856
7857 //$valuemultiselectinput = array();
7858 $out = '';
7859 $type = '';
7860 $isDependList = 0;
7861 $param = array();
7862 $param['options'] = array();
7863 $reg = array();
7864 // @phan-suppress-next-line PhanTypeMismatchProperty
7865 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7866 // Because we work on extrafields
7867 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7868 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7869 $type = 'link';
7870 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7871 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7872 $type = 'link';
7873 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7874 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7875 $type = 'link';
7876 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7877 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7878 $type = 'sellist';
7879 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7880 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7881 $type = 'sellist';
7882 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7883 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7884 $type = 'sellist';
7885 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7886 $param['options'] = array($reg[1] => 'N');
7887 $type = 'chkbxlst';
7888 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7889 $param['options'] = array();
7890 $type = 'varchar';
7891 $size = $reg[1];
7892 } elseif (preg_match('/varchar/', $val['type'])) {
7893 $param['options'] = array();
7894 $type = 'varchar';
7895 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7896 $param['options'] = array();
7897 $type = 'stars';
7898 $size = $reg[1];
7899 } else {
7900 $param['options'] = array();
7901 $type = $this->fields[$key]['type'];
7902 }
7903 //var_dump($type); var_dump($param['options']);
7904
7905 // Special case that force options and type ($type can be integer, varchar, ...)
7906 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7907 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7908 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7909 if (empty($this->fields[$key]['multiinput'])) {
7910 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7911 }
7912 }
7913
7914 $label = $this->fields[$key]['label'];
7915 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7916 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7917 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7918 // @phan-suppress-next-line PhanTypeMismatchProperty
7919 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7920 // @phan-suppress-next-line PhanTypeMismatchProperty
7921 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7922 // @phan-suppress-next-line PhanTypeMismatchProperty
7923 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7924 // @phan-suppress-next-line PhanTypeMismatchProperty
7925 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7926 // @phan-suppress-next-line PhanTypeMismatchProperty
7927 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7928
7929 // @phan-suppress-next-line PhanTypeMismatchProperty
7930 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7931 // @phan-suppress-next-line PhanTypeMismatchProperty
7932 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7933 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7934
7935 $objectid = $this->id;
7936
7937 if ($computed) {
7938 if (!preg_match('/^search_/', $keyprefix)) {
7939 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7940 } else {
7941 return '';
7942 }
7943 }
7944
7945 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7946 if (empty($morecss) && !empty($val['css'])) {
7947 $morecss = $val['css'];
7948 } elseif (empty($morecss)) {
7949 if ($type == 'date') {
7950 $morecss = 'minwidth100imp';
7951 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7952 $morecss = 'minwidth200imp';
7953 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7954 $morecss = 'maxwidth75';
7955 } elseif ($type == 'url') {
7956 $morecss = 'minwidth400';
7957 } elseif ($type == 'boolean') {
7958 $morecss = '';
7959 } else {
7960 if (is_numeric($size) && round((float) $size) < 12) {
7961 $morecss = 'minwidth100';
7962 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7963 $morecss = 'minwidth200';
7964 } else {
7965 $morecss = 'minwidth400';
7966 }
7967 }
7968 }
7969
7970 // Add validation state class
7971 if (!empty($validationClass)) {
7972 $morecss .= $validationClass;
7973 }
7974
7975 if (in_array($type, array('date'))) {
7976 $tmp = explode(',', $size);
7977 $newsize = $tmp[0];
7978 $showtime = 0;
7979
7980 // Do not show current date when field not required (see selectDate() method)
7981 if (!$required && $value == '') {
7982 $value = '-1';
7983 }
7984
7985 // TODO Must also support $moreparam
7986 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7987 } elseif (in_array($type, array('datetime'))) {
7988 $tmp = explode(',', $size);
7989 $newsize = $tmp[0];
7990 $showtime = 1;
7991
7992 // Do not show current date when field not required (see selectDate() method)
7993 if (!$required && $value == '') {
7994 $value = '-1';
7995 }
7996
7997 // TODO Must also support $moreparam
7998 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7999 } elseif (in_array($type, array('duration'))) {
8000 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
8001 } elseif (in_array($type, array('int', 'integer'))) {
8002 $tmp = explode(',', $size);
8003 $newsize = $tmp[0];
8004 $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' : '').'>';
8005 } elseif (in_array($type, array('real'))) {
8006 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8007 } elseif (preg_match('/varchar/', (string) $type)) {
8008 $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' : '').'>';
8009 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
8010 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8011 } elseif (preg_match('/^text/', (string) $type)) {
8012 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8013 if (!empty($param['options'])) {
8014 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
8015 //var_dump($param['options']);
8016 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
8017 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
8018 $out .= "<script>";
8019 $out .= '
8020 function handlemultiinputdisabling(htmlname){
8021 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
8022 multiinput = $("#"+htmlname+"_multiinput");
8023 multiinput.find("option").each(function(){
8024 tmpval = $("#"+htmlname).val();
8025 tmpvalarray = tmpval.split("\n");
8026 valtotest = $(this).val();
8027 if(tmpvalarray.includes(valtotest)){
8028 $(this).prop("disabled",true);
8029 } else {
8030 if($(this).prop("disabled") == true){
8031 console.log(valtotest)
8032 $(this).prop("disabled", false);
8033 }
8034 }
8035 });
8036 }
8037
8038 $(document).ready(function () {
8039 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
8040 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
8041 tmpvalarray = tmpval.split(",");
8042 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
8043 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8044 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
8045 if(tmpval == ""){
8046 tmpval = valtotest;
8047 } else {
8048 tmpval = tmpval + "\n" + valtotest;
8049 }
8050 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
8051 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8052 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
8053 } else {
8054 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
8055 }
8056 });
8057 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
8058 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8059 });
8060 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
8061 })';
8062 $out .= "</script>";
8063 $value = str_replace(',', "\n", $value);
8064 }
8065 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8066 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8067 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
8068 } else {
8069 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8070 }
8071 } elseif (preg_match('/^html/', (string) $type)) {
8072 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
8073 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
8074 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8075 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8076 } else {
8077 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
8078 }
8079 } elseif ($type == 'boolean') {
8080 $checked = '';
8081 if (!empty($value)) {
8082 $checked = ' checked value="1" ';
8083 } else {
8084 $checked = ' value="1" ';
8085 }
8086 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
8087 } elseif ($type == 'price') {
8088 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8089 $value = price($value);
8090 }
8091 $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>';
8092 } elseif ($type == 'stars') {
8093 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8094 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8095 $i = 1;
8096 while ($i <= $size) {
8097 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8098 $i++;
8099 }
8100 $out .= '</div>';
8101 $out .= '<script>
8102 jQuery(function($) { /* commonobject.class.php 1 */
8103 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8104 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8105 container.find(".star").each(function() {
8106 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8107 });
8108 container.find(".star").on("mouseover", function() {
8109 let selectedStar = $(this).data("value");
8110 container.find(".star").each(function() {
8111 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8112 });
8113 });
8114 container.on("mouseout", function() {
8115 container.find(".star").each(function() {
8116 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8117 });
8118 });
8119 container.find(".star").off("click").on("click", function() {
8120 selectedStars = $(this).data("value");
8121 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8122 selectedStars = 0;
8123 }
8124 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8125 container.find(".star").each(function() {
8126 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8127 });
8128 });
8129 });
8130 </script>';
8131 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8132 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8133 $value = price($value);
8134 }
8135 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8136 } elseif ($type == 'select') { // combo list
8137 $out = '';
8138 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8139 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8140 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8141 }
8142
8143 $tmpselect = '';
8144 $nbchoice = 0;
8145
8146 foreach ($param['options'] as $keyb => $valb) {
8147 if ((string) $keyb == '') {
8148 continue;
8149 }
8150 if (strpos($valb, "|") !== false) {
8151 list($valb, $parent) = explode('|', $valb);
8152 }
8153 $nbchoice++;
8154 $tmpselect .= '<option value="'.$keyb.'"';
8155 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8156 if (!empty($parent)) {
8157 $isDependList = 1;
8158 }
8159 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8160 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8161 }
8162
8163 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8164 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8165 $out .= '<option value="0">&nbsp;</option>';
8166 }
8167 $out .= $tmpselect;
8168 $out .= '</select>';
8169 } elseif ($type == 'sellist') {
8170 $out = '';
8171 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8172 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8173 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8174 }
8175
8176 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8177 if (is_array($param['options'])) {
8178 $tmpparamoptions = array_keys($param['options']);
8179 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8180
8181 $InfoFieldList = explode(":", $paramoptions[0], 5);
8182 // 0 : tableName
8183 // 1 : label field name
8184 // 2 : key fields name (if different of rowid)
8185 // optional parameters...
8186 // 3 : key field parent (for dependent lists). How this is used ?
8187 // 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".
8188 // 5 : string category type. This replace the filter.
8189 // 6 : ids categories list separated by comma for category root. This replace the filter.
8190 // 7 : sort field
8191
8192 // If there is filter
8193 if (! empty($InfoFieldList[4])) {
8194 $pos = 0;
8195 $parenthesisopen = 0;
8196 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8197 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8198 $parenthesisopen++;
8199 }
8200 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8201 $parenthesisopen--;
8202 }
8203 $pos++;
8204 }
8205 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8206 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8207 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8208 $InfoFieldList[4] = $tmpbefore;
8209 if ($tmpafter !== '') {
8210 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8211 }
8212 //var_dump($InfoFieldList);
8213
8214 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8215 $reg = array();
8216 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8217 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8218 }
8219
8220 //var_dump($InfoFieldList);
8221 }
8222
8223 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8224
8225 $parentName = '';
8226 $parentField = '';
8227
8228 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8229
8230 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8231 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8232 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8233 } else {
8234 $keyList = $InfoFieldList[2].' as rowid';
8235 }
8236 }
8237 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8238 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8239 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8240 $keyList .= ', main.'.$parentField;
8241 } else {
8242 $keyList .= ', '.$parentField;
8243 }
8244 }
8245
8246 $filter_categorie = false;
8247 if (count($InfoFieldList) > 5) {
8248 if ($InfoFieldList[0] == 'categorie') {
8249 $filter_categorie = true;
8250 }
8251 }
8252
8253 if (!$filter_categorie) {
8254 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8255 if (!empty($fields_label)) {
8256 $keyList .= ', ';
8257 $keyList .= implode(', ', $fields_label);
8258 }
8259
8260 $sqlwhere = '';
8261 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8262 $sql .= " FROM " . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8263
8264 if (!empty($InfoFieldList[4])) {
8265 // can use SELECT request
8266 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8267 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8268 }
8269
8270 // current object id can be use into filter
8271 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8272 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8273 } else {
8274 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8275 }
8276
8277 // We have to join on extrafield table
8278 $errstr = '';
8279 if (strpos($InfoFieldList[4], 'extra') !== false) {
8280 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8281 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8282 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8283 } else {
8284 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8285 }
8286 } else {
8287 $sqlwhere .= ' WHERE 1=1';
8288 }
8289
8290 // Add Usf filter on second line
8291 /*
8292 if ($Usf) {
8293 $errorstr = '';
8294 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8295 if (!$errorstr) {
8296 $sqlwhere .= $sqlusf;
8297 } else {
8298 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8299 }
8300 }
8301 */
8302
8303 // Some tables may have field, some other not. For the moment we disable it.
8304 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8305 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8306 }
8307 $sql .= $sqlwhere;
8308
8309 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8310 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8311 $sql .= " ORDER BY ".$this->db->sanitize($InfoFieldList[7]);
8312 } else {
8313 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8314 }
8315 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8316 // print $sql;
8317
8318 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8319 $resql = $this->db->query($sql);
8320 if ($resql) {
8321 $out .= '<option value="0">&nbsp;</option>';
8322 $num = $this->db->num_rows($resql);
8323 $i = 0;
8324 while ($i < $num) {
8325 $labeltoshow = '';
8326 $obj = $this->db->fetch_object($resql);
8327
8328 // Several field into label (eq table:code|libelle:rowid)
8329 $notrans = false;
8330 $fields_label = explode('|', $InfoFieldList[1]);
8331 if (count($fields_label) > 1) {
8332 $notrans = true;
8333 foreach ($fields_label as $field_toshow) {
8334 $labeltoshow .= $obj->$field_toshow . ' ';
8335 }
8336 } else {
8337 $labeltoshow = $obj->{$InfoFieldList[1]};
8338 }
8339 $labeltoshow = dol_trunc($labeltoshow, 45);
8340
8341 if ($value == $obj->rowid) {
8342 foreach ($fields_label as $field_toshow) {
8343 $translabel = $langs->trans($obj->$field_toshow);
8344 if ($translabel != $obj->$field_toshow) {
8345 $labeltoshow = dol_trunc($translabel) . ' ';
8346 } else {
8347 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8348 }
8349 }
8350 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8351 } else {
8352 if (!$notrans) {
8353 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8354 if ($translabel != $obj->{$InfoFieldList[1]}) {
8355 $labeltoshow = dol_trunc($translabel, 18);
8356 } else {
8357 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8358 }
8359 }
8360 if (empty($labeltoshow)) {
8361 $labeltoshow = '(not defined)';
8362 }
8363 if ($value == $obj->rowid) {
8364 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8365 }
8366
8367 if (!empty($InfoFieldList[3]) && $parentField) {
8368 $parent = $parentName . ':' . $obj->{$parentField};
8369 $isDependList = 1;
8370 }
8371
8372 $out .= '<option value="' . $obj->rowid . '"';
8373 $out .= ($value == $obj->rowid ? ' selected' : '');
8374 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8375 $out .= '>' . $labeltoshow . '</option>';
8376 }
8377
8378 $i++;
8379 }
8380 $this->db->free($resql);
8381 } else {
8382 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8383 }
8384 } else {
8385 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8386 $categcode = $InfoFieldList[5];
8387 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8388 $tmpcategory = new Categorie($this->db);
8389 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8390 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8391 }
8392
8393 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8394 $out .= '<option value="0">&nbsp;</option>';
8395 foreach ($data as $data_key => $data_value) {
8396 $out .= '<option value="' . $data_key . '"';
8397 $out .= ($value == $data_key ? ' selected' : '');
8398 $out .= '>' . $data_value . '</option>';
8399 }
8400 }
8401 }
8402 $out .= '</select>';
8403 } elseif ($type == 'checkbox') {
8404 $value_arr = explode(',', $value);
8405 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8406 } elseif ($type == 'radio') {
8407 $out = '';
8408 foreach ($param['options'] as $keyopt => $valopt) {
8409 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8410 $out .= ' value="'.$keyopt.'"';
8411 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8412 $out .= ($value == $keyopt ? 'checked' : '');
8413 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8414 }
8415 } elseif ($type == 'chkbxlst') {
8416 if (is_array($value)) {
8417 $value_arr = $value;
8418 } else {
8419 $value_arr = explode(',', $value);
8420 }
8421
8422 if (is_array($param['options'])) {
8423 $tmpparamoptions = array_keys($param['options']);
8424 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8425
8426 $InfoFieldList = explode(":", $paramoptions[0], 5);
8427 // 0 : tableName
8428 // 1 : label field name
8429 // 2 : key fields name (if different of rowid)
8430 // optional parameters...
8431 // 3 : key field parent (for dependent lists). How this is used ?
8432 // 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".
8433 // 5 : string category type. This replace the filter.
8434 // 6 : ids categories list separated by comma for category root. This replace the filter.
8435 // 7 : sort field
8436
8437 // If there is a filter
8438 if (! empty($InfoFieldList[4])) {
8439 $pos = 0;
8440 $parenthesisopen = 0;
8441 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8442 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8443 $parenthesisopen++;
8444 }
8445 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8446 $parenthesisopen--;
8447 }
8448 $pos++;
8449 }
8450 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8451 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8452 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8453 $InfoFieldList[4] = $tmpbefore;
8454 if ($tmpafter !== '') {
8455 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8456 }
8457
8458 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8459 $reg = array();
8460 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8461 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8462 }
8463
8464 //var_dump($InfoFieldList);
8465 }
8466
8467 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8468
8469 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8470
8471 $parentName = '';
8472 $parentField = '';
8473
8474 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8475
8476 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8477 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8478 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8479 } else {
8480 $keyList = $InfoFieldList[2].' as rowid';
8481 }
8482 }
8483 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8484 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8485 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8486 $keyList .= ', main.'.$parentField;
8487 } else {
8488 $keyList .= ', '.$parentField;
8489 }
8490 }
8491
8492 $filter_categorie = false;
8493 if (count($InfoFieldList) > 5) {
8494 if ($InfoFieldList[0] == 'categorie') {
8495 $filter_categorie = true;
8496 }
8497 }
8498
8499 // Common filter
8500 if (!$filter_categorie) {
8501 $fields_label = explode('|', $InfoFieldList[1]);
8502 if (is_array($fields_label)) {
8503 $keyList .= ', ';
8504 $keyList .= implode(', ', $fields_label);
8505 }
8506
8507 $sqlwhere = '';
8508 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
8509 $sql .= ' FROM ' . $this->db->prefix() . $this->db->sanitize($InfoFieldList[0]);
8510
8511 if (!empty($InfoFieldList[4])) {
8512 // can use SELECT request
8513 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8514 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8515 }
8516
8517 // current object id can be use into filter
8518 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8519 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8520 } else {
8521 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8522 }
8523
8524 // We have to join on extrafield table
8525 $errstr = '';
8526 if (strpos($InfoFieldList[4], 'extra') !== false) {
8527 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8528 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8529 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8530 } else {
8531 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8532 }
8533 } else {
8534 $sqlwhere .= ' WHERE 1=1';
8535 }
8536
8537 // Add Usf filter on second line
8538 /*
8539 if ($Usf) {
8540 $errorstr = '';
8541 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8542 if (!$errorstr) {
8543 $sqlwhere .= $sqlusf;
8544 } else {
8545 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8546 }
8547 }
8548 */
8549
8550 // Some tables may have field, some other not. For the moment we disable it.
8551 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8552 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8553 }
8554 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8555 // print $sql;
8556
8557 $sql .= $sqlwhere;
8558
8559 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8560
8561 $resql = $this->db->query($sql);
8562 if ($resql) {
8563 $num = $this->db->num_rows($resql);
8564 $i = 0;
8565
8566 $data = array();
8567
8568 while ($i < $num) {
8569 $labeltoshow = '';
8570 $obj = $this->db->fetch_object($resql);
8571
8572 $notrans = false;
8573 // Several field into label (eq table:code|libelle:rowid)
8574 $fields_label = explode('|', $InfoFieldList[1]);
8575 if (count($fields_label) > 1) {
8576 $notrans = true;
8577 foreach ($fields_label as $field_toshow) {
8578 $labeltoshow .= $obj->$field_toshow . ' ';
8579 }
8580 } else {
8581 $labeltoshow = $obj->{$InfoFieldList[1]};
8582 }
8583 $labeltoshow = dol_trunc($labeltoshow, 45);
8584
8585 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8586 foreach ($fields_label as $field_toshow) {
8587 $translabel = $langs->trans($obj->$field_toshow);
8588 if ($translabel != $obj->$field_toshow) {
8589 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8590 } else {
8591 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8592 }
8593 }
8594
8595 $data[$obj->rowid] = $labeltoshow;
8596 } else {
8597 if (!$notrans) {
8598 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8599 if ($translabel != $obj->{$InfoFieldList[1]}) {
8600 $labeltoshow = dol_trunc($translabel, 18);
8601 } else {
8602 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8603 }
8604 }
8605 if (empty($labeltoshow)) {
8606 $labeltoshow = '(not defined)';
8607 }
8608
8609 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8610 $data[$obj->rowid] = $labeltoshow;
8611 }
8612
8613 if (!empty($InfoFieldList[3]) && $parentField) {
8614 $parent = $parentName . ':' . $obj->{$parentField};
8615 $isDependList = 1;
8616 }
8617
8618 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8619 }
8620
8621 $i++;
8622 }
8623 $this->db->free($resql);
8624
8625 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8626 } else {
8627 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8628 }
8629 } else {
8630 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8631 $categcode = $InfoFieldList[5];
8632 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8633 $tmpcategory = new Categorie($this->db);
8634 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8635 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8636 }
8637
8638 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8639 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8640 }
8641 }
8642 } elseif ($type == 'link') {
8643 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8644 // Filter can contains some ':' inside.
8645 $param_list = array_keys($param['options']);
8646 $param_list_array = explode(':', $param_list[0], 4);
8647
8648 $showempty = (($required && $default != '') ? 0 : 1);
8649
8650 if (!preg_match('/search_/', $keyprefix)) {
8651 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8652 // @phan-suppress-next-line PhanTypeMismatchProperty
8653 if (!empty($this->fields[$key]['picto'])) {
8654 $morecss .= ' widthcentpercentminusxx';
8655 } else {
8656 $morecss .= ' widthcentpercentminusx';
8657 }
8658 } else {
8659 // @phan-suppress-next-line PhanTypeMismatchProperty
8660 if (!empty($this->fields[$key]['picto'])) {
8661 $morecss .= ' widthcentpercentminusx';
8662 }
8663 }
8664 }
8665
8666 // $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.
8667
8668 // $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.
8669
8670 $objectfield = $val;
8671
8672 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8673 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8674
8675 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8676 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8677 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8678 list($class, $classfile) = explode(':', $param_list[0]);
8679 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8680 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8681 } else {
8682 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8683 }
8684 $paramforthenewlink = '';
8685 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8686 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8687 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8688 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8689 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8690 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8691 $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>';
8692 }
8693 }
8694 } elseif ($type == 'password') {
8695 // If prefix is 'search_', field is used as a filter, we use a common text field.
8696 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8697 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8698 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8699 } else {
8700 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8701 }
8702 } elseif ($type == 'array') {
8703 $newval = $val;
8704 $newval['type'] = 'varchar(256)';
8705
8706 $out = '';
8707 if (!empty($value)) {
8708 foreach ($value as $option) {
8709 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8710 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8711 }
8712 }
8713 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8714
8715 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8716 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8717
8718 if (!empty($conf->use_javascript_ajax)) {
8719 $out .= '
8720 <script nonce="'.getNonce().'">
8721 $(document).ready(function() {
8722 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8723 $("'.dol_escape_js($newInput).'").insertBefore(this);
8724 });
8725
8726 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8727 $(this).parent().remove();
8728 });
8729 });
8730 </script>';
8731 }
8732 }
8733 if (!empty($hidden)) {
8734 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8735 }
8736
8737 if ($isDependList == 1) {
8738 $out .= $this->getJSListDependancies('_common');
8739 }
8740 /* Add comments
8741 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8742 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8743 */
8744
8745 // Display error message for field
8746 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8747 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8748 }
8749
8750 return $out;
8751 }
8752
8766 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8767 {
8768 global $conf, $langs, $form;
8769
8770 // 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...)
8771
8772 if (!is_object($form)) {
8773 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8774 $form = new Form($this->db);
8775 }
8776
8777 //$label = empty($val['label']) ? '' : $val['label'];
8778 $type = empty($val['type']) ? '' : $val['type'];
8779 $size = empty($val['css']) ? '' : $val['css'];
8780
8781 $reg = array();
8782
8783 // Convert var to be able to share same code than showOutputField of extrafields
8784 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8785 $type = 'varchar'; // convert varchar(xx) int varchar
8786 $size = $reg[1];
8787 } elseif (preg_match('/varchar/', $type)) {
8788 $type = 'varchar'; // convert varchar(xx) int varchar
8789 }
8790 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8791 // @phan-suppress-next-line PhanTypeMismatchProperty
8792 if (empty($this->fields[$key]['multiinput'])) {
8793 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8794 }
8795 }
8796 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8797 $type = 'link';
8798 }
8799
8800 $default = empty($val['default']) ? '' : $val['default'];
8801 $computed = empty($val['computed']) ? '' : $val['computed'];
8802 $unique = empty($val['unique']) ? '' : $val['unique'];
8803 $required = empty($val['required']) ? '' : $val['required'];
8804 $param = array();
8805 $param['options'] = array();
8806
8807 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8808 $param['options'] = $val['arrayofkeyval'];
8809 }
8810 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8811 $type = 'link';
8812 $stringforoptions = $reg[1].':'.$reg[2];
8813 // Special case: Force addition of getnomurlparam1 to -1 for users
8814 if ($reg[1] == 'User') {
8815 $stringforoptions .= ':#getnomurlparam1=-1';
8816 }
8817 $param['options'] = array($stringforoptions => $stringforoptions);
8818 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8819 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8820 $type = 'sellist';
8821 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8822 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8823 $type = 'sellist';
8824 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8825 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8826 $type = 'sellist';
8827 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8828 $param['options'] = array($reg[1] => 'N');
8829 $type = 'chkbxlst';
8830 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8831 $param['options'] = array();
8832 $type = 'stars';
8833 $size = $reg[1];
8834 }
8835
8836 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8837 $list = (empty($val['list']) ? '' : $val['list']);
8838 $help = (empty($val['help']) ? '' : $val['help']);
8839 $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)
8840
8841 if ($hidden) {
8842 return '';
8843 }
8844
8845 // If field is a computed field, value must become result of compute
8846 if ($computed) {
8847 // Make the eval of compute string
8848 //var_dump($computed);
8849 $value = dol_eval((string) $computed, 1, 0, '2');
8850 }
8851
8852 if (empty($morecss)) {
8853 if ($type == 'date') {
8854 $morecss = 'minwidth100imp';
8855 } elseif ($type == 'datetime' || $type == 'timestamp') {
8856 $morecss = 'minwidth200imp';
8857 } elseif (in_array($type, array('int', 'double', 'price'))) {
8858 $morecss = 'maxwidth75';
8859 } elseif ($type == 'url') {
8860 $morecss = 'minwidth400';
8861 } elseif ($type == 'boolean') {
8862 $morecss = '';
8863 } else {
8864 if (is_numeric($size) && round((float) $size) < 12) {
8865 $morecss = 'minwidth100';
8866 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8867 $morecss = 'minwidth200';
8868 } else {
8869 $morecss = 'minwidth400';
8870 }
8871 }
8872 }
8873
8874 // Format output value differently according to properties of field
8875 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8876 // @phan-suppress-next-line PhanTypeMismatchProperty
8877 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.
8878 $value = $this->getNomUrl(1, '', 0, '', 1);
8879 }
8880 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8881 $value = $this->getLibStatut(3);
8882 } elseif ($type == 'date') {
8883 if (!empty($value)) {
8884 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8885 } else {
8886 $value = '';
8887 }
8888 } elseif ($type == 'datetime' || $type == 'timestamp') {
8889 if (!empty($value)) {
8890 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8891 } else {
8892 $value = '';
8893 }
8894 } elseif ($type == 'duration') {
8895 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8896 if (!is_null($value) && $value !== '') {
8897 $value = convertSecondToTime((int) $value, 'allhourmin');
8898 }
8899 } elseif ($type == 'double' || $type == 'real') {
8900 if (!is_null($value) && $value !== '') {
8901 $value = price($value);
8902 }
8903 } elseif ($type == 'boolean') {
8904 $checked = '';
8905 if (!empty($value)) {
8906 $checked = ' checked ';
8907 }
8908 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8909 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8910 } else {
8911 $value = yn($value ? 1 : 0);
8912 }
8913 } elseif ($type == 'mail' || $type == 'email') {
8914 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8915 } elseif ($type == 'url') {
8916 $value = dol_print_url((string) $value, '_blank', 32, 1);
8917 } elseif ($type == 'phone') {
8918 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8919 } elseif ($type == 'ip') {
8920 $value = dol_print_ip((string) $value, 0);
8921 } elseif ($type == 'stars') {
8922 $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 : '').'>';
8923 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8924 $i = 1;
8925 while ($i <= $size) {
8926 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8927 $i++;
8928 }
8929 $value .= '</div>';
8930 $value .= '<script>
8931 $(document).ready(function() { /* commonobject.class.php 2 */
8932 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8933 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8934 container.find(".star").each(function() {
8935 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8936 });
8937 container.find(".star").on("mouseover", function() {
8938 let selectedStar = $(this).data("value");
8939 container.find(".star").each(function() {
8940 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8941 });
8942 });
8943 container.on("mouseout", function() {
8944 container.find(".star").each(function() {
8945 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8946 });
8947 });
8948 container.find(".star").off("click").on("click", function() {
8949 selectedStars = $(this).data("value");
8950 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8951 selectedStars = 0;
8952 }
8953 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8954 container.find(".star").each(function() {
8955 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8956 });
8957 $.ajax({
8958 url: "ajax/'.$this->element.'.php",
8959 method: "POST",
8960 data: {
8961 objectId: "'.$this->id.'",
8962 field: "'.$keyprefix.$key.$keysuffix.'",
8963 value: selectedStars,
8964 token: "'.newToken().'"
8965 },
8966 success: function(response) {
8967 var res = JSON.parse(response);
8968 console[res.status === "success" ? "log" : "error"](res.message);
8969 },
8970 error: function(xhr, status, error) {
8971 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8972 }
8973 });
8974 });
8975 });
8976 </script>';
8977 } elseif ($type == 'price') {
8978 if (!is_null($value) && $value !== '') {
8979 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8980 }
8981 } elseif ($type == 'select') {
8982 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8983 if (strpos($value, "|") !== false) {
8984 $value = $langs->trans(explode('|', $value)[0]);
8985 } elseif (! is_numeric($value)) {
8986 $value = $langs->trans($value);
8987 }
8988 } elseif ($type == 'sellist') {
8989 $param_list = array_keys($param['options']);
8990 $InfoFieldList = explode(":", $param_list[0]);
8991
8992 $selectkey = "rowid";
8993 $keyList = 'rowid';
8994
8995 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8996 $selectkey = $InfoFieldList[2];
8997 $keyList = $InfoFieldList[2].' as rowid';
8998 }
8999
9000 $fields_label = explode('|', $InfoFieldList[1]);
9001 if (is_array($fields_label)) {
9002 $keyList .= ', ';
9003 $keyList .= implode(', ', $fields_label);
9004 }
9005
9006 $filter_categorie = false;
9007 if (count($InfoFieldList) > 5) {
9008 if ($InfoFieldList[0] == 'categorie') {
9009 $filter_categorie = true;
9010 }
9011 }
9012
9013 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9014 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9015 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9016 $sql .= ' as main';
9017 }
9018 if ($selectkey == 'rowid' && empty($value)) {
9019 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = 0";
9020 } elseif ($selectkey == 'rowid') {
9021 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = ".((int) $value);
9022 } else {
9023 $sql .= " WHERE ".$this->db->sanitize($selectkey)." = '".$this->db->escape((string) $value)."'";
9024 }
9025
9026 //$sql.= ' AND entity = '.((int) $conf->entity);
9027
9028 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
9029 $resql = $this->db->query($sql);
9030 if ($resql) {
9031 if (!$filter_categorie) {
9032 $value = ''; // value was used, so now we reset it to use it to build final output
9033 $numrows = $this->db->num_rows($resql);
9034 if ($numrows) {
9035 $obj = $this->db->fetch_object($resql);
9036
9037 // Several field into label (eq table:code|libelle:rowid)
9038 $fields_label = explode('|', $InfoFieldList[1]);
9039
9040 if (is_array($fields_label) && count($fields_label) > 1) {
9041 foreach ($fields_label as $field_toshow) {
9042 $translabel = '';
9043 if (!empty($obj->$field_toshow)) {
9044 $translabel = $langs->trans($obj->$field_toshow);
9045 }
9046 if ($translabel != $field_toshow) {
9047 $value .= dol_trunc($translabel, 18) . ' ';
9048 } else {
9049 $value .= $obj->$field_toshow . ' ';
9050 }
9051 }
9052 } else {
9053 $translabel = '';
9054 if (!empty($obj->{$InfoFieldList[1]})) {
9055 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9056 }
9057 if ($translabel != $obj->{$InfoFieldList[1]}) {
9058 $value = dol_trunc($translabel, 18);
9059 } else {
9060 $value = $obj->{$InfoFieldList[1]};
9061 }
9062 }
9063 }
9064 } else {
9065 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9066
9067 $toprint = array();
9068 $obj = $this->db->fetch_object($resql);
9069 $c = new Categorie($this->db);
9070 $c->fetch($obj->rowid);
9071 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9072 foreach ($ways as $way) {
9073 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9074 }
9075 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9076 }
9077 } else {
9078 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9079 }
9080 } elseif ($type == 'radio') {
9081 $value = $param['options'][(string) $value];
9082 } elseif ($type == 'checkbox') {
9083 $value_arr = explode(',', (string) $value);
9084 $value = '';
9085 if (is_array($value_arr) && count($value_arr) > 0) {
9086 $toprint = array();
9087 foreach ($value_arr as $keyval => $valueval) {
9088 if (!empty($valueval)) {
9089 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9090 }
9091 }
9092 if (!empty($toprint)) {
9093 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9094 }
9095 }
9096 } elseif ($type == 'chkbxlst') {
9097 $value_arr = (isset($value) ? explode(',', $value) : array());
9098
9099 $param_list = array_keys($param['options']);
9100 $InfoFieldList = explode(":", $param_list[0]);
9101
9102 $selectkey = "rowid";
9103 $keyList = 'rowid';
9104
9105 if (count($InfoFieldList) >= 3) {
9106 $selectkey = $InfoFieldList[2];
9107 $keyList = $InfoFieldList[2].' as rowid';
9108 }
9109
9110 $fields_label = explode('|', $InfoFieldList[1]);
9111 if (is_array($fields_label)) {
9112 $keyList .= ', ';
9113 $keyList .= implode(', ', $fields_label);
9114 }
9115
9116 $filter_categorie = false;
9117 if (count($InfoFieldList) > 5) {
9118 if ($InfoFieldList[0] == 'categorie') {
9119 $filter_categorie = true;
9120 }
9121 }
9122
9123 $sql = "SELECT ".$this->db->sanitize($keyList, 0, 0, 1);
9124 $sql .= ' FROM '.$this->db->prefix().$this->db->sanitize($InfoFieldList[0]);
9125 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9126 $sql .= ' as main';
9127 }
9128 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9129 // $sql.= ' AND entity = '.((int) $conf->entity);
9130
9131 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9132 $resql = $this->db->query($sql);
9133 if ($resql) {
9134 if (!$filter_categorie) {
9135 $value = ''; // value was used, so now we reset it to use it to build final output
9136 $toprint = array();
9137 while ($obj = $this->db->fetch_object($resql)) {
9138 // Several field into label (eq table:code|libelle:rowid)
9139 $fields_label = explode('|', $InfoFieldList[1]);
9140 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9141 if (is_array($fields_label) && count($fields_label) > 1) {
9142 foreach ($fields_label as $field_toshow) {
9143 $translabel = '';
9144 if (!empty($obj->$field_toshow)) {
9145 $translabel = $langs->trans($obj->$field_toshow);
9146 }
9147 if ($translabel != $field_toshow) {
9148 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9149 } else {
9150 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9151 }
9152 }
9153 } else {
9154 $translabel = '';
9155 if (!empty($obj->{$InfoFieldList[1]})) {
9156 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9157 }
9158 if ($translabel != $obj->{$InfoFieldList[1]}) {
9159 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9160 } else {
9161 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9162 }
9163 }
9164 }
9165 }
9166 } else {
9167 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9168
9169 $toprint = array();
9170 while ($obj = $this->db->fetch_object($resql)) {
9171 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9172 $c = new Categorie($this->db);
9173 $c->fetch($obj->rowid);
9174 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9175 foreach ($ways as $way) {
9176 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9177 }
9178 }
9179 }
9180 }
9181 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9182 } else {
9183 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9184 }
9185 } elseif ($type == 'link') {
9186 $out = '';
9187
9188 // only if something to display (perf)
9189 if ($value) {
9190 $param_list = array_keys($param['options']);
9191 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9192 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9193
9194 $InfoFieldList = explode(":", $param_list[0]);
9195
9196 $classname = $InfoFieldList[0];
9197 $classpath = $InfoFieldList[1];
9198
9199 // Set $getnomurlparam1 et getnomurlparam2
9200 $getnomurlparam = 3;
9201 $getnomurlparam2 = '';
9202 $regtmp = array();
9203 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9204 $getnomurlparam = $regtmp[1];
9205 }
9206 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9207 $getnomurlparam2 = $regtmp[1];
9208 }
9209
9210 if (!empty($classpath)) {
9211 dol_include_once($InfoFieldList[1]);
9212
9213 if ($classname && !class_exists($classname)) {
9214 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9215 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9216 $element_prop = getElementProperties($classname);
9217 if ($element_prop) {
9218 $classname = $element_prop['classname'];
9219 }
9220 }
9221
9222
9223 if ($classname && class_exists($classname)) {
9224 $object = new $classname($this->db);
9225 '@phan-var-force CommonObject $object';
9226 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9227 '@phan-var-force Product $object';
9228 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9229 } else {
9230 $result = $object->fetch($value);
9231 }
9232 if ($result > 0) {
9233 if ($object->element === 'product') {
9234 '@phan-var-force Product $object';
9235 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9236 if (isset($val['get_name_url_params'])) {
9237 $get_name_url_params = explode(':', $val['get_name_url_params']);
9238 if (!empty($get_name_url_params)) {
9239 $param_num_max = count($get_name_url_param_arr) - 1;
9240 foreach ($get_name_url_params as $param_num => $param_value) {
9241 if ($param_num > $param_num_max) {
9242 break;
9243 }
9244 $get_name_url_param_arr[$param_num] = $param_value;
9245 }
9246 }
9247 }
9248
9252 $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]);
9253 } else {
9254 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9255 }
9256 } else {
9257 $value = '';
9258 }
9259 }
9260 } else {
9261 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9262 return 'Error bad setup of extrafield';
9263 }
9264 } else {
9265 $value = '';
9266 }
9267 } elseif ($type == 'password') {
9268 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9269 //$value = preg_replace('/./i', '*', $value);
9270 } elseif ($type == 'array') {
9271 if (is_array($value)) {
9272 $value = implode('<br>', $value);
9273 } else {
9274 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9275 return 'Error unexpected result from code evaluation';
9276 }
9277 } else { // text|html|varchar
9278 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9279 $value = str_replace(',', "\n", $value);
9280 }
9281 $value = dol_htmlentitiesbr((string) $value);
9282 }
9283
9284 //print $type.'-'.$size.'-'.$value;
9285 $out = $value;
9286
9287 return is_null($out) ? '' : $out;
9288 }
9289
9296 public function clearFieldError($fieldKey)
9297 {
9298 $this->error = '';
9299 unset($this->validateFieldsErrors[$fieldKey]);
9300 }
9301
9309 public function setFieldError($fieldKey, $msg = '')
9310 {
9311 global $langs;
9312 if (empty($msg)) {
9313 $msg = $langs->trans("UnknownError");
9314 }
9315
9316 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9317 }
9318
9325 public function getFieldError($fieldKey)
9326 {
9327 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9328 return $this->validateFieldsErrors[$fieldKey];
9329 }
9330 return '';
9331 }
9332
9341 public function validateField($fields, $fieldKey, $fieldValue)
9342 {
9343 global $langs;
9344
9345 if (!class_exists('Validate')) {
9346 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9347 }
9348
9349 $this->clearFieldError($fieldKey);
9350
9351 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9352 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9353 return false;
9354 }
9355
9356 $val = $fields[$fieldKey];
9357
9358 $param = array();
9359 $param['options'] = array();
9360 $type = $val['type'];
9361
9362 $required = false;
9363 if (isset($val['notnull']) && $val['notnull'] === 1) {
9364 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9365 $required = true;
9366 }
9367
9368 $maxSize = 0;
9369 $minSize = 0;
9370
9371 //
9372 // PREPARE Elements
9373 //
9374 $reg = array();
9375
9376 // Convert var to be able to share same code than showOutputField of extrafields
9377 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9378 $type = 'varchar'; // convert varchar(xx) int varchar
9379 $maxSize = (int) $reg[1];
9380 } elseif (preg_match('/varchar/', $type)) {
9381 $type = 'varchar'; // convert varchar(xx) int varchar
9382 }
9383
9384 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9385 $type = 'select';
9386 }
9387
9388 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9389 $type = 'link';
9390 }
9391
9392 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9393 $param['options'] = $val['arrayofkeyval'];
9394 }
9395
9396 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9397 $type = 'link';
9398 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9399 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9400 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9401 $type = 'sellist';
9402 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9403 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9404 $type = 'sellist';
9405 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9406 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9407 $type = 'sellist';
9408 }
9409
9410 //
9411 // TEST Value
9412 //
9413
9414 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9415 $validate = new Validate($this->db, $langs);
9416
9417
9418 // little trick : to perform tests with good performances sort tests by quick to low
9419
9420 //
9421 // COMMON TESTS
9422 //
9423
9424 // Required test and empty value
9425 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9426 $this->setFieldError($fieldKey, $validate->error);
9427 return false;
9428 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9429 // if no value sent and the field is not mandatory, no need to perform tests
9430 return true;
9431 }
9432
9433 // MAX Size test
9434 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9435 $this->setFieldError($fieldKey, $validate->error);
9436 return false;
9437 }
9438
9439 // MIN Size test
9440 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9441 $this->setFieldError($fieldKey, $validate->error);
9442 return false;
9443 }
9444
9445 //
9446 // TESTS for TYPE
9447 //
9448
9449 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9450 if (!$validate->isTimestamp($fieldValue)) {
9451 $this->setFieldError($fieldKey, $validate->error);
9452 return false;
9453 } else {
9454 return true;
9455 }
9456 } elseif ($type == 'duration') {
9457 if (!$validate->isDuration($fieldValue)) {
9458 $this->setFieldError($fieldKey, $validate->error);
9459 return false;
9460 } else {
9461 return true;
9462 }
9463 } elseif (in_array($type, array('double', 'real', 'price'))) {
9464 // is numeric
9465 if (!$validate->isNumeric($fieldValue)) {
9466 $this->setFieldError($fieldKey, $validate->error);
9467 return false;
9468 } else {
9469 return true;
9470 }
9471 } elseif ($type == 'boolean') {
9472 if (!$validate->isBool($fieldValue)) {
9473 $this->setFieldError($fieldKey, $validate->error);
9474 return false;
9475 } else {
9476 return true;
9477 }
9478 } elseif ($type == 'mail' || $type == 'email') {
9479 if (!$validate->isEmail($fieldValue)) {
9480 $this->setFieldError($fieldKey, $validate->error);
9481 return false;
9482 }
9483 } elseif ($type == 'url') {
9484 if (!$validate->isUrl($fieldValue)) {
9485 $this->setFieldError($fieldKey, $validate->error);
9486 return false;
9487 } else {
9488 return true;
9489 }
9490 } elseif ($type == 'phone') {
9491 if (!$validate->isPhone($fieldValue)) {
9492 $this->setFieldError($fieldKey, $validate->error);
9493 return false;
9494 } else {
9495 return true;
9496 }
9497 } elseif ($type == 'select' || $type == 'radio') {
9498 if (!isset($param['options'][$fieldValue])) {
9499 $this->error = $langs->trans('RequireValidValue');
9500 return false;
9501 } else {
9502 return true;
9503 }
9504 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9505 $param_list = array_keys($param['options']);
9506 $InfoFieldList = explode(":", $param_list[0]);
9507 $value_arr = explode(',', $fieldValue);
9508
9509 $selectkey = "rowid";
9510 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9511 $selectkey = $InfoFieldList[2];
9512 }
9513
9514 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9515 $this->setFieldError($fieldKey, $validate->error);
9516 return false;
9517 } else {
9518 return true;
9519 }
9520 } elseif ($type == 'link') {
9521 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9522 $InfoFieldList = explode(":", $param_list[0]);
9523 $classname = $InfoFieldList[0];
9524 $classpath = $InfoFieldList[1];
9525 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9526 $lastIsFetchableError = $validate->error;
9527
9528 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9529 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9530 return true;
9531 }
9532
9533 $this->setFieldError($fieldKey, $lastIsFetchableError);
9534 return false;
9535 } else {
9536 return true;
9537 }
9538 }
9539
9540 // if no test failed all is ok
9541 return true;
9542 }
9543
9557 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9558 {
9559 global $db, $conf, $langs, $action, $form, $hookmanager;
9560 global $objectoffield;
9561
9562 if (!is_object($form)) {
9563 $form = new Form($db);
9564 }
9565 if (!is_object($extrafields)) {
9566 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9567 return 'Bad parameter extrafields for showOptionals';
9568 }
9569 if (!is_array($extrafields->attributes[$this->table_element])) {
9570 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9571 }
9572
9573 // Ensure $objectoffield is available for dol_eval visibility formulas.
9574 $objectoffield = $this;
9575
9576 $out = '';
9577
9578 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9579 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9580 $hookResPrint = $hookmanager->resPrint;
9581
9582 if (empty($reshook)) {
9583 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) {
9584 $out .= "\n";
9585 $out .= '<!-- commonobject:showOptionals --> ';
9586 $out .= "\n";
9587
9588 $nbofextrafieldsshown = 0;
9589 $e = 0; // var to manage the modulo (odd/even)
9590
9591 $lastseparatorkeyfound = '';
9592 $extrafields_collapse_num = '';
9593 $extrafields_collapse_num_old = '';
9594 $i = 0;
9595
9596 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9597 $i++;
9598
9599 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9600 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9601 continue;
9602 }
9603
9604 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9605 $enabled = 1;
9606 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9607 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9608 }
9609 if (empty($enabled)) {
9610 continue;
9611 }
9612
9613 $visibility = 1;
9614 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9615 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9616 }
9617
9618 $perms = 1;
9619 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9620 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9621 }
9622
9623 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9624 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9625 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9626 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9627 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9628 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9629 $ef_name = 'options_' . $key;
9630 $ef_value = $this->array_options[$ef_name] ?? '';
9631 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . dol_htmlentities($ef_value) . '" />' . "\n"; // If trouble to preserve content, we can try dol_htmlentities() instead, but real solution is to remove completely the hidden field (see previous TODO).
9632 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9633 } elseif ($mode == 'view' && empty($visibility)) {
9634 continue;
9635 }
9636 if (empty($perms)) {
9637 continue;
9638 }
9639
9640 // Load language if required
9641 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9642 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9643 }
9644
9645 $colspan = 0;
9646 $value = null;
9647 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9648 if (array_key_exists('cols', $params)) {
9649 $colspan = $params['cols'];
9650 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9651 $reg = array();
9652 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9653 $colspan = $reg[1];
9654 } else {
9655 $colspan = $params['colspan'];
9656 }
9657 }
9658 }
9659 $colspan = intval($colspan);
9660
9661 switch ($mode) {
9662 case "view":
9663 $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
9664 break;
9665 case "create":
9666 case "edit":
9667 // We get the value of property found with GETPOST so it takes into account:
9668 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9669 $check = 'alphanohtml';
9670 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9671 $check = 'restricthtml';
9672 }
9673 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9674 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9675 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9676 if (is_array($getposttemp)) {
9677 // $getposttemp is an array but following code expects a comma separated string
9678 $value = implode(",", $getposttemp);
9679 } else {
9680 $value = $getposttemp;
9681 }
9682 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9683 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9684 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('varchar', 'char'))) {
9685 $value = (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] !== '') ? $this->array_options["options_".$key] : '';
9686 } else {
9687 $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.
9688 }
9689 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9690 break;
9691 }
9692
9693 $nbofextrafieldsshown++;
9694
9695 // Output value of the current field
9696 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9697 $extrafields_collapse_num = $key;
9698 /*
9699 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9700 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9701 $extrafield_param_list = array_keys($extrafield_param['options']);
9702
9703 if (count($extrafield_param_list) > 0) {
9704 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9705
9706 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9707 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9708 $extrafields_collapse_num = $key;
9709 }
9710 }
9711 }
9712 */
9713
9714 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9715 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9716
9717 $lastseparatorkeyfound = $key;
9718 } else {
9719 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9720
9721 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9722 $csstyle = '';
9723 if (is_array($params) && count($params) > 0) {
9724 if (array_key_exists('class', $params)) {
9725 $class .= $params['class'].' ';
9726 }
9727 if (array_key_exists('style', $params)) {
9728 $csstyle = $params['style'];
9729 }
9730 }
9731
9732 // add html5 elements
9733 $domData = ' data-element="extrafield"';
9734 $domData .= ' data-targetelement="'.$this->element.'"';
9735 $domData .= ' data-targetid="'.$this->id.'"';
9736
9737 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9738 if ($display_type == 'card') {
9739 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9740 $colspan = 0;
9741 }
9742
9743 if ($action == 'selectlines') {
9744 $colspan++;
9745 }
9746 }
9747
9748 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9749 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9750 $force_values_on_change_company = (
9751 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9752 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9753 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9754 );
9755 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9756 $value = $this->array_options['options_'.$key] ?? $value;
9757 }
9758
9759 // Convert date into timestamp format (value in memory must be a timestamp)
9760 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9761 $datenotinstring = null;
9762 if (array_key_exists('options_'.$key, $this->array_options)) {
9763 $datenotinstring = $this->array_options['options_'.$key];
9764 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9765 $datenotinstring = $this->db->jdate($datenotinstring);
9766 }
9767 }
9768 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9769 $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;
9770 }
9771 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9772 $datenotinstring = null;
9773 if (array_key_exists('options_'.$key, $this->array_options)) {
9774 $datenotinstring = $this->array_options['options_'.$key];
9775 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9776 $datenotinstring = $this->db->jdate($datenotinstring);
9777 }
9778 }
9779 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9780 $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;
9781 }
9782 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9783 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9784 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9785 $value = price2num($value);
9786 } elseif (isset($this->array_options['options_'.$key])) {
9787 $value = $this->array_options['options_'.$key];
9788 }
9789 }
9790
9791 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9792 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9793 if ($action == 'create' || $mode == 'create') {
9794 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9795 }
9796 }
9797
9798 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9799 if ($action == 'create') {
9800 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9801 }
9802 }
9803
9804
9805 $labeltoshow = $langs->trans($label);
9806 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9807 if ($display_type == 'card') {
9808 $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.' >';
9809 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9810 $out .= '<td></td>';
9811 }
9812 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9813 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9814 $out .= ' tdtop';
9815 }
9816 } elseif ($display_type == 'line') {
9817 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9818 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9819 }
9820 //$out .= "titlefield";
9821 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9822 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9823 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9824 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9825 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9826 $out .= ' fieldrequired';
9827 }
9828 }
9829 $out .= '">';
9830 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9831 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9832 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9833 } else {
9834 $out .= $labeltoshow;
9835 }
9836 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9837 $out .= '&nbsp;<span style="color: red">*</span>';
9838 }
9839 } else {
9840 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9841 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9842 } else {
9843 $out .= $labeltoshow;
9844 }
9845 }
9846
9847 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9848
9849 // Second column
9850 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9851 if ($display_type == 'card') {
9852 // 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
9853 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9854 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9855 $out .= '>';
9856 } elseif ($display_type == 'line') {
9857 $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].'">';
9858 }
9859
9860 switch ($mode) {
9861 case "view":
9862 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9863 break;
9864 case "create":
9865 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9866 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9867 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9868 }
9869 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9870 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9871 break;
9872 case "edit":
9873 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9874 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9875 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9876 }
9877 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9878 break;
9879 }
9880
9881 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9882 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9883 $e++;
9884 }
9885 }
9886 $out .= "\n";
9887 // Add code to manage list depending on others
9888 if (!empty($conf->use_javascript_ajax)) {
9889 $out .= $this->getJSListDependancies();
9890 }
9891
9892 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9893
9894 if (empty($nbofextrafieldsshown)) {
9895 $out = '';
9896 }
9897 }
9898 }
9899
9900 $out .= $hookResPrint;
9901
9902 return $out;
9903 }
9904
9909 public function getJSListDependancies($type = '_extra')
9910 {
9911 $out = '
9912 <script nonce="'.getNonce().'">
9913 jQuery(document).ready(function() {
9914 function showOptions'.$type.'(child_list, parent_list, orig_select)
9915 {
9916 var val = $("select[name=\""+parent_list+"\"]").val();
9917 var parentVal = parent_list + ":" + val;
9918 if(typeof val == "string"){
9919 if(val != "") {
9920 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9921 $("select[name=\""+child_list+"\"] option[parent]").remove();
9922 $("select[name=\""+child_list+"\"]").append(options);
9923 } else {
9924 var options = orig_select.find("option[parent]").clone();
9925 $("select[name=\""+child_list+"\"] option[parent]").remove();
9926 $("select[name=\""+child_list+"\"]").append(options);
9927 }
9928 } else if(val > 0) {
9929 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9930 $("select[name=\""+child_list+"\"] option[parent]").remove();
9931 $("select[name=\""+child_list+"\"]").append(options);
9932 } else {
9933 var options = orig_select.find("option[parent]").clone();
9934 $("select[name=\""+child_list+"\"] option[parent]").remove();
9935 $("select[name=\""+child_list+"\"]").append(options);
9936 }
9937 }
9938 function setListDependencies'.$type.'() {
9939 jQuery("select option[parent]").parent().each(function() {
9940 var orig_select = {};
9941 var child_list = $(this).attr("name");
9942 orig_select[child_list] = $(this).clone();
9943 var parent = $(this).find("option[parent]:first").attr("parent");
9944 var infos = parent.split(":");
9945 var parent_list = infos[0];
9946
9947 //Hide daughters lists
9948 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9949 $("#"+child_list).hide();
9950 //Show mother lists
9951 } else if ($("#"+parent_list).val() != 0){
9952 $("#"+parent_list).show();
9953 }
9954 //Show the child list if the parent list value is selected
9955 $("select[name=\""+parent_list+"\"]").click(function() {
9956 if ($(this).val() != 0){
9957 $("#"+child_list).show()
9958 }
9959 });
9960
9961 //When we change parent list
9962 $("select[name=\""+parent_list+"\"]").change(function() {
9963 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9964 //Select the value 0 on child list after a change on the parent list
9965 $("#"+child_list).val(0).trigger("change");
9966 //Hide child lists if the parent value is set to 0
9967 if ($(this).val() == 0){
9968 $("#"+child_list).hide();
9969 }
9970 });
9971 });
9972 }
9973
9974 setListDependencies'.$type.'();
9975 });
9976 </script>'."\n";
9977 return $out;
9978 }
9979
9985 public function getRights()
9986 {
9987 global $user;
9988
9989 $module = empty($this->module) ? '' : $this->module;
9990 $element = $this->element;
9991
9992 if ($element == 'facturerec') {
9993 $element = 'facture';
9994 } elseif ($element == 'invoice_supplier_rec') {
9995 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9996 } elseif ($module && $user->hasRight($module, $element)) {
9997 // for modules built with ModuleBuilder
9998 return $user->hasRight($module, $element);
9999 }
10000
10001 return $user->rights->$element;
10002 }
10003
10016 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10017 {
10018 global $hookmanager;
10019
10020 $parameters = array(
10021 'origin_id' => $origin_id,
10022 'dest_id' => $dest_id,
10023 'tables' => $tables,
10024 );
10025 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
10026 if ($reshook) {
10027 return true; // replacement code
10028 } elseif ($reshook < 0) {
10029 return $ignoreerrors === 1; // failure
10030 } // reshook = 0 => execute normal code
10031
10032 foreach ($tables as $table) {
10033 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
10034
10035 if (!$dbs->query($sql)) {
10036 if ($ignoreerrors) {
10037 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.
10038 }
10039 //$this->errors = $db->lasterror();
10040 return false;
10041 }
10042 }
10043
10044 return true;
10045 }
10046
10059 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
10060 {
10061 foreach ($tables as $table) {
10062 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
10063
10064 if (!$dbs->query($sql)) {
10065 if ($ignoreerrors) {
10066 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.
10067 }
10068 //$this->errors = $db->lasterror();
10069 return false;
10070 }
10071 }
10072
10073 return true;
10074 }
10075
10088 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
10089 {
10090 global $conf;
10091
10092 $buyPrice = 0;
10093
10094 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10095 // When ForceBuyingPriceIfNull is set
10096 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10097 } else {
10098 // Get cost price for margin calculation
10099 if (!empty($fk_product) && $fk_product > 0) {
10100 $result = 0;
10101 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10102 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10103 $product = new Product($this->db);
10104 $result = $product->fetch($fk_product);
10105 if ($result <= 0) {
10106 $this->errors[] = 'ErrorProductIdDoesNotExists';
10107 return -1;
10108 }
10109 if ($product->cost_price > 0) {
10110 $buyPrice = $product->cost_price;
10111 } elseif ($product->pmp > 0) {
10112 $buyPrice = $product->pmp;
10113 }
10114 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10115 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10116 $product = new Product($this->db);
10117 $result = $product->fetch($fk_product);
10118 if ($result <= 0) {
10119 $this->errors[] = 'ErrorProductIdDoesNotExists';
10120 return -1;
10121 }
10122 if ($product->pmp > 0) {
10123 $buyPrice = $product->pmp;
10124 }
10125 }
10126
10127 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10128 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10129 $productFournisseur = new ProductFournisseur($this->db);
10130 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
10131 $buyPrice = $productFournisseur->fourn_unitprice;
10132 } elseif ($result < 0) {
10133 $this->errors[] = $productFournisseur->error;
10134 return -2;
10135 }
10136 }
10137 }
10138 }
10139
10140 return (float) $buyPrice;
10141 }
10142
10150 public function getDataToShowPhoto($modulepart, $imagesize)
10151 {
10152 // See getDataToShowPhoto() implemented by Product for example.
10153 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10154 }
10155
10156
10157 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10177 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')
10178 {
10179 // phpcs:enable
10180 global $user, $langs;
10181
10182 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10183 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10184 include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
10185
10186 $sortfield = 'position_name';
10187 $sortorder = 'asc';
10188
10189 $dir = $sdir.'/';
10190 $pdir = '/';
10191
10192 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10193 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10194
10195 // For backward compatibility
10196 if ($modulepart == 'product') {
10197 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10198 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10199 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10200 }
10201 }
10202 if ($modulepart == 'category') {
10203 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10204 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10205 }
10206
10207 // Defined relative dir to DOL_DATA_ROOT
10208 $relativedir = '';
10209 if ($dir) {
10210 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10211 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10212 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10213 }
10214
10215 $dirthumb = $dir.'thumbs/';
10216 $pdirthumb = $pdir.'thumbs/';
10217
10218 $return = '<!-- Photo -->'."\n";
10219 $nbphoto = 0;
10220
10221 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10222
10223 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10224 {
10225 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10226 $filearray=array_merge($filearray, $filearrayold);
10227 }*/
10228
10229 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10230 '@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';
10231
10232 $useLinkPathPhoto = false;
10233
10234 if (getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO')) {
10235 $link = new Link($this->db);
10236 $links = array();
10237 $link->fetchAll($links, 'product', $this->id);
10238
10239 if (count($links) > 0) {
10240 $useLinkPathPhoto = true;
10241 }
10242
10243 if ($useLinkPathPhoto) {
10244 // Start table or div based on nbbyrow
10245 if ($nbbyrow > 0) {
10246 $return .= '<table class="valigntop center centpercent" style="border:0; padding:2px; border-spacing:2px; border-collapse: separate;">';
10247 }
10248
10249 $i = 0;
10250 foreach ($links as $link) {
10251 if (!empty($link->url) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $link->url)) {
10252 $url = dol_escape_htmltag($link->url);
10253 $i++;
10254 $nbphoto++;
10255
10256 if ($nbbyrow > 0 && ($i % $nbbyrow == 1)) {
10257 $return .= '<tr class="center valignmiddle">';
10258 }
10259
10260 if ($nbbyrow > 0) {
10261 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%" class="photo">';
10262 } else {
10263 $return .= '<div class="inline-block">';
10264 }
10265
10266 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'"'.($maxHeight ? ' height="'.$maxHeight.'"' : '').' src="'.$url.'" title="External image">';
10267
10268 if ($nbbyrow > 0) {
10269 $return .= '</td>';
10270 if ($i % $nbbyrow == 0) {
10271 $return .= '</tr>';
10272 }
10273 } else {
10274 $return .= '</div>';
10275 }
10276
10277 if ($nbmax && $nbphoto >= $nbmax) {
10278 break;
10279 }
10280 }
10281 }
10282
10283 if ($nbbyrow > 0) {
10284 while ($i % $nbbyrow) {
10285 $return .= '<td style="width:'.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10286 $i++;
10287 }
10288 if ($nbphoto) {
10289 $return .= '</table>';
10290 }
10291 }
10292 }
10293 }
10294
10295 if (count($filearray)) {
10296 if ($sortfield && $sortorder) {
10297 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10298 }
10299
10300 foreach ($filearray as $key => $val) {
10301 $photo = '';
10302 $file = $val['name'];
10303
10304 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10305 if (image_format_supported($file) >= 0) {
10306 $nbphoto++;
10307 $photo = $file;
10308 $viewfilename = $file;
10309
10310 if ($size == 1 || $size == 'small') { // Format vignette
10311 // Find name of thumb file
10312 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10313 if (!dol_is_file($dirthumb.$photo_vignette)) {
10314 // The thumb does not exists, so we will use the original file
10315 $dirthumb = $dir;
10316 $pdirthumb = $pdir;
10317 $photo_vignette = basename($file);
10318 }
10319
10320 // Get filesize of original file
10321 $imgarray = dol_getImageSize($dir.$photo);
10322
10323 if ($nbbyrow > 0) {
10324 if ($nbphoto == 1) {
10325 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10326 }
10327
10328 if ($nbphoto % $nbbyrow == 1) {
10329 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10330 }
10331 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10332 } elseif ($nbbyrow < 0) {
10333 $return .= '<div class="inline-block">'."\n";
10334 }
10335
10336 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10337 if (empty($nolink)) {
10338 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10339 if ($urladvanced) {
10340 $return .= '<a href="'.$urladvanced.'">';
10341 } else {
10342 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10343 }
10344 }
10345
10346 // Show image (width height=$maxHeight)
10347 // If thumb file available and image source is too large, we use the thumb, otherwise we use the original photo
10348 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10349 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10350 if ($overwritetitle) {
10351 if (is_numeric($overwritetitle)) {
10352 $alt = '';
10353 } else {
10354 $alt = $overwritetitle;
10355 }
10356 }
10357 if (empty($cache) && !empty($val['label'])) {
10358 // label is md5 of file
10359 // use it in url to say we want to cache this version of the file
10360 $cache = $val['label'];
10361 }
10362 if ($usesharelink) {
10363 if (array_key_exists('share', $val) && $val['share']) {
10364 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10365 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10366 $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).'">';
10367 } else {
10368 $return .= '<!-- Show original file -->';
10369 $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).'">';
10370 }
10371 } else {
10372 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10373 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10374 }
10375 } else {
10376 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10377 $return .= '<!-- Show thumb -->';
10378 $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).'">';
10379 } else {
10380 $return .= '<!-- Show original file -->';
10381 $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).'">';
10382 }
10383 }
10384
10385 if (empty($nolink)) {
10386 $return .= '</a>';
10387 }
10388
10389 if ($showfilename) {
10390 $return .= '<br>'.$viewfilename;
10391 }
10392 if ($showaction) {
10393 $return .= '<br>';
10394 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10395 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10396 $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>';
10397 }
10398 // Special case for product
10399 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10400 // Link to resize
10401 $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; ';
10402
10403 // Link to delete
10404 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10405 $return .= img_delete().'</a>';
10406 }
10407 }
10408 $return .= "\n";
10409
10410 if ($nbbyrow > 0) {
10411 $return .= '</td>';
10412 if (($nbphoto % $nbbyrow) == 0) {
10413 $return .= '</tr>';
10414 }
10415 } elseif ($nbbyrow < 0) {
10416 $return .= '</div>'."\n";
10417 }
10418 }
10419
10420 if (empty($size)) { // Format origine
10421 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10422
10423 if ($showfilename) {
10424 $return .= '<br>'.$viewfilename;
10425 }
10426 if ($showaction) {
10427 // Special case for product
10428 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10429 // Link to resize
10430 $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; ';
10431
10432 // Link to delete
10433 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10434 $return .= img_delete().'</a>';
10435 }
10436 }
10437 }
10438
10439 // On continue ou on arrete de boucler ?
10440 if ($nbmax && $nbphoto >= $nbmax) {
10441 break;
10442 }
10443 }
10444 }
10445
10446 if ($size == 1 || $size == 'small') {
10447 if ($nbbyrow > 0) {
10448 // Ferme tableau
10449 while ($nbphoto % $nbbyrow) {
10450 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10451 $nbphoto++;
10452 }
10453
10454 if ($nbphoto) {
10455 $return .= '</table>';
10456 }
10457 }
10458 }
10459 }
10460
10461 $this->nbphoto = $nbphoto;
10462
10463 return $return;
10464 }
10465
10466
10473 protected function isArray($info)
10474 {
10475 if (is_array($info)) {
10476 if (isset($info['type']) && $info['type'] == 'array') {
10477 return true;
10478 } else {
10479 return false;
10480 }
10481 }
10482 return false;
10483 }
10484
10491 public function isDate($info)
10492 {
10493 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10494 return true;
10495 }
10496 return false;
10497 }
10498
10505 public function isDuration($info)
10506 {
10507 if (is_array($info)) {
10508 if (isset($info['type']) && ($info['type'] == 'duration')) {
10509 return true;
10510 } else {
10511 return false;
10512 }
10513 } else {
10514 return false;
10515 }
10516 }
10517
10524 public function isInt($info)
10525 {
10526 if (is_array($info)) {
10527 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10528 return true;
10529 } else {
10530 return false;
10531 }
10532 } else {
10533 return false;
10534 }
10535 }
10536
10543 public function isFloat($info)
10544 {
10545 if (is_array($info)) {
10546 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10547 return true;
10548 } else {
10549 return false;
10550 }
10551 }
10552 return false;
10553 }
10554
10561 public function isText($info)
10562 {
10563 if (is_array($info)) {
10564 if (isset($info['type']) && $info['type'] == 'text') {
10565 return true;
10566 } else {
10567 return false;
10568 }
10569 }
10570 return false;
10571 }
10572
10579 protected function canBeNull($info)
10580 {
10581 if (is_array($info)) {
10582 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10583 return true;
10584 } else {
10585 return false;
10586 }
10587 }
10588 return true;
10589 }
10590
10597 protected function isForcedToNullIfZero($info)
10598 {
10599 if (is_array($info)) {
10600 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10601 return true;
10602 } else {
10603 return false;
10604 }
10605 }
10606 return false;
10607 }
10608
10615 protected function isIndex($info)
10616 {
10617 if (is_array($info)) {
10618 if (array_key_exists('index', $info) && $info['index'] == true) {
10619 return true;
10620 } else {
10621 return false;
10622 }
10623 }
10624 return false;
10625 }
10626
10627
10636 protected function setSaveQuery()
10637 {
10638 global $conf;
10639
10640 $queryarray = array();
10641 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10642 // Depending on field type ('datetime', ...)
10643 if ($this->isDate($info)) {
10644 if (empty($this->{$field})) {
10645 $queryarray[$field] = null;
10646 } else {
10647 $queryarray[$field] = $this->db->idate($this->{$field});
10648 }
10649 } elseif ($this->isDuration($info)) {
10650 // $this->{$field} may be null, '', 0, '0', 123, '123'
10651 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10652 if (!isset($this->{$field})) {
10653 if (!empty($info['default'])) {
10654 $queryarray[$field] = $info['default']; // From safe source @phan-suppress-current-line SqlInjection
10655 } else {
10656 $queryarray[$field] = 0;
10657 }
10658 } else {
10659 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10660 }
10661 } else {
10662 $queryarray[$field] = null;
10663 }
10664 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10665 if ($field == 'entity' && is_null($this->{$field})) {
10666 $queryarray[$field] = ((int) $conf->entity);
10667 } else {
10668 // $this->{$field} may be null, '', 0, '0', 123, '123'
10669 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10670 if (!isset($this->{$field})) {
10671 $queryarray[$field] = 0;
10672 } elseif ($this->isInt($info)) {
10673 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10674 } elseif ($this->isFloat($info)) {
10675 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10676 }
10677 } else {
10678 $queryarray[$field] = null;
10679 }
10680 }
10681 } else {
10682 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10683 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10684 $queryarray[$field] = $this->{$field};
10685 }
10686
10687 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10688 unset($queryarray[$field]);
10689 }
10690 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10691 $queryarray[$field] = null; // May force 0 to null
10692 }
10693 }
10694
10695 return $queryarray;
10696 }
10697
10704 public function setVarsFromFetchObj(&$obj)
10705 {
10706 global $db;
10707
10708 foreach ($this->fields as $field => $info) {
10709 if ($this->isDate($info)) {
10710 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') {
10711 $this->$field = '';
10712 } else {
10713 $this->$field = $db->jdate($obj->$field);
10714 }
10715 } elseif ($this->isInt($info)) {
10716 if ($field == 'rowid') {
10717 $this->id = (int) $obj->$field;
10718 } else {
10719 if ($this->isForcedToNullIfZero($info)) {
10720 if (empty($obj->$field)) {
10721 $this->$field = null;
10722 } else {
10723 $this->$field = (int) $obj->$field;
10724 }
10725 } else {
10726 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10727 $this->$field = (int) $obj->$field;
10728 } else {
10729 $this->$field = null;
10730 }
10731 }
10732 }
10733 } elseif ($this->isFloat($info)) {
10734 if ($this->isForcedToNullIfZero($info)) {
10735 if (empty($obj->$field)) {
10736 $this->$field = null;
10737 } else {
10738 $this->$field = (float) $obj->$field;
10739 }
10740 } else {
10741 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10742 $this->$field = (float) $obj->$field;
10743 } else {
10744 $this->$field = null;
10745 }
10746 }
10747 } else {
10748 $this->$field = isset($obj->$field) ? $obj->$field : null;
10749 }
10750 }
10751
10752 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10753 if (!isset($this->fields['ref']) && isset($this->id)) {
10754 $this->ref = (string) $this->id;
10755 }
10756 }
10757
10762 public function emtpyObjectVars()
10763 {
10764 foreach ($this->fields as $field => $arr) {
10765 $this->$field = null;
10766 }
10767 }
10768
10776 public function getFieldList($alias = '', $excludefields = array())
10777 {
10778 $keys = array_keys($this->fields);
10779 if (!empty($alias)) {
10780 $keys_with_alias = array();
10781 foreach ($keys as $fieldname) {
10782 if (!empty($excludefields)) {
10783 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10784 continue;
10785 }
10786 }
10787 $keys_with_alias[] = $this->db->sanitize($alias . '.' . $fieldname);
10788 }
10789 return implode(', ', $keys_with_alias);
10790 } else {
10791 return implode(', ', $keys);
10792 }
10793 }
10794
10802 protected function quote($value, $fieldsentry)
10803 {
10804 if (is_null($value)) {
10805 return 'NULL';
10806 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10807 return price2num((string) $value);
10808 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10809 return (int) $value;
10810 } elseif ($fieldsentry['type'] == 'boolean') {
10811 if ($value) {
10812 return 'true';
10813 } else {
10814 return 'false';
10815 }
10816 } else {
10817 return "'".$this->db->escape($value)."'";
10818 }
10819 }
10820
10821
10829 public function createCommon(User $user, $notrigger = 0)
10830 {
10831 global $conf;
10832 //global $langs; // Should be able to work with $langs loaded
10833
10834 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10835
10836 $error = 0;
10837
10838 $now = dol_now();
10839
10840 $fieldvalues = $this->setSaveQuery();
10841
10842 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10843
10844 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10845 $fieldvalues['date_creation'] = $this->db->idate($now);
10846 $this->date_creation = $this->db->idate($now);
10847 }
10848 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10849 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10850 $fieldvalues['fk_user_creat'] = $user->id;
10851 $this->fk_user_creat = $user->id;
10852 }
10853 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10854 $fieldvalues['user_creation_id'] = $user->id;
10855 $this->user_creation_id = $user->id;
10856 }
10857 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10858 // @phan-suppress-next-line PhanUndeclaredProperty
10859 $tmparray = dol_hash($this->pass, '0', 0, 1);
10860 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10861 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10862 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10863 }
10864 }
10865 if (array_key_exists('ref', $fieldvalues)) {
10866 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10867 }
10868 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
10869 $fieldvalues['tms'] = $this->db->idate($now);
10870 }
10871
10872 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10873
10874 $sanitized_keys = array();
10875 $sanitized_values = array(); // Array to store string forged for SQL syntax
10876 foreach ($fieldvalues as $k => $v) {
10877 $sanitized_keys[$k] = $k; // Safe source @phan-suppress-current-line SqlInjection
10878 $value = $this->fields[$k];
10879 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10880 $sanitized_values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10881 }
10882
10883 // Clean and check mandatory
10884 foreach ($sanitized_keys as $key) {
10885 if (!isset($this->fields[$key])) {
10886 continue;
10887 }
10888 $key_fields = $this->fields[$key];
10889
10890 // If field is an implicit foreign key field (so type = 'integer:...')
10891 if (preg_match('/^integer:/i', $key_fields['type']) && $sanitized_values[$key] == '-1') {
10892 $sanitized_values[$key] = '';
10893 }
10894 if (!empty($key_fields['foreignkey']) && $sanitized_values[$key] == '-1') {
10895 $sanitized_values[$key] = '';
10896 }
10897
10898 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($sanitized_values[$key]) || $sanitized_values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10899 $error++;
10900 global $langs;
10901 if (empty($langs)) {
10902 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10903 $langs = new Translate('', $conf);
10904 $langs->setDefaultLang();
10905 $langs->load("errors");
10906 }
10907 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10908 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10909 }
10910
10911 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10912 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($sanitized_values[$key]) || $sanitized_values[$key] === 'NULL') && array_key_exists('default', $key_fields) && !is_null($key_fields['default'])) {
10913 $sanitized_values[$key] = $this->quote($key_fields['default'], $key_fields);
10914 }
10915
10916 // If field is an implicit foreign key field (so type = 'integer:...')
10917 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($sanitized_values[$key])) {
10918 if (isset($key_fields['default'])) {
10919 $sanitized_values[$key] = ((int) $key_fields['default']);
10920 } else {
10921 $sanitized_values[$key] = 'null';
10922 }
10923 }
10924 if (!empty($key_fields['foreignkey']) && empty($sanitized_values[$key])) {
10925 $sanitized_values[$key] = 'null';
10926 }
10927 }
10928
10929 if ($error) {
10930 return -1;
10931 }
10932
10933 $this->db->begin();
10934
10935 if (!$error) {
10936 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10937 $sql .= " (".implode(", ", $sanitized_keys).')';
10938 $sql .= " VALUES (".implode(", ", $sanitized_values).")"; // $sanitized_values can contains 'abc' or 123
10939
10940 $res = $this->db->query($sql);
10941 if (!$res) {
10942 $error++;
10943 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10944 $this->errors[] = "ErrorRefAlreadyExists";
10945 } else {
10946 $this->errors[] = $this->db->lasterror();
10947 }
10948 }
10949 }
10950
10951 if (!$error) {
10952 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10953 }
10954
10955 // If we have a field ref with a default value of (PROV)
10956 if (!$error) {
10957 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10958 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)') {
10959 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10960 $resqlupdate = $this->db->query($sql);
10961
10962 if ($resqlupdate === false) {
10963 $error++;
10964 $this->errors[] = $this->db->lasterror();
10965 } else {
10966 $this->ref = '(PROV'.$this->id.')';
10967 }
10968 }
10969 }
10970
10971 // Create extrafields
10972 if (!$error) {
10973 $result = $this->insertExtraFields();
10974 if ($result < 0) {
10975 $error++;
10976 }
10977 }
10978
10979 // Create lines
10980 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10981 foreach ($this->lines as $line) {
10982 $keyforparent = $this->fk_element;
10983 $line->$keyforparent = $this->id;
10984
10985 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10986 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10987 if (!is_object($line)) {
10988 $line = (object) $line;
10989 }
10990
10991 $result = 0;
10992 if (method_exists($line, 'insert')) {
10993 $result = $line->insert($user, 1);
10994 } elseif (method_exists($line, 'create')) {
10995 $result = $line->create($user, 1);
10996 }
10997 if ($result < 0) {
10998 $this->error = $line->error;
10999 $this->db->rollback();
11000 return -1;
11001 }
11002 }
11003 }
11004
11005 // Triggers
11006 if (!$error && !$notrigger) {
11007 // Call triggers
11008 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
11009 if ($result < 0) {
11010 $error++;
11011 }
11012 // End call triggers
11013 }
11014
11015 // Commit or rollback
11016 if ($error) {
11017 $this->db->rollback();
11018 return -1;
11019 } else {
11020 $this->db->commit();
11021 return $this->id;
11022 }
11023 }
11024
11025
11035 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
11036 {
11037 if (empty($id) && empty($ref) && empty($morewhere)) {
11038 return -1;
11039 }
11040
11041 $fieldlist = $this->getFieldList('t');
11042 if (empty($fieldlist)) {
11043 return 0;
11044 }
11045
11046 $sql = "SELECT ".$this->db->sanitize($fieldlist, 0, 0, 1);
11047 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
11048
11049 if (!empty($id)) {
11050 $sql .= ' WHERE t.rowid = '.((int) $id);
11051 } elseif (!empty($ref)) {
11052 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
11053 } else {
11054 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
11055 }
11056 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
11057 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
11058 }
11059 if ($morewhere) {
11060 $sql .= $morewhere;
11061 }
11062 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
11063
11064 $res = $this->db->query($sql);
11065 if ($res) {
11066 $obj = $this->db->fetch_object($res);
11067 if ($obj) {
11068 $this->setVarsFromFetchObj($obj);
11069
11070 // Retrieve all extrafield
11071 // fetch optionals attributes and labels
11072 if (empty($noextrafields)) {
11073 $result = $this->fetch_optionals();
11074 if ($result < 0) {
11075 $this->error = $this->db->lasterror();
11076 $this->errors[] = $this->error;
11077 return -4;
11078 }
11079 }
11080
11081 return $this->id;
11082 } else {
11083 return 0;
11084 }
11085 } else {
11086 $this->error = $this->db->lasterror();
11087 $this->errors[] = $this->error;
11088 return -1;
11089 }
11090 }
11091
11099 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
11100 {
11101 $objectlineclassname = get_class($this).'Line';
11102 if (!class_exists($objectlineclassname)) {
11103 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
11104 return -1;
11105 }
11106
11107 $objectline = new $objectlineclassname($this->db);
11108 '@phan-var-force CommonObjectLine $objectline';
11109
11110 $sql = "SELECT ".$objectline->getFieldList('l');
11111 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
11112 $sql .= " WHERE l.fk_".$this->db->sanitize($this->element)." = ".((int) $this->id);
11113 if ($morewhere) {
11114 $sql .= $morewhere;
11115 }
11116 if (isset($objectline->fields['position'])) {
11117 $sql .= $this->db->order('position', 'ASC');
11118 }
11119
11120 $resql = $this->db->query($sql);
11121 if ($resql) {
11122 $num_rows = $this->db->num_rows($resql);
11123 $i = 0;
11124 $this->lines = array();
11125 while ($i < $num_rows) {
11126 $obj = $this->db->fetch_object($resql);
11127 if ($obj) {
11128 $newline = new $objectlineclassname($this->db);
11129 '@phan-var-force CommonObjectLine $newline';
11130 $newline->setVarsFromFetchObj($obj);
11131
11132 // Load also extrafields for the line
11133 if (empty($noextrafields)) {
11134 $newline->fetch_optionals();
11135 }
11136
11137 $this->lines[] = $newline;
11138 }
11139 $i++;
11140 }
11141
11142 return 1;
11143 } else {
11144 $this->error = $this->db->lasterror();
11145 $this->errors[] = $this->error;
11146 return -1;
11147 }
11148 }
11149
11157 public function updateCommon(User $user, $notrigger = 0)
11158 {
11159 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11160
11161 $error = 0;
11162
11163 $now = dol_now();
11164
11165 // $this->oldcopy should have been set by the caller of update
11166 //if (empty($this->oldcopy)) {
11167 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11168 // $this->oldcopy = dol_clone($this, 2);
11169 //}
11170
11171 $fieldvalues = $this->setSaveQuery();
11172
11173 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11174
11175 if (array_key_exists('date_modification', $fieldvalues)) {
11176 $fieldvalues['date_modification'] = $this->db->idate($now);
11177 }
11178 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
11179 $fieldvalues['tms'] = $this->db->idate($now);
11180 }
11181 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11182 $fieldvalues['fk_user_modif'] = $user->id;
11183 }
11184 if (array_key_exists('user_modification_id', $fieldvalues)) {
11185 $fieldvalues['user_modification_id'] = $user->id;
11186 }
11187 // @phan-suppress-next-line PhanUndeclaredProperty
11188 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11189 // @phan-suppress-next-line PhanUndeclaredProperty
11190 $tmparray = dol_hash($this->pass, '0', 0, 1);
11191 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11192 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11193 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11194 }
11195 }
11196 if (array_key_exists('ref', $fieldvalues)) {
11197 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11198 }
11199
11200 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11201
11202 // Add quotes and escape on fields with type string
11203 $keys = array();
11204 $values = array();
11205 $sanitized_tmp = array();
11206 foreach ($fieldvalues as $k => $v) {
11207 $keys[$k] = $k;
11208 $value = $this->fields[$k];
11209 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11210 $values[$k] = $this->quote($v, $value);
11211 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11212 // Clean values for text with selectbox
11213 $v = preg_replace('/\s/', ',', $v);
11214 $v = preg_replace('/,+/', ',', $v);
11215 }
11216 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, SqlInjection
11217 $sanitized_tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
11218 }
11219
11220 // Clean and check mandatory fields
11221 foreach ($keys as $key) {
11222 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11223 $values[$key] = ''; // This is an implicit foreign key field
11224 }
11225 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11226 $values[$key] = ''; // This is an explicit foreign key field
11227 }
11228
11229 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11230 /*
11231 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11232 {
11233 $error++;
11234 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11235 }*/
11236 }
11237
11238 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $sanitized_tmp).' WHERE rowid='.((int) $this->id);
11239
11240 $this->db->begin();
11241
11242 if (!$error) {
11243 $res = $this->db->query($sql);
11244 if (!$res) {
11245 $error++;
11246 $this->errors[] = $this->db->lasterror();
11247 }
11248 }
11249
11250 // Update extrafield
11251 if (!$error) {
11252 $result = $this->insertExtraFields(); // This update extrafields
11253 if ($result < 0) {
11254 $error++;
11255 }
11256 }
11257
11258 // Triggers
11259 if (!$error && !$notrigger) {
11260 // Call triggers
11261 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11262 if ($result < 0) {
11263 $error++;
11264 } //Do also here what you must do to rollback action if trigger fail
11265 // End call triggers
11266 }
11267
11268 // Commit or rollback
11269 if ($error) {
11270 $this->db->rollback();
11271 return -1;
11272 } else {
11273 $this->db->commit();
11274 return $this->id;
11275 }
11276 }
11277
11286 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11287 {
11288 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11289
11290 $error = 0;
11291
11292 $this->db->begin();
11293
11294 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11295 foreach ($this->childtables as $table) {
11296 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($table)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11297 $resql = $this->db->query($sql);
11298 if (!$resql) {
11299 $this->error = $this->db->lasterror();
11300 $this->errors[] = $this->error;
11301 $this->db->rollback();
11302 return -1;
11303 }
11304 }
11305 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11306 $objectisused = $this->isObjectUsed($this->id);
11307 if (!empty($objectisused)) {
11308 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11309 $this->error = 'ErrorRecordHasChildren';
11310 $this->errors[] = $this->error;
11311 $this->db->rollback();
11312 return 0;
11313 }
11314 }
11315
11316 // Delete cascade first
11317 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11318 foreach ($this->childtablesoncascade as $tabletodelete) {
11319 $deleteFromObject = explode(':', $tabletodelete, 4);
11320 if (count($deleteFromObject) >= 2) {
11321 $className = str_replace('@', '', $deleteFromObject[0]);
11322 $filePath = $deleteFromObject[1];
11323 $columnName = $deleteFromObject[2];
11324 $filter = '';
11325 if (!empty($deleteFromObject[3])) {
11326 $filter = $deleteFromObject[3];
11327 }
11328
11329 if (dol_include_once($filePath)) {
11330 $childObject = new $className($this->db);
11331 if (method_exists($childObject, 'deleteByParentField')) {
11332 '@phan-var-force CommonObject $childObject';
11333 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11334 if ($result < 0) {
11335 $error++;
11336 $this->errors[] = $childObject->error;
11337 break;
11338 }
11339 } else {
11340 $error++;
11341 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11342 break;
11343 }
11344 } else {
11345 $error++;
11346 $this->errors[] = 'Cannot include child class file '.$filePath;
11347 break;
11348 }
11349 } else {
11350 // Delete record in child table
11351 $sql = "DELETE FROM ".$this->db->prefix().$this->db->sanitize($tabletodelete)." WHERE ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11352
11353 $resql = $this->db->query($sql);
11354 if (!$resql) {
11355 $error++;
11356 $this->error = $this->db->lasterror();
11357 $this->errors[] = $this->error;
11358 break;
11359 }
11360 }
11361 }
11362 }
11363
11364 if (!$error) {
11365 if (!$notrigger) {
11366 // Call triggers
11367 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11368 if ($result < 0) {
11369 $error++;
11370 } // Do also here what you must do to rollback action if trigger fail
11371 // End call triggers
11372 }
11373 }
11374
11375 // Delete llx_ecm_files
11376 if (!$error) {
11377 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11378 if (!$res) {
11379 $error++;
11380 }
11381 }
11382
11383 if (!$error) {
11384 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11385 // For remove dir
11386 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11387 if (dol_is_dir($dir)) {
11388 if (!dol_delete_dir_recursive($dir)) {
11389 $this->errors[] = 'ErrorFailToDeleteDir';
11390 }
11391 }
11392 }
11393
11394 // Delete linked object
11395 $res = $this->deleteObjectLinked();
11396 if ($res < 0) {
11397 $error++;
11398 }
11399
11400 if (!$error && !empty($this->isextrafieldmanaged)) {
11401 $result = $this->deleteExtraFields();
11402 if ($result < 0) {
11403 $error++;
11404 }
11405 }
11406
11407 if (!$error) {
11408 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11409
11410 $resql = $this->db->query($sql);
11411 if (!$resql) {
11412 $error++;
11413 $this->errors[] = $this->db->lasterror();
11414 }
11415 }
11416
11417 // Commit or rollback
11418 if ($error) {
11419 $this->db->rollback();
11420 return -1;
11421 } else {
11422 $this->db->commit();
11423 return 1;
11424 }
11425 }
11426
11438 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11439 {
11440 global $user;
11441
11442 $error = 0;
11443 $deleted = 0;
11444
11445 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11446
11447 if (!empty($parentId) && !empty($parentField)) {
11448 if (empty($this->table_element)) {
11449 $this->error = 'Property table_element for object is not defined';
11450 $this->errors[] = $this->error;
11451 $error++;
11452 return -1;
11453 }
11454 if (!method_exists($this, 'fetch')) {
11455 $this->error = 'Method fetch for object is not defined';
11456 $this->errors[] = $this->error;
11457 $error++;
11458 return -1;
11459 }
11460 if (!method_exists($this, 'delete')) {
11461 $this->error = 'Method delete for object is not defined';
11462 $this->errors[] = $this->error;
11463 $error++;
11464 return -1;
11465 }
11466
11467 $this->db->begin();
11468
11469 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11470 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11471
11472 // Manage filter
11473 $errormessage = '';
11474 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11475 if ($errormessage) {
11476 $this->errors[] = $errormessage;
11477 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11478 return -1;
11479 }
11480
11481 $resql = $this->db->query($sql);
11482 if (!$resql) {
11483 $this->errors[] = $this->db->lasterror();
11484 $error++;
11485 } else {
11486 while ($obj = $this->db->fetch_object($resql)) {
11487 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11488 if ($result < 0) {
11489 $error++;
11490 $this->errors[] = $this->error;
11491 } else {
11492 $result = $this->delete($user); // @phpstan-ignore-line
11493 if ($result < 0) {
11494 $error++;
11495 $this->errors[] = $this->error;
11496 } else {
11497 $deleted++;
11498 }
11499 }
11500 }
11501 }
11502
11503 if (empty($error)) {
11504 $this->db->commit();
11505 return $deleted;
11506 } else {
11507 $this->error = implode(', ', $this->errors);
11508 $this->db->rollback();
11509 return $error * -1;
11510 }
11511 }
11512
11513 return $deleted;
11514 }
11515
11524 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11525 {
11526 $error = 0;
11527
11528 $tmpforobjectclass = get_class($this);
11529 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11530
11531 $this->db->begin();
11532
11533 // Call trigger
11534 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11535 if ($result < 0) {
11536 $error++;
11537 }
11538 // End call triggers
11539
11540 if (empty($error)) {
11541 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11542 $sql .= " WHERE rowid = ".((int) $idline);
11543
11544 $resql = $this->db->query($sql);
11545 if (!$resql) {
11546 $this->error = "Error ".$this->db->lasterror();
11547 $error++;
11548 }
11549 }
11550
11551 if (empty($error)) {
11552 // Remove extrafields
11553 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11554 '@phan-var-force CommonObjectLine $tmpobjectline';
11555 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11556 $tmpobjectline->id = $idline;
11557 $result = $tmpobjectline->deleteExtraFields();
11558 if ($result < 0) {
11559 $error++;
11560 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11561 }
11562 }
11563 }
11564
11565 if (empty($error)) {
11566 $this->db->commit();
11567 return 1;
11568 } else {
11569 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11570 $this->db->rollback();
11571 return -1;
11572 }
11573 }
11574
11575
11586 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11587 {
11588 $error = 0;
11589
11590 $this->db->begin();
11591
11592 $statusfield = 'status';
11593 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11594 $statusfield = 'fk_statut';
11595 }
11596
11597 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11598 $sql .= " SET ".$this->db->sanitize($statusfield)." = ".((int) $status);
11599 $sql .= " WHERE rowid = ".((int) $this->id);
11600
11601 if ($this->db->query($sql)) {
11602 if (!$error) {
11603 $this->oldcopy = clone $this;
11604 }
11605
11606 if (!$error && !$notrigger) {
11607 // Call trigger
11608 $result = $this->call_trigger($triggercode, $user);
11609 if ($result < 0) {
11610 $error++;
11611 }
11612 }
11613
11614 if (!$error) {
11615 $this->status = $status;
11616 if (property_exists($this, 'statut')) { // For backward compatibility
11617 $this->statut = $status;
11618 }
11619 $this->db->commit();
11620 return 1;
11621 } else {
11622 $this->db->rollback();
11623 return -1;
11624 }
11625 } else {
11626 $this->error = $this->db->error();
11627 $this->db->rollback();
11628 return -1;
11629 }
11630 }
11631
11638 public function initAsSpecimenCommon()
11639 {
11640 global $user;
11641
11642 $this->id = 0;
11643 $this->specimen = 1;
11644 $fields = array(
11645 'label' => 'This is label',
11646 'ref' => 'ABCD1234',
11647 'description' => 'This is a description',
11648 'qty' => 123.12,
11649 'note_public' => 'Public note',
11650 'note_private' => 'Private note',
11651 'date_creation' => (dol_now() - 3600 * 48),
11652 'date_modification' => (dol_now() - 3600 * 24),
11653 'fk_user_creat' => $user->id,
11654 'fk_user_modif' => $user->id,
11655 'date' => dol_now(),
11656 );
11657 foreach ($fields as $key => $value) {
11658 if (array_key_exists($key, $this->fields)) {
11659 $this->{$key} = $value; // @phpstan-ignore-line
11660 }
11661 }
11662
11663 // Force values to default values when known
11664 if (property_exists($this, 'fields')) {
11665 foreach ($this->fields as $key => $value) {
11666 // If fields are already set, do nothing
11667 if (array_key_exists($key, $fields)) {
11668 continue;
11669 }
11670
11671 if (!empty($value['default'])) {
11672 $this->$key = $value['default'];
11673 }
11674 }
11675 }
11676
11677 return 1;
11678 }
11679
11680
11681 /* Part for comments */
11682
11688 public function fetchComments()
11689 {
11690 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11691
11692 $comment = new Comment($this->db);
11693 $result = $comment->fetchAllFor($this->element, $this->id);
11694 if ($result < 0) {
11695 $this->setErrorsFromObject($comment);
11696 return -1;
11697 } else {
11698 $this->comments = $comment->comments;
11699 }
11700 return count($this->comments);
11701 }
11702
11708 public function getNbComments()
11709 {
11710 return count($this->comments);
11711 }
11712
11719 public function trimParameters($parameters)
11720 {
11721 if (!is_array($parameters)) {
11722 return;
11723 }
11724 foreach ($parameters as $parameter) {
11725 if (isset($this->$parameter)) {
11726 $this->$parameter = trim($this->$parameter);
11727 }
11728 }
11729 }
11730
11731 /* Part for categories/tags */
11732
11743 public function getCategoriesCommon($type_categ)
11744 {
11745 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11746
11747 // Get current categories
11748 $c = new Categorie($this->db);
11749 $existing = $c->containing($this->id, $type_categ, 'id');
11750
11751 return $existing;
11752 }
11753
11766 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11767 {
11768 // Handle single category
11769 if (!is_array($categories)) {
11770 $categories = array($categories);
11771 }
11772
11773 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11774
11775 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11776
11777 if (empty($type_categ)) {
11778 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11779 return -1;
11780 }
11781
11782 // Get current categories
11783 $c = new Categorie($this->db);
11784 $existing = $c->containing($this->id, $type_categ, 'id');
11785 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11786 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11787 if (!is_array($existing)) {
11788 $existing = array();
11789 }
11790 if ($remove_existing) {
11791 // Diff
11792 $to_del = array_diff($existing, $categories);
11793 $to_add = array_diff($categories, $existing);
11794 } else {
11795 $to_del = array(); // Nothing to delete
11796 $to_add = array_diff($categories, $existing);
11797 }
11798
11799 $error = 0;
11800 $ok = 0;
11801
11802 // Process
11803 foreach ($to_del as $del) {
11804 if ($c->fetch($del) > 0) {
11805 $result = $c->del_type($this, $type_categ);
11806 if ($result < 0) {
11807 $error++;
11808 $this->error = $c->error;
11809 $this->errors = $c->errors;
11810 break;
11811 } else {
11812 $ok += $result;
11813 }
11814 }
11815 }
11816 foreach ($to_add as $add) {
11817 if ($c->fetch($add) > 0) {
11818 $result = $c->add_type($this, $type_categ);
11819 if ($result < 0) {
11820 $error++;
11821 $this->error = $c->error;
11822 $this->errors = $c->errors;
11823 break;
11824 } else {
11825 $ok += $result;
11826 }
11827 }
11828 }
11829
11830 return $error ? (-1 * $error) : $ok;
11831 }
11832
11841 public function cloneCategories($fromId, $toId, $type = '')
11842 {
11843 $this->db->begin();
11844
11845 if (empty($type)) {
11846 $type = $this->table_element;
11847 }
11848
11849 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11850 $categorystatic = new Categorie($this->db);
11851
11852 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11853 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11854
11855 $sql = "INSERT INTO ".$this->db->sanitize($tablename)." (fk_categorie, ".$this->db->sanitize($fkname).")";
11856 $sql .= " SELECT fk_categorie, ".((int) $toId)." FROM ".$this->db->sanitize($tablename);
11857 $sql .= " WHERE ".$this->db->sanitize($fkname)." = ".((int) $fromId);
11858
11859 if (!$this->db->query($sql)) {
11860 $this->error = $this->db->lasterror();
11861 $this->db->rollback();
11862 return -1;
11863 }
11864
11865 $this->db->commit();
11866 return 1;
11867 }
11868
11875 public function deleteEcmFiles($mode = 0)
11876 {
11877 global $conf;
11878
11879 $this->db->begin();
11880
11881 // Delete in database with mode 0
11882 if ($mode == 0) {
11883 switch ($this->element) {
11884 case 'propal':
11885 $element = 'propale';
11886 break;
11887 case 'product':
11888 $element = 'produit';
11889 break;
11890 case 'order_supplier':
11891 $element = 'fournisseur/commande';
11892 break;
11893 case 'invoice_supplier':
11894 // Special cases that need to use get_exdir to get real dir of object
11895 // In future, all object should use this to define path of documents.
11896 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11897 break;
11898 case 'shipping':
11899 $element = 'expedition/sending';
11900 break;
11901 case 'task':
11902 case 'project_task':
11903 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11904
11905 $project_result = $this->fetchProject();
11906 if ($project_result >= 0) {
11907 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11908 }
11909 // no break
11910 case 'contrat':
11911 $element = 'contract';
11912 break;
11913 default:
11914 $element = $this->element;
11915 }
11916 '@phan-var-force string $element';
11917
11918 // Delete ecm_files_extrafields with mode 0 (using name)
11919 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11920 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11921 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11922 $sql .= ")";
11923
11924 if (!$this->db->query($sql)) {
11925 $this->error = $this->db->lasterror();
11926 $this->db->rollback();
11927 return false;
11928 }
11929
11930 // Delete ecm_files with mode 0 (using name)
11931 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11932 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11933 $sql .= " AND filepath = '".$this->db->escape($element."/".$this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11934
11935 if (!$this->db->query($sql)) {
11936 $this->error = $this->db->lasterror();
11937 $this->db->rollback();
11938 return false;
11939 }
11940 }
11941
11942 // Delete in database with mode 1
11943 if ($mode == 1) {
11944 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11945 $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).")";
11946 $resql = $this->db->query($sql);
11947 if (!$resql) {
11948 $this->error = $this->db->lasterror();
11949 $this->db->rollback();
11950 return false;
11951 }
11952
11953 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11954 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11955 $resql = $this->db->query($sql);
11956 if (!$resql) {
11957 $this->error = $this->db->lasterror();
11958 $this->db->rollback();
11959 return false;
11960 }
11961 }
11962
11963 $this->db->commit();
11964 return true;
11965 }
11966
11975 public function checkActiveProductInLines($status = 'onsale')
11976 {
11977 global $langs;
11978
11979 if (isModEnabled('product') || isModEnabled('service')) {
11980 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
11981
11982 $ret = true;
11983 $tmpproduct = new Product($this->db);
11984 foreach ($this->lines as $line) {
11985 if ($line->fk_product > 0) {
11986 $tmpproduct->fetch($line->fk_product);
11987 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
11988 if (!$tmpproduct->$statustotest) {
11989 $langs->load('products');
11990 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
11991 $ret = false;
11992 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
11993 break;
11994 }
11995 }
11996 }
11997 if (!$ret) {
11998 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
11999 }
12000 return $ret;
12001 } else {
12002 return true;
12003 }
12004 }
12005
12012 public function setErrorWithoutLog($message)
12013 {
12014 $this->error = $message;
12015 $this->errors[] = $message;
12016 }
12017
12025 public function setErrorWithLog($message, $loglevel = LOG_ERR)
12026 {
12027 global $dolibarr_main_document_root;
12028
12029 $this->setErrorWithoutLog($message);
12030 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
12031 $file = str_replace($dolibarr_main_document_root, '', $trace[0]['file'] ?? 'file unknown');
12032 $line = $trace[0]['line'] ?? 'line unknown';
12033 $syslogMessage = sprintf('%s:%s %s', $file, $line, $message);
12034 dol_syslog($syslogMessage, $loglevel);
12035 }
12036}
$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:169
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
getState($id, $withcode='0', $dbtouse=null, $withregion=0, $outputlangs=null, $entconv=1)
Return state translated from an id.
convertSecondToTime($iSecond, $format='all', $lengthOfDay=86400, $lengthOfWeek=7)
Return, in clear text, value of a number of seconds in days, hours and minutes.
Definition date.lib.php:248
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_meta_create($object)
Create a meta file with document file into same directory.
completeFileArrayWithDatabaseInfo(&$filearray, $relativedir, $object=null)
Complete $filearray with data from database.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_is_dir($folder)
Test if filename is a directory.
dol_delete_preview($object)
Delete all preview files linked to object instance.
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getPictoForType($key, $morecss='')
Return the picto for a data type.
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'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dol_htmlentities($string, $flags=ENT_QUOTES|ENT_SUBSTITUTE, $encoding='UTF-8', $double_encode=false)
Replace htmlentities functions.
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.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
getDictionaryValue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
Return the value of a filed into a dictionary for the record $id.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png, bmp and webp)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php: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.