dolibarr 23.0.4
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-2022 Alexandre Spangaro <aspangaro@open-dsi.fr>
11 * Copyright (C) 2016 Bahfir abbes <bafbes@gmail.com>
12 * Copyright (C) 2017 ATM Consulting <support@atm-consulting.fr>
13 * Copyright (C) 2017-2019 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-2025 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*
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <https://www.gnu.org/licenses/>.
37 */
38
45require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
46require_once DOL_DOCUMENT_ROOT.'/core/class/commontrigger.class.php';
47
53abstract class CommonObject
54{
55 use DolDeprecationHandler;
56 use CommonTrigger;
57
61 public $module;
62
66 public $db;
67
71 public $id;
72
76 public $entity;
77
82 public $error;
83
87 public $errorhidden;
88
92 public $errors = array();
93
97 public $warnings = array();
98
102 private $validateFieldsErrors = array();
103
107 public $element;
108
113 public $fk_element;
114
120 public $element_for_permission;
121
125 public $table_element;
126
130 public $table_rowid;
131
135 public $table_element_line = '';
136
141 public $ismultientitymanaged;
142
146 public $import_key;
147
151 public $array_options = array();
152
153
198 public $fields = array();
199
205 public $array_languages = null; // Value is array() when load already tried
206
210 public $contacts_ids;
211
215 public $contacts_ids_internal;
216
220 public $linked_objects;
221
225 public $linkedObjectsIds;
226
230 public $linkedObjects;
231
235 private $linkedObjectsFullLoaded = array();
236
240 public $oldcopy;
241
245 public $oldref;
246
250 protected $table_ref_field = '';
251
255 public $restrictiononfksoc = 0;
256
257
258 // The following vars are used by some objects only.
259 // We keep these properties in CommonObject in order to provide common methods using them.
260
264 public $context = array();
265
269 public $actionmsg;
273 public $actionmsg2;
274
278 public $canvas;
279
284 public $project;
285
290 public $fk_project;
291
297 public $fk_projet;
298
303 public $contact;
304
309 public $contact_id;
310
315 public $thirdparty;
316
321 public $user;
322
327 public $product;
328
333 public $origin_type;
334
339 public $origin_id;
340
345
351 public $origin;
352
361 private $expedition;
362
368 private $livraison;
369
375 private $commandeFournisseur;
376
377
381 public $ref;
382
386 public $ref_ext;
387
391 public $ref_previous;
392
396 public $ref_next;
397
401 public $newref;
402
409 public $statut;
410
419 public $status;
420
425 public $country;
426
431 public $country_id;
432
437 public $country_code;
438
443 public $state;
444
449 public $state_id;
450
455 public $state_code;
456
461 public $region_id;
462
467 public $region_code;
468
473 public $region;
474
475
480 public $barcode_type;
481
486 public $barcode_type_code;
487
492 public $barcode_type_label;
493
498 public $barcode_type_coder;
499
504 public $mode_reglement_id;
505
510 public $cond_reglement_id;
511
515 public $demand_reason_id;
516
521 public $transport_mode_id;
522
530 private $cond_reglement; // Private to call DolDeprecationHandler
534 protected $depr_cond_reglement; // Internal value for deprecation
535
541 public $fk_delivery_address;
542
547 public $shipping_method_id;
548
553 public $shipping_method;
554
555 // Multicurrency
559 public $fk_multicurrency;
560
565 public $multicurrency_code;
566
571 public $multicurrency_tx;
572
576 public $multicurrency_total_ht;
577
581 public $multicurrency_total_tva;
582
586 public $multicurrency_total_localtax1; // not in database
587
591 public $multicurrency_total_localtax2; // not in database
592
596 public $multicurrency_total_ttc;
597
598
603 public $model_pdf;
604
609 public $last_main_doc;
610
616 public $fk_bank;
617
622 public $fk_account;
623
628 public $note_public;
629
634 public $note_private;
635
641 public $note;
642
647 public $total_ht;
648
653 public $total_tva;
654
659 public $total_localtax1;
660
665 public $total_localtax2;
666
671 public $total_ttc;
672
673
677 public $lines;
678
682 public $actiontypecode;
683
688 public $comments = array();
689
693 public $name;
694
698 public $lastname;
699
703 public $firstname;
704
709 public $civility_id;
710
714 public $civility_code;
715
716 // Dates
720 public $date_creation;
721
725 public $date_validation;
726
730 public $date_modification;
731
737 public $tms;
738
744 private $date_update;
745
749 public $date_cloture;
750
754 public $user_creation_id;
755
759 public $user_validation_id;
760
764 public $user_closing_id;
765
769 public $user_modification_id;
770
775 public $fk_user_creat;
776
781 public $fk_user_modif;
782
783
787 public $next_prev_filter;
788
792 public $specimen = 0;
793
797 public $sendtoid;
798
799
804 public $alreadypaid;
805
810 public $totalpaid;
811
816 public $totalpaid_multicurrency;
817
818
822 public $labelStatus = array();
823
827 public $labelStatusShort = array();
828
832 public $tpl;
833
834
838 public $showphoto_on_popup;
839
843 public $nb = array();
844
848 public $nbphoto;
849
853 public $output;
854
858 public $extraparams = array();
859
863 protected $childtables = array();
864
870 protected $childtablesoncascade = array();
871
875 public $cond_reglement_supplier_id;
876
882 public $deposit_percent;
883
884
888 public $retained_warranty_fk_cond_reglement;
889
893 public $warehouse_id;
894
898 public $isextrafieldmanaged = 0;
899
900
901 // No constructor as it is an abstract class
902
908 protected function deprecatedProperties()
909 {
910 return array(
911 'alreadypaid' => 'totalpaid',
912 'cond_reglement' => 'depr_cond_reglement',
913 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
914 'commandeFournisseur' => 'origin_object',
915 'expedition' => 'origin_object',
916 'fk_project' => 'fk_project',
917 'livraison' => 'origin_object',
918 'projet' => 'project',
919 'statut' => 'status',
920 );
921 }
922
923
934 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
935 {
936 global $db, $conf;
937
938 $sql = "SELECT rowid";
939 $sql .= " FROM ".$db->prefix().$element;
940 $sql .= " WHERE entity IN (".getEntity($element).")";
941
942 if ($id > 0) {
943 $sql .= " AND rowid = ".((int) $id);
944 } elseif ($ref) {
945 $sql .= " AND ref = '".$db->escape($ref)."'";
946 } elseif ($ref_ext) {
947 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
948 } else {
949 $error = 'ErrorWrongParameters';
950 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
951 return -1;
952 }
953 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
954 $sql .= " AND entity = ".((int) $conf->entity);
955 }
956
957 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
958 $resql = $db->query($sql);
959 if ($resql) {
960 $num = $db->num_rows($resql);
961 if ($num > 0) {
962 return 1;
963 } else {
964 return 0;
965 }
966 }
967 return -1;
968 }
969
975 public function isEmpty()
976 {
977 return (empty($this->id));
978 }
979
987 {
988 if (!empty($object->error)) {
989 $this->error = $object->error;
990 }
991 if (!empty($object->errors)) {
992 $this->errors = array_merge($this->errors, $object->errors);
993 }
994 }
995
1003 public function getTooltipContentArray($params)
1004 {
1005 return [];
1006 }
1007
1015 public function getTooltipContent($params)
1016 {
1017 global $action, $extrafields, $langs, $hookmanager;
1018
1019 // If there is too much extrafields, we do not include them into tooltip
1020 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
1021
1022 $data = $this->getTooltipContentArray($params);
1023 $count = 0;
1024
1025 // Add extrafields
1026 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1027 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1028 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1029 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1030 continue;
1031 }
1032 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1033 $data['more_extrafields'] = '<br>...';
1034 break;
1035 }
1036 $enabled = 1;
1037 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1038 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1039 }
1040 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1041 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1042 }
1043 $perms = 1;
1044 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1045 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1046 }
1047 if (empty($enabled)) {
1048 continue; // 0 = Never visible field
1049 }
1050 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1051 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1052 }
1053 if (empty($perms)) {
1054 continue; // 0 = Not visible
1055 }
1056 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1057 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1058 }
1059 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1060 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1061 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1062 } else {
1063 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1064 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1065 $count++;
1066 }
1067 }
1068 $data['closedivextra'] = '</div>';
1069 }
1070
1071 $hookmanager->initHooks(array($this->element . 'dao'));
1072 $parameters = array(
1073 'tooltipcontentarray' => &$data,
1074 'params' => $params,
1075 );
1076 // Note that $action and $object may have been modified by some hooks
1077 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1078
1079 //var_dump($data);
1080 $label = implode($data);
1081
1082 return $label;
1083 }
1084
1085
1091 public function errorsToString()
1092 {
1093 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1094 }
1095
1096
1103 public function getFormatedCustomerRef($objref)
1104 {
1105 global $hookmanager;
1106
1107 $parameters = array('objref' => $objref);
1108 $action = '';
1109 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1110 if ($reshook > 0) {
1111 return $hookmanager->resArray['objref'];
1112 }
1113 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1114 }
1115
1122 public function getFormatedSupplierRef($objref)
1123 {
1124 global $hookmanager;
1125
1126 $parameters = array('objref' => $objref);
1127 $action = '';
1128 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1129 if ($reshook > 0) {
1130 return $hookmanager->resArray['objref'];
1131 }
1132 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1133 }
1134
1144 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1145 {
1146 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1147 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1148 $tmparray = getCountry($this->country_id, 'all');
1149 $this->country_code = $tmparray['code'];
1150 $this->country = $tmparray['label'];
1151 }
1152
1153 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1154 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1155 $tmparray = getState($this->state_id, 'all', null, 1);
1156 $this->state_code = $tmparray['code'];
1157 $this->state = $tmparray['label'];
1158 $this->region_code = $tmparray['region_code'];
1159 $this->region = $tmparray['region'];
1160 }
1161
1162 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1163 }
1164
1165
1174 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1175 {
1176 global $user, $dolibarr_main_url_root;
1177
1178 if (empty($this->last_main_doc)) {
1179 return ''; // No way to known which document name to use
1180 }
1181
1182 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1183 $ecmfile = new EcmFiles($this->db);
1184 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1185 if ($result < 0) {
1186 $this->error = $ecmfile->error;
1187 $this->errors = $ecmfile->errors;
1188 return -1;
1189 }
1190
1191 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1192 // Add entry into index
1193 if ($initsharekey) {
1194 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1195
1196 // 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
1197 /*
1198 $ecmfile->filepath = $rel_dir;
1199 $ecmfile->filename = $filename;
1200 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1201 $ecmfile->fullpath_orig = '';
1202 $ecmfile->gen_or_uploaded = 'generated';
1203 $ecmfile->description = ''; // indexed content
1204 $ecmfile->keywords = ''; // keyword content
1205 $ecmfile->share = getRandomPassword(true);
1206 $result = $ecmfile->create($user);
1207 if ($result < 0)
1208 {
1209 $this->error = $ecmfile->error;
1210 $this->errors = $ecmfile->errors;
1211 }
1212 */
1213 } else {
1214 return '';
1215 }
1216 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1217 // Add entry into index
1218 if ($initsharekey) {
1219 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1220 $ecmfile->share = getRandomPassword(true);
1221 $ecmfile->update($user);
1222 } else {
1223 return '';
1224 }
1225 }
1226 // Define $urlwithroot
1227 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1228 // This is to use external domain name found into config file
1229 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1230 //else
1231 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1232 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1233
1234 $forcedownload = 0;
1235
1236 $paramlink = '';
1237 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1238 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1239 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1240 if (!empty($ecmfile->share)) {
1241 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1242 }
1243 if ($forcedownload) {
1244 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1245 }
1246
1247 if ($relativelink) {
1248 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1249 } else {
1250 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1251 }
1252
1253 // Here $ecmfile->share is defined
1254 return $linktoreturn;
1255 }
1256
1257
1258 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1268 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1269 {
1270 // phpcs:enable
1271 global $user, $langs;
1272
1273 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1274
1275 // Check parameters
1276 if ($fk_socpeople <= 0) {
1277 $langs->load("errors");
1278 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1279 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1280 return -1;
1281 }
1282 if (!$type_contact) {
1283 $langs->load("errors");
1284 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1285 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1286 return -2;
1287 }
1288
1289 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && !$user->hasRight('societe', 'client', 'voir')) {
1290 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1291 $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid;
1292 $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id;
1293
1294 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1295
1296 if (!$resql_allowed_contacts) {
1297 $this->errors[] = $this->db->lasterror();
1298 return -3;
1299 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1300 if ($obj->cnt == 0) {
1301 $langs->load("companies");
1302 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1303 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1304 return -4;
1305 }
1306 }
1307 }
1308
1309 $id_type_contact = 0;
1310 if (is_numeric($type_contact)) {
1311 $id_type_contact = $type_contact;
1312 } else {
1313 // We look for id type_contact
1314 $sql = "SELECT tc.rowid";
1315 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1316 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1317 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1318 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1319 //print $sql;
1320 $resql = $this->db->query($sql);
1321 if ($resql) {
1322 $obj = $this->db->fetch_object($resql);
1323 if ($obj) {
1324 $id_type_contact = $obj->rowid;
1325 }
1326 }
1327 }
1328 if ($id_type_contact == 0) {
1329 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");
1330 return 0;
1331 }
1332
1333 $datecreate = dol_now();
1334
1335 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1336 $TListeContacts = $this->liste_contact(-1, $source);
1337 $already_added = false;
1338 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1339 foreach ($TListeContacts as $array_contact) {
1340 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1341 $already_added = true;
1342 break;
1343 }
1344 }
1345 }
1346
1347 if (!$already_added) {
1348 $this->db->begin();
1349
1350 // Insert into database
1351 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1352 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1353 $sql .= " VALUES (".$this->id.", ".((int) $fk_socpeople)." , ";
1354 $sql .= "'".$this->db->idate($datecreate)."'";
1355 $sql .= ", 4, ".((int) $id_type_contact);
1356 $sql .= ")";
1357
1358 $resql = $this->db->query($sql);
1359 if ($resql) {
1360 if (!$notrigger) {
1361 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1362 $result = $this->call_trigger($triggerPrefix.'_ADD_CONTACT', $user);
1363 if ($result < 0) {
1364 $this->db->rollback();
1365 return -5;
1366 }
1367 }
1368
1369 $this->db->commit();
1370 return 1;
1371 } else {
1372 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1373 $this->error = $this->db->errno();
1374 $this->db->rollback();
1375 return -6;
1376 } else {
1377 $this->error = $this->db->lasterror();
1378 $this->db->rollback();
1379 return -7;
1380 }
1381 }
1382 } else {
1383 return 0;
1384 }
1385 }
1386
1387 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1395 public function copy_linked_contact($objFrom, $source = 'internal')
1396 {
1397 // phpcs:enable
1398 $contacts = $objFrom->liste_contact(-1, $source);
1399 foreach ($contacts as $contact) {
1400 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1401 return -1;
1402 }
1403 }
1404 return 1;
1405 }
1406
1407 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1417 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1418 {
1419 // phpcs:enable
1420 // Insert into database
1421 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1422 $sql .= " statut = ".((int) $statut);
1423 if ($type_contact_id) {
1424 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1425 }
1426 if ($fk_socpeople) {
1427 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1428 }
1429 $sql .= " where rowid = ".((int) $rowid);
1430
1431 $resql = $this->db->query($sql);
1432 if ($resql) {
1433 return 0;
1434 } else {
1435 $this->error = $this->db->lasterror();
1436 return -1;
1437 }
1438 }
1439
1440 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1448 public function delete_contact($rowid, $notrigger = 0)
1449 {
1450 // phpcs:enable
1451 global $user;
1452
1453 $error = 0;
1454
1455 // When called on a loaded object, the link must be one of its own contacts (same filter as liste_contact())
1456 if ($this->id > 0) {
1457 $sql = "SELECT ec.rowid FROM ".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc";
1458 $sql .= " WHERE ec.rowid = ".((int) $rowid)." AND ec.element_id = ".((int) $this->id);
1459 $sql .= " AND ec.fk_c_type_contact = tc.rowid AND tc.element = '".$this->db->escape($this->element)."'";
1460 $resql = $this->db->query($sql);
1461 if (!$resql || !$this->db->num_rows($resql)) {
1462 $this->error = 'ErrorRecordNotFound';
1463 return -1;
1464 }
1465 }
1466
1467 $this->db->begin();
1468
1469 if (!$error && empty($notrigger)) {
1470 // Call trigger
1471 $this->context['contact_id'] = ((int) $rowid);
1472 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper($this->element) : $this->TRIGGER_PREFIX);
1473 $result = $this->call_trigger($triggerPrefix.'_DELETE_CONTACT', $user);
1474 if ($result < 0) {
1475 $error++;
1476 }
1477 // End call triggers
1478 }
1479
1480 if (!$error) {
1481 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1482
1483 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1484 $sql .= " WHERE rowid = ".((int) $rowid);
1485
1486 $result = $this->db->query($sql);
1487 if (!$result) {
1488 $error++;
1489 $this->errors[] = $this->db->lasterror();
1490 }
1491 }
1492
1493 if (!$error) {
1494 $this->db->commit();
1495 return 1;
1496 } else {
1497 $this->error = $this->db->lasterror();
1498 $this->db->rollback();
1499 return -1;
1500 }
1501 }
1502
1503 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1511 public function delete_linked_contact($source = '', $code = '')
1512 {
1513 // phpcs:enable
1514 $listId = '';
1515 $temp = array();
1516 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1517
1518 if (!empty($typeContact)) {
1519 foreach ($typeContact as $key => $value) {
1520 array_push($temp, $key);
1521 }
1522 $listId = implode(",", $temp);
1523 }
1524
1525 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1526 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1527 if (empty($listId)) {
1528 return 0;
1529 }
1530
1531 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1532 $sql .= " WHERE element_id = ".((int) $this->id);
1533 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1534
1535 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1536 if ($this->db->query($sql)) {
1537 return 1;
1538 } else {
1539 $this->error = $this->db->lasterror();
1540 return -1;
1541 }
1542 }
1543
1544 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1556 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1557 {
1558 // phpcs:enable
1559 global $langs;
1560
1561 $tab = array();
1562
1563 $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
1564 if ($source == 'internal') {
1565 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1566 }
1567 if ($source == 'external' || $source == 'thirdparty') {
1568 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1569 }
1570 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1571 if (empty($arrayoftcids)) {
1572 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1573 }
1574 $sql .= " FROM";
1575 if (empty($arrayoftcids)) {
1576 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1577 }
1578 $sql .= " ".$this->db->prefix()."element_contact as ec";
1579 if ($source == 'internal') { // internal contact (user)
1580 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1581 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1582 }
1583 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1584 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1585 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1586 }
1587 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1588 if (empty($arrayoftcids)) {
1589 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1590 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1591 if ($code) {
1592 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1593 }
1594 if ($source == 'internal') {
1595 $sql .= " AND tc.source = 'internal'";
1596 }
1597 if ($source == 'external' || $source == 'thirdparty') {
1598 $sql .= " AND tc.source = 'external'";
1599 }
1600 $sql .= " AND tc.active = 1";
1601 } else {
1602 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1603 }
1604 if ($status >= 0) {
1605 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1606 }
1607 if ($statusoflink >= 0) {
1608 $sql .= " AND ec.statut = ".((int) $statusoflink);
1609 }
1610 $sql .= " ORDER BY t.lastname ASC";
1611
1612 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1613 $resql = $this->db->query($sql);
1614 if ($resql) {
1615 $num = $this->db->num_rows($resql);
1616 $i = 0;
1617 while ($i < $num) {
1618 $obj = $this->db->fetch_object($resql);
1619
1620 if (!$list) {
1621 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1622 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1623 $tab[$i] = array(
1624 'parentId' => $this->id,
1625 'source' => $obj->source,
1626 'socid' => $obj->socid,
1627 'id' => $obj->id,
1628 'nom' => $obj->lastname, // For backward compatibility
1629 'civility' => $obj->civility,
1630 'lastname' => $obj->lastname,
1631 'firstname' => $obj->firstname,
1632 'email' => $obj->email,
1633 'address' => $obj->address,
1634 'zip' => $obj->zip,
1635 'town' => $obj->town,
1636 'country_id' => $obj->country_id,
1637 'country' => $obj->country,
1638 'login' => (empty($obj->login) ? '' : $obj->login),
1639 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1640 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1641 'statuscontact' => $obj->statuscontact,
1642 'rowid' => $obj->rowid,
1643 'code' => $obj->code,
1644 'libelle' => $libelle_type,
1645 'status' => (int) $obj->statuslink,
1646 'fk_c_type_contact' => $obj->fk_c_type_contact
1647 );
1648 } else {
1649 $tab[$i] = $obj->id;
1650 }
1651
1652 $i++;
1653 }
1654
1655 return $tab;
1656 } else {
1657 $this->error = $this->db->lasterror();
1658 dol_print_error($this->db);
1659 return -1;
1660 }
1661 }
1662
1663
1670 public function swapContactStatus($rowid)
1671 {
1672 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1673 $sql .= " tc.code, tc.libelle as type_label";
1674 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1675 $sql .= " WHERE ec.rowid =".((int) $rowid);
1676 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1677 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1678
1679 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1680 $resql = $this->db->query($sql);
1681 if ($resql) {
1682 $obj = $this->db->fetch_object($resql);
1683 $newstatut = ($obj->statut == 4) ? 5 : 4;
1684 $result = $this->update_contact($rowid, $newstatut);
1685 $this->db->free($resql);
1686 return $result;
1687 } else {
1688 $this->error = $this->db->error();
1689 dol_print_error($this->db);
1690 return -1;
1691 }
1692 }
1693
1694 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1705 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1706 {
1707 // phpcs:enable
1708 global $langs;
1709
1710 if (empty($order)) {
1711 $order = 'position';
1712 }
1713 if ($order == 'position') {
1714 $order .= ',code';
1715 }
1716
1717 $tab = array();
1718
1719 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1720 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1721 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1722 if ($activeonly == 1) {
1723 $sql .= " AND tc.active = 1"; // only the active types
1724 }
1725 if (!empty($source) && $source != 'all') {
1726 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1727 }
1728 if (!empty($code)) {
1729 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1730 }
1731 $sql .= $this->db->order($order, 'ASC');
1732
1733 //print "sql=".$sql;
1734 $resql = $this->db->query($sql);
1735 if (!$resql) {
1736 $this->error = $this->db->lasterror();
1737 //dol_print_error($this->db);
1738 return null;
1739 }
1740
1741 $num = $this->db->num_rows($resql);
1742 $i = 0;
1743 while ($i < $num) {
1744 $obj = $this->db->fetch_object($resql);
1745
1746 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1747 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1748 if (empty($option)) {
1749 $tab[$obj->rowid] = $libelle_type;
1750 } elseif ($option == 1) {
1751 $tab[$obj->code] = $libelle_type;
1752 } else {
1753 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1754 }
1755 $i++;
1756 }
1757
1758 return $tab;
1759 }
1760
1772 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1773 {
1774 global $langs;
1775
1776 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1777
1778 $tab = array();
1779
1780 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1781 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1782
1783 $sqlWhere = array();
1784 if (!empty($element)) {
1785 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1786 }
1787 if (!empty($excludeelement)) {
1788 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1789 }
1790
1791 if ($activeonly == 1) {
1792 $sqlWhere[] = " tc.active=1"; // only the active types
1793 }
1794
1795 if (!empty($source) && $source != 'all') {
1796 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1797 }
1798
1799 if (!empty($code)) {
1800 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1801 }
1802
1803 if (count($sqlWhere) > 0) {
1804 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1805 }
1806
1807 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1808
1809 dol_syslog(__METHOD__, LOG_DEBUG);
1810 $resql = $this->db->query($sql);
1811 if ($resql) {
1812 $num = $this->db->num_rows($resql);
1813 if ($num > 0) {
1814 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1815
1816 while ($obj = $this->db->fetch_object($resql)) {
1817 $modulename = $obj->module ?? $obj->element;
1818 if (strpos($obj->element, 'project') !== false) {
1819 $modulename = 'projet';
1820 } elseif ($obj->element == 'contrat') {
1821 $element = 'contract';
1822 } elseif ($obj->element == 'action') {
1823 $modulename = 'agenda';
1824 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1825 $modulename = 'fournisseur';
1826 }
1827 if (isModEnabled($modulename)) {
1828 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1829 $tmpelement = $obj->element;
1830 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1831 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1832 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1833 }
1834 }
1835 }
1836 return $tab;
1837 } else {
1838 $this->error = $this->db->lasterror();
1839 return null;
1840 }
1841 }
1842
1854 public function getIdContact($source, $code, $status = 0)
1855 {
1856 $result = array();
1857 $i = 0;
1858 // Particular case for shipping
1859 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1860 $id = $this->origin_id;
1861 $element = 'commande';
1862 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1863 $id = $this->origin_id;
1864 $element = 'order_supplier';
1865 } else {
1866 $id = $this->id;
1867 $element = $this->element;
1868 }
1869
1870 $sql = "SELECT ec.fk_socpeople";
1871 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1872 if ($source == 'internal') {
1873 $sql .= " ".$this->db->prefix()."user as c,";
1874 }
1875 if ($source == 'external') {
1876 $sql .= " ".$this->db->prefix()."socpeople as c,";
1877 }
1878 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1879 $sql .= " WHERE ec.element_id = ".((int) $id);
1880 $sql .= " AND ec.fk_socpeople = c.rowid";
1881 if ($source == 'internal') {
1882 $sql .= " AND c.entity IN (".getEntity('user').")";
1883 }
1884 if ($source == 'external') {
1885 $sql .= " AND c.entity IN (".getEntity('societe').")";
1886 }
1887 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1888 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1889 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1890 if ($code) {
1891 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1892 }
1893 $sql .= " AND tc.active = 1";
1894 if ($status) {
1895 $sql .= " AND ec.statut = ".((int) $status);
1896 }
1897
1898 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1899 $resql = $this->db->query($sql);
1900 if ($resql) {
1901 while ($obj = $this->db->fetch_object($resql)) {
1902 $result[$i] = $obj->fk_socpeople;
1903 $i++;
1904 }
1905 } else {
1906 $this->error = $this->db->error();
1907 return null;
1908 }
1909
1910 return $result;
1911 }
1912
1913 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1920 public function fetch_contact($contactid = null)
1921 {
1922 // phpcs:enable
1923 if (empty($contactid)) {
1924 $contactid = $this->contact_id;
1925 }
1926
1927 if (empty($contactid)) {
1928 return 0;
1929 }
1930
1931 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1932 $contact = new Contact($this->db);
1933 $result = $contact->fetch($contactid);
1934 $this->contact = $contact;
1935 return $result;
1936 }
1937
1938 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1946 public function fetch_thirdparty($force_thirdparty_id = 0)
1947 {
1948 // phpcs:enable
1949 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1950 return 0;
1951 }
1952
1953 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1954
1955 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1956 if ($force_thirdparty_id) {
1957 $idtofetch = $force_thirdparty_id;
1958 }
1959
1960 if ($idtofetch) {
1961 $thirdparty = new Societe($this->db);
1962 $result = $thirdparty->fetch($idtofetch);
1963 if ($result < 0) {
1964 $this->errors = array_merge($this->errors, $thirdparty->errors);
1965 }
1966 $this->thirdparty = $thirdparty;
1967
1968 // Use first price level if level not defined for third party
1969 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
1970 $this->thirdparty->price_level = 1;
1971 }
1972
1973 return $result;
1974 } else {
1975 return -1;
1976 }
1977 }
1978
1979
1987 public function fetchOneLike($ref)
1988 {
1989 if (!$this->table_ref_field) {
1990 return 0;
1991 }
1992
1993 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
1994 $sql .= " WHERE ".$this->table_ref_field." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
1995 $sql .= " LIMIT 1";
1996
1997 $query = $this->db->query($sql);
1998
1999 if (!$this->db->num_rows($query)) {
2000 return 0;
2001 }
2002
2003 $result = $this->db->fetch_object($query);
2004
2005 if (method_exists($this, 'fetch')) {
2006 return $this->fetch($result->rowid);
2007 } else {
2008 $this->error = 'Fetch method not implemented on '.get_class($this);
2009 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2010 array_push($this->errors, $this->error);
2011 return -1;
2012 }
2013 }
2014
2015 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2024 public function fetch_barcode()
2025 {
2026 // phpcs:enable
2027 return $this->fetchBarCode();
2028 }
2029
2037 public function fetchBarCode()
2038 {
2039 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2040
2041 $idtype = $this->barcode_type;
2042 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
2043 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2044 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2045 } elseif ($this->element == 'societe') {
2046 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2047 } else {
2048 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2049 }
2050 }
2051
2052 if ($idtype > 0) {
2053 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
2054 $sql = "SELECT rowid, code, libelle as label, coder";
2055 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2056 $sql .= " WHERE rowid = ".((int) $idtype);
2057
2058 $resql = $this->db->query($sql);
2059 if ($resql) {
2060 $obj = $this->db->fetch_object($resql);
2061
2062 $this->barcode_type = $obj->rowid;
2063 $this->barcode_type_code = $obj->code;
2064 $this->barcode_type_label = $obj->label;
2065 $this->barcode_type_coder = $obj->coder;
2066 return 1;
2067 } else {
2068 dol_print_error($this->db);
2069 return -1;
2070 }
2071 }
2072 }
2073 return 0;
2074 }
2075
2081 public function fetchProject()
2082 {
2083 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2084
2085 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2086 $this->fk_project = $this->fk_projet; // For backward compatibility
2087 }
2088 if (empty($this->fk_project)) {
2089 return 0;
2090 }
2091
2092 $project = new Project($this->db);
2093 $result = $project->fetch($this->fk_project);
2094
2095 $this->project = $project;
2096
2097 return $result;
2098 }
2099
2100 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2108 public function fetch_project()
2109 {
2110 // phpcs:enable
2111 return $this->fetchProject();
2112 }
2113
2114 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2122 public function fetch_projet()
2123 {
2124 // phpcs:enable
2125 return $this->fetchProject();
2126 }
2127
2128 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2134 public function fetch_product()
2135 {
2136 // phpcs:enable
2137 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2138
2139 // @phan-suppress-next-line PhanUndeclaredProperty
2140 if (empty($this->fk_product)) {
2141 return 0;
2142 }
2143
2144 $product = new Product($this->db);
2145 // @phan-suppress-next-line PhanUndeclaredProperty
2146 $result = $product->fetch($this->fk_product);
2147
2148 $this->product = $product;
2149 return $result;
2150 }
2151
2152 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2159 public function fetch_user($userid)
2160 {
2161 // phpcs:enable
2162 $user = new User($this->db);
2163 $result = $user->fetch($userid);
2164 $this->user = $user;
2165 return $result;
2166 }
2167
2168 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2175 public function fetch_origin()
2176 {
2177 // phpcs:enable
2178 $tmpclassname = $this->origin ? $this->origin : $this->origin_type;
2179
2180 // Manage classes with non standard name
2181 if ($tmpclassname == 'shipping') {
2182 $tmpclassname = 'Expedition';
2183 }
2184 if ($tmpclassname == 'delivery') {
2185 $tmpclassname = 'Livraison';
2186 }
2187 if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') {
2188 $tmpclassname = 'CommandeFournisseur';
2189 }
2190
2191 $classname = ucfirst($tmpclassname);
2192
2193 $this->origin_object = new $classname($this->db);
2194 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2195 $this->origin_object->fetch($this->origin_id);
2196 }
2197
2207 public function fetchObjectFrom($table, $field, $key, $element = null)
2208 {
2209 global $conf;
2210
2211 $result = false;
2212
2213 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2214 $sql .= " WHERE ".$field." = '".$this->db->escape($key)."'";
2215 if (!empty($element)) {
2216 $sql .= " AND entity IN (".getEntity($element).")";
2217 } else {
2218 $sql .= " AND entity = ".((int) $conf->entity);
2219 }
2220
2221 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2222 $resql = $this->db->query($sql);
2223 if ($resql) {
2224 $obj = $this->db->fetch_object($resql);
2225 // Test for avoid error -1
2226 if ($obj) {
2227 if (method_exists($this, 'fetch')) {
2228 $result = $this->fetch($obj->rowid);
2229 } else {
2230 $this->error = 'fetch() method not implemented on '.get_class($this);
2231 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2232 array_push($this->errors, $this->error);
2233 $result = -1;
2234 }
2235 }
2236 }
2237
2238 return $result;
2239 }
2240
2249 public function getValueFrom($table, $id, $field)
2250 {
2251 $result = false;
2252 if (!empty($id) && !empty($field) && !empty($table)) {
2253 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2254 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2255 return -1;
2256 }
2257 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2258 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2259 return -1;
2260 }
2261
2262 $sql = "SELECT ".$this->db->sanitize($field)." FROM ".$this->db->prefix().$this->db->sanitize($table);
2263 $sql .= " WHERE rowid = ".((int) $id);
2264
2265 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2266 $resql = $this->db->query($sql);
2267 if ($resql) {
2268 $row = $this->db->fetch_row($resql);
2269 $result = $row[0];
2270 }
2271 }
2272 return $result;
2273 }
2274
2291 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2292 {
2293 global $user;
2294
2295 if (empty($table)) {
2296 $table = $this->table_element;
2297 }
2298 if (empty($id)) {
2299 $id = $this->id;
2300 }
2301 if (empty($format)) {
2302 $format = 'text';
2303 }
2304 if (empty($id_field)) {
2305 $id_field = 'rowid';
2306 }
2307
2308 $propfield = $field;
2309
2310 // Special case
2311 if ($table == 'product') {
2312 if ($field == 'note_private') {
2313 $field = 'note';
2314 $propfield = 'note_private';
2315 } elseif ($field == 'status') {
2316 $field = 'tosell';
2317 $propfield = 'status';
2318 } elseif ($field == 'status_buy') {
2319 $field = 'tobuy';
2320 $propfield = 'status_buy';
2321 } elseif ($field == 'status_batch') {
2322 $field = 'tobatch';
2323 $propfield = 'status_batch';
2324 }
2325 }
2326
2327 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2328 $fk_user_field = 'fk_user_mod';
2329 }
2330 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2331 $fk_user_field = '';
2332 }
2333
2334 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $table)) {
2335 dol_syslog(get_class($this).'::getValueFrom Bad table name: '.$table, LOG_WARNING);
2336 return -1;
2337 }
2338 if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) {
2339 dol_syslog(get_class($this).'::getValueFrom Bad field name: '.$field, LOG_WARNING);
2340 return -1;
2341 }
2342
2343 $oldvalue = null;
2344 if ($trigkey) {
2345 $sql = "SELECT " . $this->db->sanitize($field);
2346 $sql .= " FROM " . MAIN_DB_PREFIX . $this->db->sanitize($table);
2347 $sql .= " WHERE " . $this->db->sanitize($id_field) . " = " . ((int) $id);
2348
2349 $resql = $this->db->query($sql);
2350 if ($resql) {
2351 if ($obj = $this->db->fetch_object($resql)) {
2352 if ($format == 'date') {
2353 $oldvalue = $this->db->jdate($obj->$field);
2354 } else {
2355 $oldvalue = $obj->$field;
2356 }
2357 }
2358 } else {
2359 $this->error = $this->db->lasterror();
2360 return -1;
2361 }
2362 }
2363
2364 $error = 0;
2365
2366 dol_syslog(__METHOD__, LOG_DEBUG);
2367
2368 $this->db->begin();
2369
2370 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2371
2372 if ($format == 'text') {
2373 $sql .= $this->db->sanitize($field)." = '".$this->db->escape($value)."'";
2374 } elseif ($format == 'int') {
2375 $sql .= $this->db->sanitize($field)." = ".((int) $value);
2376 } elseif ($format == 'date') {
2377 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2378 } elseif ($format == 'dategmt') {
2379 $sql .= $this->db->sanitize($field)." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2380 }
2381
2382 if ($fk_user_field) {
2383 if (!empty($fuser) && is_object($fuser)) {
2384 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2385 } elseif (empty($fuser) || $fuser != 'none') {
2386 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2387 }
2388 }
2389
2390 $sql .= " WHERE ".$this->db->sanitize($id_field)." = ".((int) $id);
2391
2392 $resql = $this->db->query($sql);
2393 if ($resql) {
2394 if ($trigkey) {
2395 // call trigger with updated object values
2396 if (method_exists($this, 'fetch')) {
2397 $result = $this->fetch($id);
2398 } else {
2399 $result = $this->fetchCommon($id);
2400 }
2401
2402 $this->oldcopy = clone $this;
2403 if (property_exists($this->oldcopy, $field)) {
2404 $this->oldcopy->$field = $oldvalue;
2405 }
2406 if ($propfield != $field && property_exists($this->oldcopy, $propfield)) {
2407 $this->oldcopy->$propfield = $oldvalue;
2408 }
2409
2410 if (empty($this->context['actionmsgmore'])) {
2411 $this->context['actionmsgmore'] = 'Trigger called by setValueFrom';
2412 }
2413
2414 if ($result >= 0) {
2415 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2416 }
2417 if ($result < 0) {
2418 $error++;
2419 }
2420 } else {
2421 if (property_exists($this, $field)) {
2422 $this->$field = $value;
2423 }
2424 if ($propfield != $field && property_exists($this, $propfield)) {
2425 $this->$propfield = $value;
2426 }
2427 }
2428
2429 if (!$error) {
2430 $this->db->commit();
2431 return 1;
2432 } else {
2433 if (property_exists($this, $field)) {
2434 $this->$field = $oldvalue;
2435 }
2436 if ($propfield != $field && property_exists($this, $propfield)) {
2437 $this->$propfield = $oldvalue;
2438 }
2439 $this->db->rollback();
2440 return -2;
2441 }
2442 } else {
2443 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2444 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2445 } else {
2446 $this->error = $this->db->lasterror();
2447 }
2448 $this->db->rollback();
2449 return -1;
2450 }
2451 }
2452
2453 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2464 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2465 {
2466 // phpcs:enable
2467 global $conf, $user;
2468
2469 if (!$this->table_element) {
2470 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2471 return -1;
2472 }
2473 if ($fieldid == 'none') {
2474 return 1;
2475 }
2476
2477 // For backward compatibility
2478 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2479 $fieldid = 'titre';
2480 }
2481
2482 // Security on socid
2483 $socid = 0;
2484 if ($user->socid > 0) {
2485 $socid = $user->socid;
2486 }
2487
2488 // this->ismultientitymanaged contains
2489 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2490 $aliastablesociete = 's';
2491 if ($this->element == 'societe') {
2492 $aliastablesociete = 'te'; // te as table_element
2493 }
2494 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2495 $sql = "SELECT MAX(te.".$fieldid.")";
2496 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2497 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2498 $tmparray = explode('@', $this->ismultientitymanaged);
2499 $sql .= ", ".$this->db->prefix().$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2500 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2501 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2502 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2503 $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
2504 }
2505 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2506 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2507 }
2508 if ($fieldid == 'rowid') {
2509 $sql .= " WHERE te.".$fieldid." < ".((int) $this->id);
2510 } elseif ($fieldid == 'label') {
2511 $sql .= " WHERE te.".$fieldid." < '".$this->db->escape((string) $this->label)."'";
2512 } else { // Should be 'ref' or any other string field
2513 $sql .= " WHERE te.".$fieldid." < '".$this->db->escape((string) $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2514 }
2515 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2516 $sql .= " AND sc.fk_user = ".((int) $user->id);
2517 }
2518 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2519 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2520 }
2521
2522 $filtermax = $filter;
2523
2524 // Manage filter
2525 $errormessage = '';
2526 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2527 if ($errormessage) {
2528 if (!preg_match('/^\s*AND/i', $filtermax)) {
2529 $sql .= " AND ";
2530 }
2531 $sql .= $filtermax;
2532 } else {
2533 $sql .= $tmpsql;
2534 }
2535
2536 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2537 $tmparray = explode('@', $this->ismultientitymanaged);
2538 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2539 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2540 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2541 }
2542 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2543 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2544 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2545 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2546 } else {
2547 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2548 }
2549 } else {
2550 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2551 }
2552 }
2553 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2554 $tmparray = explode('@', $this->ismultientitymanaged);
2555 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2556 }
2557 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2558 $sql .= ' AND te.fk_soc = '.((int) $socid);
2559 }
2560 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2561 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2562 }
2563 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2564 $sql .= ' AND te.rowid = '.((int) $socid);
2565 }
2566 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2567
2568 $result = $this->db->query($sql);
2569 if (!$result) {
2570 $this->error = $this->db->lasterror();
2571 return -1;
2572 }
2573 $row = $this->db->fetch_row($result);
2574 $this->ref_previous = $row[0];
2575
2576 $sql = "SELECT MIN(te.".$fieldid.")";
2577 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2578 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2579 $tmparray = explode('@', $this->ismultientitymanaged);
2580 $sql .= ", ".$this->db->prefix().$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2581 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2582 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2583 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2584 $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
2585 }
2586 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2587 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2588 }
2589 if ($fieldid == 'rowid') {
2590 $sql .= " WHERE te.".$fieldid." > ".((int) $this->id);
2591 } elseif ($fieldid == 'label') {
2592 $sql .= " WHERE te.".$fieldid." > '".$this->db->escape((string) $this->label)."'";
2593 } else { // Should be 'ref' or any other string field
2594 $sql .= " WHERE te.".$fieldid." > '".$this->db->escape((string) $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2595 }
2596 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2597 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2598 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2599 $userschilds = $user->getAllChildIds();
2600 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2601 }
2602 $sql .= ')';
2603 }
2604 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2605 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2606 }
2607
2608 $filtermin = $filter;
2609
2610 // Manage filter
2611 $errormessage = '';
2612 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2613 if ($errormessage) {
2614 if (!preg_match('/^\s*AND/i', $filtermin)) {
2615 $sql .= " AND ";
2616 }
2617 $sql .= $filtermin;
2618
2619 $filtermin = '';
2620 } else {
2621 $sql .= $tmpsql;
2622 }
2623
2624 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2625 $tmparray = explode('@', $this->ismultientitymanaged);
2626 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // 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 .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2629 }
2630 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2631 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2632 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2633 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2634 } else {
2635 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2636 }
2637 } else {
2638 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2639 }
2640 }
2641 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2642 $tmparray = explode('@', $this->ismultientitymanaged);
2643 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2644 }
2645 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2646 $sql .= ' AND te.fk_soc = '.((int) $socid);
2647 }
2648 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2649 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2650 }
2651 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2652 $sql .= ' AND te.rowid = '.((int) $socid);
2653 }
2654 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2655 // 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
2656
2657 $result = $this->db->query($sql);
2658 if (!$result) {
2659 $this->error = $this->db->lasterror();
2660 return -2;
2661 }
2662 $row = $this->db->fetch_row($result);
2663 $this->ref_next = $row[0];
2664
2665 return 1;
2666 }
2667
2668
2676 public function getListContactId($source = 'external')
2677 {
2678 $contactAlreadySelected = array();
2679 $tab = $this->liste_contact(-1, $source);
2680 $num = count($tab);
2681 $i = 0;
2682 while ($i < $num) {
2683 if ($source == 'thirdparty') {
2684 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2685 } else {
2686 $contactAlreadySelected[$i] = $tab[$i]['id'];
2687 }
2688 $i++;
2689 }
2690 return $contactAlreadySelected;
2691 }
2692
2693
2701 public function setProject($projectid, $notrigger = 0)
2702 {
2703 global $user;
2704 $error = 0;
2705
2706 if (!$this->table_element) {
2707 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2708 return -1;
2709 }
2710
2711 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2712 if ($this->table_element == 'actioncomm') { // Special case for actioncomm
2713 if ($projectid) {
2714 $sql .= " SET fk_project = ".((int) $projectid);
2715 } else {
2716 $sql .= " SET fk_project = NULL";
2717 }
2718 $sql .= ' WHERE id = '.((int) $this->id);
2719 // @phan-suppress-next-line PhanTypeMismatchProperty
2720 } elseif (!empty($this->fields['fk_project'])) { // Common case
2721 if ($projectid) {
2722 $sql .= " SET fk_project = ".((int) $projectid);
2723 } else {
2724 $sql .= " SET fk_project = NULL";
2725 }
2726 $sql .= ' WHERE rowid = '.((int) $this->id);
2727 } else { // Special case for old architecture objects
2728 if ($projectid) {
2729 $sql .= ' SET fk_projet = '.((int) $projectid);
2730 } else {
2731 $sql .= ' SET fk_projet = NULL';
2732 }
2733 $sql .= " WHERE rowid = ".((int) $this->id);
2734 }
2735
2736 $this->db->begin();
2737
2738 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2739 if ($this->db->query($sql)) {
2740 $this->fk_project = ((int) $projectid);
2741 } else {
2742 dol_print_error($this->db);
2743 $error++;
2744 }
2745
2746 // Triggers
2747 if (!$error && !$notrigger) {
2748 // Call triggers
2749 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2750 if ($result < 0) {
2751 $error++;
2752 } //Do also here what you must do to rollback action if trigger fail
2753 // End call triggers
2754 }
2755
2756 // Commit or rollback
2757 if ($error) {
2758 $this->db->rollback();
2759 return -1;
2760 } else {
2761 $this->db->commit();
2762 return 1;
2763 }
2764 }
2765
2772 public function setPaymentMethods($id)
2773 {
2774 global $user;
2775
2776 $error = 0;
2777 $notrigger = 0;
2778
2779 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2780
2781 if ($this->status >= 0 || $this->element == 'societe') {
2782 // TODO uniformize field name
2783 $fieldname = 'fk_mode_reglement';
2784 if ($this->element == 'societe') {
2785 $fieldname = 'mode_reglement';
2786 }
2787 if (get_class($this) == 'Fournisseur') {
2788 $fieldname = 'mode_reglement_supplier';
2789 }
2790 if (get_class($this) == 'Tva') {
2791 $fieldname = 'fk_typepayment';
2792 }
2793 if (get_class($this) == 'Salary') {
2794 $fieldname = 'fk_typepayment';
2795 }
2796
2797 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2798 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2799 $sql .= ' WHERE rowid = '.((int) $this->id);
2800
2801 if ($this->db->query($sql)) {
2802 $this->mode_reglement_id = $id;
2803 // for supplier
2804 if (get_class($this) == 'Fournisseur') {
2805 $this->mode_reglement_supplier_id = $id;
2806 }
2807 // Triggers
2808 if (!$error && !$notrigger) {
2809 // Call triggers
2810 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
2811 $result = $this->call_trigger($triggerName.'_MODIFY', $user);
2812 if ($result < 0) {
2813 $error++;
2814 }
2815 // End call triggers
2816 }
2817 return 1;
2818 } else {
2819 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2820 $this->error = $this->db->error();
2821 return -1;
2822 }
2823 } else {
2824 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2825 $this->error = 'Status of the object is incompatible '.$this->status;
2826 return -2;
2827 }
2828 }
2829
2836 public function setMulticurrencyCode($code)
2837 {
2838 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2839 if ($this->status >= 0 || $this->element == 'societe') {
2840 $fieldname = 'multicurrency_code';
2841
2842 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2843 $sql .= " SET ".$fieldname." = '".$this->db->escape($code)."'";
2844 $sql .= ' WHERE rowid='.((int) $this->id);
2845
2846 if ($this->db->query($sql)) {
2847 $this->multicurrency_code = $code;
2848
2849 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2850 if ($rate) {
2851 $this->setMulticurrencyRate($rate, 2);
2852 }
2853
2854 return 1;
2855 } else {
2856 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2857 $this->error = $this->db->error();
2858 return -1;
2859 }
2860 } else {
2861 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2862 $this->error = 'Status of the object is incompatible '.$this->status;
2863 return -2;
2864 }
2865 }
2866
2874 public function setMulticurrencyRate($rate, $mode = 1)
2875 {
2876 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2877 if ($this->status >= 0 || $this->element == 'societe') {
2878 $fieldname = 'multicurrency_tx';
2879
2880 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2881 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".((float) $rate);
2882 $sql .= ' WHERE rowid='.((int) $this->id);
2883
2884 if ($this->db->query($sql)) {
2885 $this->multicurrency_tx = $rate;
2886
2887 // Update line price
2888 if (!empty($this->lines)) {
2889 foreach ($this->lines as &$line) {
2890 // Amounts in company currency will be recalculated
2891 if ($mode == 1) {
2892 $line->subprice = 0;
2893 }
2894
2895 // Amounts in foreign currency will be recalculated
2896 if ($mode == 2) {
2897 $line->multicurrency_subprice = 0;
2898 }
2899
2900 switch ($this->element) {
2901 case 'propal':
2904 '@phan-var-force Propal $this';
2905 '@phan-var-force PropaleLigne $line';
2906 $this->updateline(
2907 $line->id,
2908 $line->subprice,
2909 $line->qty,
2910 $line->remise_percent,
2911 $line->tva_tx,
2912 $line->localtax1_tx,
2913 $line->localtax2_tx,
2914 ($line->description ? $line->description : $line->desc),
2915 'HT',
2916 $line->info_bits,
2917 $line->special_code,
2918 $line->fk_parent_line,
2919 $line->skip_update_total,
2920 $line->fk_fournprice,
2921 $line->pa_ht,
2922 $line->label,
2923 $line->product_type,
2924 $line->date_start,
2925 $line->date_end,
2926 $line->array_options,
2927 $line->fk_unit,
2928 $line->multicurrency_subprice
2929 );
2930 break;
2931 case 'commande':
2934 '@phan-var-force Commande $this';
2935 '@phan-var-force OrderLine $line';
2936 $this->updateline(
2937 $line->id,
2938 ($line->description ? $line->description : $line->desc),
2939 $line->subprice,
2940 $line->qty,
2941 $line->remise_percent,
2942 $line->tva_tx,
2943 $line->localtax1_tx,
2944 $line->localtax2_tx,
2945 'HT',
2946 $line->info_bits,
2947 $line->date_start,
2948 $line->date_end,
2949 $line->product_type,
2950 $line->fk_parent_line,
2951 $line->skip_update_total,
2952 $line->fk_fournprice,
2953 $line->pa_ht,
2954 $line->label,
2955 $line->special_code,
2956 $line->array_options,
2957 $line->fk_unit,
2958 $line->multicurrency_subprice
2959 );
2960 break;
2961 case 'facture':
2964 '@phan-var-force Facture $this';
2965 '@phan-var-force FactureLigne $line';
2966 $this->updateline(
2967 $line->id,
2968 ($line->description ? $line->description : $line->desc),
2969 $line->subprice,
2970 $line->qty,
2971 $line->remise_percent,
2972 $line->date_start,
2973 $line->date_end,
2974 $line->tva_tx,
2975 $line->localtax1_tx,
2976 $line->localtax2_tx,
2977 'HT',
2978 $line->info_bits,
2979 $line->product_type,
2980 $line->fk_parent_line,
2981 $line->skip_update_total,
2982 $line->fk_fournprice,
2983 $line->pa_ht,
2984 $line->label,
2985 $line->special_code,
2986 $line->array_options,
2987 $line->situation_percent,
2988 $line->fk_unit,
2989 $line->multicurrency_subprice
2990 );
2991 break;
2992 case 'facturerec':
2995 '@phan-var-force FactureRec $this';
2996 '@phan-var-force FactureLigneRec $line';
2997 $this->updateline(
2998 $line->id,
2999 ($line->description ? $line->description : $line->desc),
3000 $line->subprice,
3001 $line->qty,
3002 $line->tva_tx,
3003 $line->localtax1_tx,
3004 $line->localtax2_tx,
3005 $line->fk_product,
3006 $line->remise_percent,
3007 'HT',
3008 $line->info_bits,
3009 0,
3010 0,
3011 $line->product_type,
3012 $line->rang,
3013 $line->special_code,
3014 $line->label,
3015 $line->fk_unit,
3016 $line->multicurrency_subprice,
3017 0,
3018 $line->date_start,
3019 $line->date_end,
3020 $line->fk_fournprice,
3021 $line->pa_ht,
3022 $line->fk_parent_line
3023 );
3024 break;
3025 case 'supplier_proposal':
3028 '@phan-var-force SupplierProposal $this';
3029 '@phan-var-force SupplierProposalLine $line';
3030 $this->updateline(
3031 $line->id,
3032 $line->subprice,
3033 $line->qty,
3034 $line->remise_percent,
3035 $line->tva_tx,
3036 $line->localtax1_tx,
3037 $line->localtax2_tx,
3038 ($line->description ? $line->description : $line->desc),
3039 'HT',
3040 $line->info_bits,
3041 $line->special_code,
3042 $line->fk_parent_line,
3043 $line->skip_update_total,
3044 $line->fk_fournprice,
3045 $line->pa_ht,
3046 $line->label,
3047 $line->product_type,
3048 $line->array_options,
3049 $line->ref_fourn,
3050 (int) $line->fk_unit,
3051 $line->multicurrency_subprice
3052 );
3053 break;
3054 case 'order_supplier':
3057 '@phan-var-force CommandeFournisseur $this';
3058 '@phan-var-force CommandeFournisseurLigne $line';
3059 $this->updateline(
3060 $line->id,
3061 ($line->description ? $line->description : $line->desc),
3062 $line->subprice,
3063 $line->qty,
3064 $line->remise_percent,
3065 $line->tva_tx,
3066 $line->localtax1_tx,
3067 $line->localtax2_tx,
3068 'HT',
3069 $line->info_bits,
3070 $line->product_type,
3071 0,
3072 $line->date_start,
3073 $line->date_end,
3074 $line->array_options,
3075 $line->fk_unit,
3076 $line->multicurrency_subprice,
3077 $line->ref_supplier
3078 );
3079 break;
3080 case 'invoice_supplier':
3083 '@phan-var-force FactureFournisseur $this';
3084 '@phan-var-force SupplierInvoiceLIne $line';
3085 $this->updateline(
3086 $line->id,
3087 ($line->description ? $line->description : $line->desc),
3088 $line->subprice,
3089 $line->tva_tx,
3090 $line->localtax1_tx,
3091 $line->localtax2_tx,
3092 $line->qty,
3093 0,
3094 'HT',
3095 $line->info_bits,
3096 $line->product_type,
3097 $line->remise_percent,
3098 0,
3099 $line->date_start,
3100 $line->date_end,
3101 $line->array_options,
3102 $line->fk_unit,
3103 $line->multicurrency_subprice,
3104 $line->ref_supplier
3105 );
3106 break;
3107 default:
3108 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3109 break;
3110 }
3111 }
3112 }
3113
3114 return 1;
3115 } else {
3116 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3117 $this->error = $this->db->error();
3118 return -1;
3119 }
3120 } else {
3121 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3122 $this->error = 'Status of the object is incompatible '.$this->status;
3123 return -2;
3124 }
3125 }
3126
3134 public function setPaymentTerms($id, $deposit_percent = null)
3135 {
3136 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.var_export($deposit_percent, true).')');
3137 if ($this->status >= 0 || $this->element == 'societe') {
3138 // TODO uniformize field name
3139 $fieldname = 'fk_cond_reglement';
3140 if ($this->element == 'societe') {
3141 $fieldname = 'cond_reglement';
3142 }
3143 if (get_class($this) == 'Fournisseur') {
3144 $fieldname = 'cond_reglement_supplier';
3145 }
3146
3147 if (empty($deposit_percent) || $deposit_percent < 0) {
3148 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3149 }
3150
3151 if ($deposit_percent > 100) {
3152 $deposit_percent = 100;
3153 }
3154
3155 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3156 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3157 if (in_array($this->table_element, array('propal', 'commande', 'supplier_proposal', 'commande_fournisseur', 'societe'))) {
3158 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3159 }
3160 $sql .= ' WHERE rowid='.((int) $this->id);
3161
3162 if ($this->db->query($sql)) {
3163 $this->cond_reglement_id = $id;
3164 // for supplier
3165 if (get_class($this) == 'Fournisseur') {
3166 $this->cond_reglement_supplier_id = $id;
3167 }
3168 $this->deposit_percent = $deposit_percent;
3169 return 1;
3170 } else {
3171 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3172 $this->error = $this->db->error();
3173 return -1;
3174 }
3175 } else {
3176 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3177 $this->error = 'Status of the object is incompatible '.$this->status;
3178 return -2;
3179 }
3180 }
3181
3188 public function setTransportMode($id)
3189 {
3190 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3191 if ($this->status >= 0 || $this->element == 'societe') {
3192 $fieldname = 'fk_transport_mode';
3193 if ($this->element == 'societe') {
3194 $fieldname = 'transport_mode';
3195 }
3196 if (get_class($this) == 'Fournisseur') {
3197 $fieldname = 'transport_mode_supplier';
3198 }
3199
3200 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3201 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3202 $sql .= ' WHERE rowid='.((int) $this->id);
3203
3204 if ($this->db->query($sql)) {
3205 $this->transport_mode_id = $id;
3206 // for supplier
3207 if (get_class($this) == 'Fournisseur') {
3208 $this->transport_mode_supplier_id = $id;
3209 }
3210 return 1;
3211 } else {
3212 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3213 $this->error = $this->db->error();
3214 return -1;
3215 }
3216 } else {
3217 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3218 $this->error = 'Status of the object is incompatible '.$this->status;
3219 return -2;
3220 }
3221 }
3222
3230 {
3231 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
3232 if ($this->status >= 0 || $this->element == 'societe') {
3233 $fieldname = 'retained_warranty_fk_cond_reglement';
3234
3235 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3236 $sql .= " SET ".$fieldname." = ".((int) $id);
3237 $sql .= ' WHERE rowid='.((int) $this->id);
3238
3239 if ($this->db->query($sql)) {
3240 $this->retained_warranty_fk_cond_reglement = $id;
3241 return 1;
3242 } else {
3243 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
3244 $this->error = $this->db->error();
3245 return -1;
3246 }
3247 } else {
3248 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3249 $this->error = 'Status of the object is incompatible '.$this->status;
3250 return -2;
3251 }
3252 }
3253
3261 public function setDeliveryAddress($id)
3262 {
3263 $fieldname = 'fk_delivery_address';
3264 if ($this->element == 'delivery' || $this->element == 'shipping') {
3265 $fieldname = 'fk_address';
3266 }
3267
3268 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$fieldname." = ".((int) $id);
3269 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3270
3271 if ($this->db->query($sql)) {
3272 $this->fk_delivery_address = $id;
3273 return 1;
3274 } else {
3275 $this->error = $this->db->error();
3276 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3277 return -1;
3278 }
3279 }
3280
3281
3290 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3291 {
3292 global $user;
3293
3294 if (empty($userused)) {
3295 $userused = $user;
3296 }
3297
3298 $error = 0;
3299
3300 if (!$this->table_element) {
3301 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3302 return -1;
3303 }
3304
3305 $this->db->begin();
3306
3307 if ($shipping_method_id < 0) {
3308 $shipping_method_id = 'NULL';
3309 }
3310 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3311
3312 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3313 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3314 $sql .= " WHERE rowid=".((int) $this->id);
3315 $resql = $this->db->query($sql);
3316 if (!$resql) {
3317 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3318 $this->error = $this->db->lasterror();
3319 $error++;
3320 } else {
3321 if (!$notrigger) {
3322 // Call trigger
3323 $this->context = array('shippingmethodupdate' => 1);
3324 $triggerPrefix = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3325 $result = $this->call_trigger($triggerPrefix.'_MODIFY', $userused);
3326 if ($result < 0) {
3327 $error++;
3328 }
3329 // End call trigger
3330 }
3331 }
3332 if ($error) {
3333 $this->db->rollback();
3334 return -1;
3335 } else {
3336 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3337 $this->db->commit();
3338 return 1;
3339 }
3340 }
3341
3342
3349 public function setWarehouse($warehouse_id)
3350 {
3351 if (!$this->table_element) {
3352 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3353 return -1;
3354 }
3355 if ($warehouse_id < 0) {
3356 $warehouse_id = 'NULL';
3357 }
3358 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3359
3360 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3361 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3362 $sql .= " WHERE rowid=".((int) $this->id);
3363
3364 if ($this->db->query($sql)) {
3365 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3366 return 1;
3367 } else {
3368 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3369 $this->error = $this->db->error();
3370 return 0;
3371 }
3372 }
3373
3374
3382 public function setDocModel($user, $modelpdf)
3383 {
3384 if (!$this->table_element) {
3385 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3386 return -1;
3387 }
3388
3389 $newmodelpdf = dol_trunc($modelpdf, 255);
3390
3391 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3392 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3393 $sql .= " WHERE rowid = ".((int) $this->id);
3394
3395 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3396 $resql = $this->db->query($sql);
3397 if ($resql) {
3398 $this->model_pdf = $modelpdf;
3399 return 1;
3400 } else {
3401 dol_print_error($this->db);
3402 return 0;
3403 }
3404 }
3405
3406
3415 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3416 {
3417 global $user;
3418
3419 if (empty($userused)) {
3420 $userused = $user;
3421 }
3422
3423 $error = 0;
3424
3425 if (!$this->table_element) {
3426 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3427 return -1;
3428 }
3429 $this->db->begin();
3430
3431 if ($fk_account < 0) {
3432 $fk_account = 'NULL';
3433 }
3434 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3435
3436 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3437 $sql .= " SET fk_account = ".((int) $fk_account);
3438 $sql .= " WHERE rowid=".((int) $this->id);
3439
3440 $resql = $this->db->query($sql);
3441 if (!$resql) {
3442 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3443 $this->error = $this->db->lasterror();
3444 $error++;
3445 } else {
3446 if (!$notrigger) {
3447 // Call trigger
3448 $this->context['bankaccountupdate'] = 1;
3449
3450 $triggerName = (empty($this->TRIGGER_PREFIX) ? strtoupper(get_class($this)) : $this->TRIGGER_PREFIX);
3451 $result = $this->call_trigger($triggerName . '_MODIFY', $userused);
3452 if ($result < 0) {
3453 $error++;
3454 }
3455 // End call trigger
3456 }
3457 }
3458 if ($error) {
3459 $this->db->rollback();
3460 return -1;
3461 } else {
3462 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3463 $this->db->commit();
3464 return 1;
3465 }
3466 }
3467
3468
3469 // TODO: Move line related operations to CommonObjectLine?
3470
3471 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3481 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3482 {
3483 // phpcs:enable
3484 if (!$this->table_element_line) {
3485 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3486 return -1;
3487 }
3488 if (!$this->fk_element) {
3489 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3490 return -1;
3491 }
3492
3493 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3494 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3495 $fieldposition = 'position';
3496 }
3497
3498 // Count number of lines to reorder (according to choice $renum)
3499 $nl = 0;
3500 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->table_element_line;
3501 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3502 if (!$renum) {
3503 $sql .= " AND " . $fieldposition . " = 0";
3504 }
3505 if ($renum) {
3506 $sql .= " AND " . $fieldposition . " <> 0";
3507 }
3508
3509 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3510 $resql = $this->db->query($sql);
3511 if ($resql) {
3512 $row = $this->db->fetch_row($resql);
3513 $nl = $row[0];
3514 } else {
3515 dol_print_error($this->db);
3516 }
3517 if ($nl > 0) {
3518 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3519 $rows = array();
3520
3521 // We first search all lines that are parent lines (for multilevel details lines)
3522 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3523 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3524 if ($fk_parent_line) {
3525 $sql .= ' AND fk_parent_line IS NULL';
3526 }
3527 $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3528
3529 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3530 $resql = $this->db->query($sql);
3531 if ($resql) {
3532 $i = 0;
3533 $num = $this->db->num_rows($resql);
3534 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3535 while ($i < $num) {
3536 $row = $this->db->fetch_row($resql);
3537 $rows[] = $row[0]; // Add parent line into array rows
3538 if ($fk_parent_line) {
3539 $children = $this->getChildrenOfLine($row[0], $grandchild);
3540 }
3541 if (!empty($children)) {
3542 foreach ($children as $child) {
3543 array_push($rows, $child);
3544 }
3545 }
3546 $i++;
3547 }
3548
3549 // Now we set a new number for each lines (parent and children with children included into parent tree)
3550 if (!empty($rows)) {
3551 foreach ($rows as $key => $row) {
3552 $this->updateRangOfLine($row, ($key + 1));
3553 }
3554 }
3555 } else {
3556 dol_print_error($this->db);
3557 }
3558 }
3559 return 1;
3560 }
3561
3569 public function getChildrenOfLine($id, $includealltree = 0)
3570 {
3571 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3572 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3573 $fieldposition = 'position';
3574 }
3575
3576 $rows = array();
3577
3578 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3579 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3580 $sql .= ' AND fk_parent_line = '.((int) $id);
3581 $sql .= " ORDER BY " . $fieldposition . " ASC";
3582
3583 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3584
3585 $resql = $this->db->query($sql);
3586 if (!$resql || $this->db->num_rows($resql) <= 0) {
3587 return array();
3588 }
3589
3590 while ($row = $this->db->fetch_row($resql)) {
3591 $rows[] = $row[0];
3592 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3593 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3594 }
3595 }
3596 return $rows;
3597 }
3598
3599 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3607 public function line_up($rowid, $fk_parent_line = true)
3608 {
3609 // phpcs:enable
3610 $this->line_order(false, 'ASC', $fk_parent_line);
3611
3612 // Get rang of line
3613 $rang = $this->getRangOfLine($rowid);
3614
3615 // Update position of line
3616 $this->updateLineUp($rowid, $rang);
3617 }
3618
3619 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3627 public function line_down($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 // Get max value for rang
3636 $max = $this->line_max();
3637
3638 // Update position of line
3639 $this->updateLineDown($rowid, $rang, $max);
3640 }
3641
3649 public function updateRangOfLine($rowid, $rang)
3650 {
3651 global $hookmanager;
3652 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3653 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3654 $fieldposition = 'position';
3655 }
3656
3657 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3658 $sql .= ' WHERE rowid = '.((int) $rowid);
3659 if ($this->id > 0 && !empty($this->fk_element)) {
3660 // The line must belong to the object we reorder lines of
3661 $sql .= " AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3662 }
3663
3664 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3665 if (!$this->db->query($sql)) {
3666 dol_print_error($this->db);
3667 return -1;
3668 }
3669
3670 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3671 $action = '';
3672 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3673 return 1;
3674 }
3675
3676 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3683 public function line_ajaxorder($rows)
3684 {
3685 // phpcs:enable
3686 $num = count($rows);
3687 for ($i = 0; $i < $num; $i++) {
3688 $this->updateRangOfLine($rows[$i], ($i + 1));
3689 }
3690 }
3691
3699 public function updateLineUp($rowid, $rang)
3700 {
3701 if ($rang <= 1) {
3702 return -1;
3703 }
3704
3705 $fieldposition = 'rang';
3706 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3707 $fieldposition = 'position';
3708 }
3709
3710 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3711 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3712 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3713 if (!$this->db->query($sql)) {
3714 $this->error = $this->db->lasterror();
3715 return -1;
3716 }
3717
3718 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3719 $sql .= ' WHERE rowid = '.((int) $rowid);
3720 if (!$this->db->query($sql)) {
3721 $this->error = $this->db->lasterror();
3722 return -1;
3723 }
3724
3725 return 1;
3726 }
3727
3736 public function updateLineDown($rowid, $rang, $max)
3737 {
3738 if ($rang >= $max) {
3739 return -1;
3740 }
3741
3742 $fieldposition = 'rang';
3743 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3744 $fieldposition = 'position';
3745 }
3746
3747 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3748 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3749 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3750 if (!$this->db->query($sql)) {
3751 $this->error = $this->db->lasterror();
3752 return -1;
3753 }
3754
3755 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3756 $sql .= ' WHERE rowid = '.((int) $rowid);
3757 if (!$this->db->query($sql)) {
3758 $this->error = $this->db->lasterror();
3759 return -1;
3760 }
3761
3762 return 1;
3763 }
3764
3771 public function getRangOfLine($rowid)
3772 {
3773 $fieldposition = 'rang';
3774 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3775 $fieldposition = 'position';
3776 }
3777
3778 $sql = "SELECT " . $fieldposition . " FROM ".$this->db->prefix().$this->table_element_line;
3779 $sql .= " WHERE rowid = ".((int) $rowid);
3780
3781 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3782 $resql = $this->db->query($sql);
3783 if (!$resql) {
3784 return 0;
3785 }
3786
3787 $row = $this->db->fetch_row($resql);
3788 return $row[0];
3789 }
3790
3797 public function getIdOfLine($rang)
3798 {
3799 $fieldposition = 'rang';
3800 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3801 $fieldposition = 'position';
3802 }
3803
3804 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3805 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3806 $sql .= " AND " . $fieldposition . " = ".((int) $rang);
3807 $resql = $this->db->query($sql);
3808 if (!$resql) {
3809 return 0;
3810 }
3811
3812 $row = $this->db->fetch_row($resql);
3813 return $row[0];
3814 }
3815
3825 public function roundQtyToPackaging($qty, $packaging)
3826 {
3827 if (empty($packaging) || !is_numeric($packaging) || (float) $packaging <= 0) {
3828 return $qty;
3829 }
3830 $sign = ((float) $qty < 0 ? -1 : 1);
3831 $absqty = abs((float) $qty);
3832 if ($absqty < (float) $packaging) {
3833 return $sign * (float) $packaging;
3834 }
3835 if ((float) price2num(fmod($absqty, (float) $packaging), 'MS')) {
3836 $coeff = intval($absqty / (float) $packaging) + 1;
3837 return $sign * (float) price2num((float) $packaging * $coeff, 'MS');
3838 }
3839 return $qty;
3840 }
3841
3842 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3849 public function line_max($fk_parent_line = 0)
3850 {
3851 // phpcs:enable
3852 $positionfield = 'rang';
3853 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3854 $positionfield = 'position';
3855 }
3856
3857 $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3858 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3859
3860 // Search the last rang with fk_parent_line
3861 if ($fk_parent_line) {
3862 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3863 }
3864
3865 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3866 $resql = $this->db->query($sql);
3867 if ($resql) {
3868 $row = $this->db->fetch_row($resql);
3869
3870 if ($fk_parent_line) {
3871 if (!empty($row[0])) {
3872 return $row[0];
3873 } else {
3874 return $this->getRangOfLine($fk_parent_line);
3875 }
3876 } else {
3877 return $row[0];
3878 }
3879 }
3880
3881 return 0;
3882 }
3883
3884 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3891 public function update_ref_ext($ref_ext)
3892 {
3893 // phpcs:enable
3894 if (!$this->table_element) {
3895 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3896 return -1;
3897 }
3898
3899 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3900 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3901 // @phan-suppress-next-line PhanUndeclaredProperty
3902 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3903
3904 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3905 if ($this->db->query($sql)) {
3906 $this->ref_ext = $ref_ext;
3907 return 1;
3908 } else {
3909 $this->error = $this->db->error();
3910 return -1;
3911 }
3912 }
3913
3914 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3923 public function update_note($note, $suffix = '', $notrigger = 0)
3924 {
3925 // phpcs:enable
3926 global $user;
3927
3928 if (!$this->table_element) {
3929 $this->error = 'update_note was called on object with property table_element not defined';
3930 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3931 return -1;
3932 }
3933 if (!in_array($suffix, array('', '_public', '_private'))) {
3934 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3935 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3936 return -2;
3937 }
3938
3939 $newsuffix = $suffix;
3940
3941 // Special case
3942 if ($this->table_element == 'product' && $newsuffix == '_private') {
3943 $newsuffix = '';
3944 }
3945 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3946 $fieldusermod = "fk_user_mod";
3947 } elseif ($this->table_element == 'ecm_files') {
3948 $fieldusermod = "fk_user_m";
3949 } else {
3950 $fieldusermod = "fk_user_modif";
3951 }
3952 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3953 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3954 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3955 $sql .= " WHERE rowid = ".((int) $this->id);
3956
3957 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3958 if ($this->db->query($sql)) {
3959 if ($suffix == '_public') {
3960 $this->note_public = $note;
3961 } elseif ($suffix == '_private') {
3962 $this->note_private = $note;
3963 } else {
3964 $this->note = $note; // deprecated
3965 $this->note_private = $note;
3966 }
3967 if (empty($notrigger)) {
3968 // TODO Use the $this->TRIGGER_PREFIX when implemented
3969 switch ($this->element) {
3970 case 'societe':
3971 $triggerName = 'COMPANY_MODIFY';
3972 break;
3973 case 'facture':
3974 $triggerName = 'BILL_MODIFY';
3975 break;
3976 case 'invoice_supplier':
3977 $triggerName = 'BILL_SUPPLIER_MODIFY';
3978 break;
3979 case 'facturerec':
3980 $triggerName = 'BILLREC_MODIFIY';
3981 break;
3982 case 'expensereport':
3983 $triggerName = 'EXPENSE_REPORT_MODIFY';
3984 break;
3985 default:
3986 $triggerName = (!empty($this->TRIGGER_PREFIX) ? $this->TRIGGER_PREFIX : strtoupper($this->element)) . '_MODIFY';
3987 }
3988 $ret = $this->call_trigger($triggerName, $user);
3989 if ($ret < 0) {
3990 return -1;
3991 }
3992 }
3993 return 1;
3994 } else {
3995 $this->error = $this->db->lasterror();
3996 return -1;
3997 }
3998 }
3999
4000 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4009 public function update_note_public($note)
4010 {
4011 // phpcs:enable
4012 return $this->update_note($note, '_public');
4013 }
4014
4015 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4026 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4027 {
4028 // phpcs:enable
4029 global $conf, $hookmanager, $action;
4030
4031 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4032 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4033 if ($reshook > 0) {
4034 return 1; // replacement code
4035 } elseif ($reshook < 0) {
4036 return -1; // failure
4037 } // reshook = 0 => execute normal code
4038
4039 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4040 $isElementForSupplier = false;
4041 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4042 $MODULE = "";
4043 if ($this->element == 'propal') {
4044 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4045 } elseif ($this->element == 'commande' || $this->element == 'order') {
4046 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4047 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4048 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4049 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4050 $isElementForSupplier = true;
4051 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4052 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4053 $isElementForSupplier = true;
4054 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4055 } elseif ($this->element == 'supplier_proposal') {
4056 $isElementForSupplier = true;
4057 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4058 }
4059 if ($isElementForSupplier) {
4060 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4061 }
4062
4063 if (!empty($MODULE)) {
4064 if (getDolGlobalString($MODULE)) {
4065 $modsactivated = explode(',', getDolGlobalString($MODULE));
4066 foreach ($modsactivated as $mod) {
4067 if (isModEnabled($mod)) {
4068 return 1; // update was disabled by specific setup
4069 }
4070 }
4071 }
4072 }
4073
4074 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4075
4076 $forcedroundingmode = $roundingadjust;
4077 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4078 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4079 } elseif ($forcedroundingmode == 'auto') {
4080 $forcedroundingmode = '0';
4081 }
4082
4083 $error = 0;
4084
4085 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4086
4087 // Define constants to find lines to sum (field name int the table_element_line not into table_element)
4088 $fieldtva = 'total_tva';
4089 $fieldlocaltax1 = 'total_localtax1';
4090 $fieldlocaltax2 = 'total_localtax2';
4091 $fieldup = 'subprice';
4092 $base_price_type = 'HT';
4093 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4094 $fieldtva = 'tva';
4095 $fieldup = 'pu_ht';
4096 }
4097 if ($this->element == 'invoice_supplier_rec') {
4098 $fieldup = 'pu_ht';
4099 }
4100 if ($this->element == 'expensereport') {
4101 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4102 // 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
4103 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4104 $forcedroundingmode = '0';
4105 $fieldup = 'value_unit';
4106 $base_price_type = 'TTC';
4107 }
4108
4109 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent,";
4110 $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,";
4111 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4112 $sql .= ' info_bits, product_type,';
4113 if ($this->element == 'expensereport') {
4114 $sql .= ' comments as description';
4115 } else {
4116 $sql .= ' description';
4117 }
4118 if ($this->table_element_line == 'facturedet') {
4119 $sql .= ', situation_percent';
4120 }
4121 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4122 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
4123 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4124 if ($exclspec) {
4125 $product_field = 'product_type';
4126 if ($this->table_element_line == 'contratdet') {
4127 $product_field = ''; // contratdet table has no product_type field
4128 }
4129 if ($product_field) {
4130 $sql .= " AND ".$product_field." <> 9";
4131 }
4132 }
4133 $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
4134
4135 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4136
4137 $resql = $this->db->query($sql);
4138 if ($resql) {
4139 $this->total_ht = 0;
4140 $this->total_tva = 0;
4141 $this->total_localtax1 = 0;
4142 $this->total_localtax2 = 0;
4143 $this->total_ttc = 0;
4144 $total_ht_by_vats = array();
4145 $total_tva_by_vats = array();
4146 $total_ttc_by_vats = array();
4147 $this->multicurrency_total_ht = 0;
4148 $this->multicurrency_total_tva = 0;
4149 $this->multicurrency_total_ttc = 0;
4150
4151 $this->db->begin();
4152
4153 $num = $this->db->num_rows($resql);
4154 $i = 0;
4155 while ($i < $num) {
4156 $obj = $this->db->fetch_object($resql);
4157
4158 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4159 $parameters = array('fk_element' => $obj->rowid);
4160 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4161
4162 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'
4163 // This part of code is to fix data. We should not call it too often.
4164 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4165 $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);
4166
4167 $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.
4168 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4169 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4170 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4171
4172 if ($diff_on_current_total) {
4173 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4174 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4175 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4176 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4177 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4178 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);
4179 $resqlfix = $this->db->query($sqlfix);
4180 if (!$resqlfix) {
4181 dol_print_error($this->db, 'Failed to update line');
4182 }
4183 $obj->total_tva = $tmpcal[1];
4184 $obj->total_ttc = $tmpcal[2];
4185 $obj->multicurrency_total_tva = $tmpcal[17];
4186 $obj->multicurrency_total_ttc = $tmpcal[18];
4187 } elseif ($diff_when_using_price_ht) {
4188 // 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
4189 // 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),
4190 // so we continue only if price HT are same.
4191 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4192 // 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
4193 // will give different results than the one stored in database (the good one are the one in database and we must not fix anything).
4194 if ($obj->description != '(DEPOSIT)' && !getDolGlobalInt('MAIN_DISABLE_AUTOFIX_CORRUPTED_LINES_WHEN_TOTAL_DOES_NOT_MATCH_RECALCULATION_FROM_UP')) {
4195 // 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,
4196 // 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.
4197 // 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.
4198 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4199 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4200 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4201 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4202
4203 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);
4204
4205 $resqlfix = $this->db->query($sqlfix);
4206 if (!$resqlfix) {
4207 dol_print_error($this->db, 'Failed to update line');
4208 }
4209 $obj->total_tva = $tmpcal[1];
4210 $obj->total_ttc = $tmpcal[2];
4211 $obj->multicurrency_total_tva = $tmpcal[17];
4212 $obj->multicurrency_total_ttc = $tmpcal[18];
4213 }
4214 }
4215 }
4216 }
4217
4218 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4219 $this->total_tva += $obj->total_tva;
4220 $this->total_localtax1 += $obj->total_localtax1;
4221 $this->total_localtax2 += $obj->total_localtax2;
4222 $this->total_ttc += $obj->total_ttc;
4223 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4224 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4225 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4226
4227 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4228 $total_ht_by_vats[$obj->vatrate] = 0;
4229 }
4230 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4231 $total_tva_by_vats[$obj->vatrate] = 0;
4232 }
4233 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4234 $total_ttc_by_vats[$obj->vatrate] = 0;
4235 }
4236 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4237 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4238 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4239
4240 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4241 if ($base_price_type == 'TTC') {
4242 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4243 } else {
4244 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4245 }
4246 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4247 //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";
4248 if ($diff) {
4249 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4250 if (abs((float) $diff) > $maxdiff) {
4251 // If error is more than 10 times the accuracy of rounding. This should not happen.
4252 $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.';
4253 dol_syslog($errmsg, LOG_WARNING);
4254 $this->error = $errmsg;
4255 $error++;
4256 break;
4257 }
4258
4259 if ($base_price_type == 'TTC') {
4260 $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);
4261 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);
4262 } else {
4263 $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);
4264 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);
4265 }
4266
4267 $resqlfix = $this->db->query($sqlfix);
4268
4269 if (!$resqlfix) {
4270 dol_print_error($this->db, 'Failed to update line');
4271 }
4272
4273 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4274 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4275 if ($base_price_type == 'TTC') {
4276 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4277 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4278 } else {
4279 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4280 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4281 }
4282 }
4283 }
4284
4285 $i++;
4286 }
4287
4288 // Add revenue stamp to total
4289 // @phan-suppress-next-line PhanUndeclaredProperty
4290 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4291 // @phan-suppress-next-line PhanUndeclaredProperty
4292 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4293
4294 // Situations totals
4295 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
4296 '@phan-var-force Facture $this';
4297 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
4298 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4299 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4300 $prev_sits = $this->get_prev_sits();
4301
4302 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4303 $this->total_ht -= $sit->total_ht;
4304 $this->total_tva -= $sit->total_tva;
4305 $this->total_localtax1 -= $sit->total_localtax1;
4306 $this->total_localtax2 -= $sit->total_localtax2;
4307 $this->total_ttc -= $sit->total_ttc;
4308 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4309 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4310 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4311 }
4312 }
4313 }
4314 }
4315
4316 // Clean total
4317 $this->total_ht = (float) price2num($this->total_ht);
4318 $this->total_tva = (float) price2num($this->total_tva);
4319 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4320 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4321 $this->total_ttc = (float) price2num($this->total_ttc);
4322
4323 $this->db->free($resql);
4324
4325 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4326 $fieldht = 'total_ht';
4327 $fieldtva = 'tva';
4328 $fieldlocaltax1 = 'localtax1';
4329 $fieldlocaltax2 = 'localtax2';
4330 $fieldttc = 'total_ttc';
4331 // Specific code for backward compatibility with old field names
4332 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4333 $fieldtva = 'total_tva';
4334 }
4335
4336 if (!$error && empty($nodatabaseupdate)) {
4337 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4338 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4339 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4340 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4341 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4342 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4343 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4344 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4345 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4346 $sql .= " WHERE rowid = ".((int) $this->id);
4347
4348 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4349 $resql = $this->db->query($sql);
4350
4351 if (!$resql) {
4352 $error++;
4353 $this->error = $this->db->lasterror();
4354 $this->errors[] = $this->db->lasterror();
4355 }
4356 }
4357
4358 if (!$error) {
4359 $this->db->commit();
4360 return 1;
4361 } else {
4362 $this->db->rollback();
4363 return -1;
4364 }
4365 } else {
4366 dol_print_error($this->db, 'Bad request in update_price');
4367 return -1;
4368 }
4369 }
4370
4371 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4382 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4383 {
4384 // phpcs:enable
4385 global $user, $hookmanager, $action;
4386
4387 if (empty($this->origin_type) && !empty($this->origin)) {
4388 $this->origin_type = $this->origin;
4389 }
4390 $origin = (!empty($origin) ? $origin : $this->origin_type);
4391 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4392 $f_user = isset($f_user) ? $f_user : $user;
4393
4394 // Special case
4395 if ($origin == 'order') {
4396 $origin = 'commande';
4397 }
4398 if ($origin == 'invoice') {
4399 $origin = 'facture';
4400 }
4401 if ($origin == 'invoice_template') {
4402 $origin = 'facturerec';
4403 }
4404 if ($origin == 'supplierorder') {
4405 $origin = 'order_supplier';
4406 }
4407
4408 // Add module part to target type
4409 $targettype = $this->getElementType();
4410
4411 $parameters = array('targettype' => $targettype);
4412 // Hook for explicitly set the targettype if it must be different than $this->element
4413 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4414 if ($reshook > 0) {
4415 if (!empty($hookmanager->resArray['targettype'])) {
4416 $targettype = $hookmanager->resArray['targettype'];
4417 }
4418 }
4419
4420 $this->db->begin();
4421 $error = 0;
4422
4423 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4424 $sql .= "fk_source";
4425 $sql .= ", sourcetype";
4426 $sql .= ", fk_target";
4427 $sql .= ", targettype";
4428 $sql .= ") VALUES (";
4429 $sql .= ((int) $origin_id);
4430 $sql .= ", '" . $this->db->escape($origin) . "'";
4431 $sql .= ", " . ((int) $this->id);
4432 $sql .= ", '" . $this->db->escape($targettype) . "'";
4433 $sql .= ")";
4434
4435 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4436 if ($this->db->query($sql)) {
4437 if (!$notrigger) {
4438 // Call trigger
4439 $this->context['link_origin'] = $origin;
4440 $this->context['link_origin_id'] = $origin_id;
4441
4442 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user); // Note: We should have used here a hook. Not a business event
4443 if ($result < 0) {
4444 $error++;
4445 }
4446 // End call triggers
4447 }
4448 } else {
4449 $this->error = $this->db->lasterror();
4450 $error++;
4451 }
4452
4453 if (!$error) {
4454 $this->db->commit();
4455 return 1;
4456 } else {
4457 $this->db->rollback();
4458 return 0;
4459 }
4460 }
4461
4467 public function getElementType()
4468 {
4469 // 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.
4470 // 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).
4471 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4472 // Add module part to target type if object has $module property and isn't in core modules.
4473 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4474 }
4475
4476
4499 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4500 {
4501 global $hookmanager, $action;
4502
4503 // Important for pdf generation time reduction
4504 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4505 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4506 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4507 return 1;
4508 }
4509
4510 $this->linkedObjectsIds = array();
4511 $this->linkedObjects = array();
4512
4513 // Hook for allowing modules to completely alter the behavior of the method
4514 $parameters = array(
4515 'sourceid' => $sourceid,
4516 'sourcetype' => $sourcetype,
4517 'targetid' => $targetid,
4518 'targettype' => $targettype,
4519 'clause' => $clause,
4520 'alsosametype' => $alsosametype,
4521 'orderby' => $orderby,
4522 'loadalsoobjects' => $loadalsoobjects
4523 );
4524 $reshook = $hookmanager->executeHooks('fetchObjectLinked', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4525 if ($reshook > 0) {
4526 return $reshook;
4527 }
4528
4529 $justsource = false;
4530 $justtarget = false;
4531 $withtargettype = false;
4532 $withsourcetype = false;
4533
4534 // Hook for explicitly set the targettype if it must be differtent than $this->element
4535 if (is_object($hookmanager)) {
4536 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4537 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4538 if ($reshook > 0) {
4539 if (!empty($hookmanager->resArray['sourcetype'])) {
4540 $sourcetype = $hookmanager->resArray['sourcetype'];
4541 }
4542 if (!empty($hookmanager->resArray['sourceid'])) {
4543 $sourceid = $hookmanager->resArray['sourceid'];
4544 }
4545 if (!empty($hookmanager->resArray['targettype'])) {
4546 $targettype = $hookmanager->resArray['targettype'];
4547 }
4548 if (!empty($hookmanager->resArray['targetid'])) {
4549 $targetid = $hookmanager->resArray['targetid'];
4550 }
4551 }
4552 }
4553
4554 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4555 $justsource = true; // the source (id and type) is a search criteria
4556 if (!empty($targettype)) {
4557 $withtargettype = true;
4558 }
4559 }
4560 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4561 $justtarget = true; // the target (id and type) is a search criteria
4562 if (!empty($sourcetype)) {
4563 $withsourcetype = true;
4564 }
4565 }
4566
4567 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4568 $targetid = (!empty($targetid) ? $targetid : $this->id);
4569 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4570 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4571
4572 /*if (empty($sourceid) && empty($targetid))
4573 {
4574 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4575 return -1;
4576 }*/
4577
4578 // Links between objects are stored in table element_element
4579 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4580 $sql .= " FROM ".$this->db->prefix()."element_element";
4581 $sql .= " WHERE ";
4582 if ($justsource || $justtarget) {
4583 if ($justsource) {
4584 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4585 if ($withtargettype) {
4586 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4587 }
4588 } elseif ($justtarget) {
4589 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4590 if ($withsourcetype) {
4591 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4592 }
4593 }
4594 } else {
4595 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4596 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4597 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4598 $this->linkedObjectsFullLoaded[$this->id] = true;
4599 }
4600 }
4601 $sql .= " ORDER BY ".$orderby;
4602
4603 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4604 $resql = $this->db->query($sql);
4605 if ($resql) {
4606 $num = $this->db->num_rows($resql);
4607 $i = 0;
4608 while ($i < $num) {
4609 $obj = $this->db->fetch_object($resql);
4610 if ($justsource || $justtarget) {
4611 if ($justsource) {
4612 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4613 } elseif ($justtarget) {
4614 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4615 }
4616 } else {
4617 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4618 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4619 }
4620 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4621 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4622 }
4623 }
4624 $i++;
4625 }
4626
4627 if (!empty($this->linkedObjectsIds)) {
4628 $tmparray = $this->linkedObjectsIds;
4629 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4630 $element_properties = getElementProperties($objecttype);
4631 $element = $element_properties['element'];
4632 $classPath = $element_properties['classpath'];
4633 $classFile = $element_properties['classfile'];
4634 $className = $element_properties['classname'];
4635 $module = $element_properties['module'];
4636
4637 // Here $module, $classFile and $className are set, we can use them.
4638 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4639 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4640 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4641 if (class_exists($className)) {
4642 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4643 $object = new $className($this->db);
4644 '@phan-var-force CommonObject $object';
4645 $ret = $object->fetch($objectid);
4646 if ($ret >= 0) {
4647 $this->linkedObjects[$objecttype][$i] = $object;
4648 }
4649 }
4650 }
4651 }
4652 } else {
4653 unset($this->linkedObjectsIds[$objecttype]);
4654 }
4655 }
4656 }
4657 return 1;
4658 } else {
4659 dol_print_error($this->db);
4660 return -1;
4661 }
4662 }
4663
4670 public function clearObjectLinkedCache()
4671 {
4672 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4673 unset($this->linkedObjectsFullLoaded[$this->id]);
4674 }
4675
4676 return 1;
4677 }
4678
4691 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4692 {
4693 global $user;
4694 $updatesource = false;
4695 $updatetarget = false;
4696 $f_user = isset($f_user) ? $f_user : $user;
4697
4698 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4699 $updatesource = true;
4700 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4701 $updatetarget = true;
4702 }
4703
4704 $this->db->begin();
4705 $error = 0;
4706
4707 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4708 if ($updatesource) {
4709 $sql .= "fk_source = " . ((int) $sourceid);
4710 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4711 $sql .= " WHERE fk_target = " . ((int) $this->id);
4712 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4713 } elseif ($updatetarget) {
4714 $sql .= "fk_target = " . ((int) $targetid);
4715 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4716 $sql .= " WHERE fk_source = " . ((int) $this->id);
4717 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4718 }
4719
4720 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4721 if ($this->db->query($sql)) {
4722 if (!$notrigger) {
4723 // Call trigger
4724 $this->context['link_source_id'] = $sourceid;
4725 $this->context['link_source_type'] = $sourcetype;
4726 $this->context['link_target_id'] = $targetid;
4727 $this->context['link_target_type'] = $targettype;
4728
4729 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user); // Note: We should have used here a hook. Not a business event
4730 if ($result < 0) {
4731 $error++;
4732 }
4733 // End call triggers
4734 }
4735 } else {
4736 $this->error = $this->db->lasterror();
4737 $error++;
4738 }
4739
4740 if (!$error) {
4741 $this->db->commit();
4742 return 1;
4743 } else {
4744 $this->db->rollback();
4745 return -1;
4746 }
4747 }
4748
4762 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4763 {
4764 global $user;
4765 $deletesource = false;
4766 $deletetarget = false;
4767 $f_user = isset($f_user) ? $f_user : $user;
4768
4769 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4770 $deletesource = true;
4771 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4772 $deletetarget = true;
4773 }
4774
4775 $element = $this->getElementType();
4776 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4777 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4778 $targetid = (!empty($targetid) ? $targetid : $this->id);
4779 $targettype = (!empty($targettype) ? $targettype : $element);
4780 $this->db->begin();
4781 $error = 0;
4782
4783 if (!$notrigger) {
4784 // Call trigger
4785 $this->context['link_id'] = $rowid;
4786 $this->context['link_source_id'] = $sourceid;
4787 $this->context['link_source_type'] = $sourcetype;
4788 $this->context['link_target_id'] = $targetid;
4789 $this->context['link_target_type'] = $targettype;
4790
4791 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user); // Note: We should have used here a hook. Not a business event
4792 if ($result < 0) {
4793 $error++;
4794 }
4795 // End call triggers
4796 }
4797
4798 if (!$error) {
4799 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4800 $sql .= " WHERE";
4801 if ($rowid > 0) {
4802 $sql .= " rowid = " . ((int) $rowid);
4803 } else {
4804 if ($deletesource) {
4805 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4806 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4807 } elseif ($deletetarget) {
4808 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4809 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4810 } else {
4811 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4812 $sql .= " OR";
4813 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4814 }
4815 }
4816
4817 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4818 if (!$this->db->query($sql)) {
4819 $this->error = $this->db->lasterror();
4820 $this->errors[] = $this->error;
4821 $error++;
4822 }
4823 }
4824
4825 if (!$error) {
4826 $this->db->commit();
4827 return 1;
4828 } else {
4829 $this->db->rollback();
4830 return 0;
4831 }
4832 }
4833
4843 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4844 {
4845 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4846 return -1;
4847 }
4848 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4849 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4850 }
4851
4852 global $db;
4853
4854 $sql = "SELECT ".$field_select." FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4855 $resql = $db->query($sql);
4856
4857 $TRes = array();
4858 if (!empty($resql)) {
4859 while ($res = $db->fetch_object($resql)) {
4860 $TRes[] = $res->{$field_select};
4861 }
4862 }
4863
4864 return $TRes;
4865 }
4866
4875 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4876 {
4877 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4878 return -1;
4879 }
4880
4881 global $db;
4882
4883 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4884 $resql = $db->query($sql);
4885 $n = 0;
4886 if ($resql) {
4887 $res = $db->fetch_object($resql);
4888 if ($res) {
4889 $n = $res->nb;
4890 }
4891 }
4892
4893 return $n;
4894 }
4895
4904 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4905 {
4906 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4907 return -1;
4908 }
4909
4910 global $db;
4911
4912 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4913 $resql = $db->query($sql);
4914
4915 if (empty($resql)) {
4916 return 0;
4917 }
4918
4919 return 1;
4920 }
4921
4933 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = '')
4934 {
4935 global $user;
4936
4937 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4938
4939 $elementId = (!empty($elementId) ? $elementId : $this->id);
4940 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4941
4942 $this->db->begin();
4943
4944 if (empty($fieldstatus)) {
4945 if ($elementTable == 'facture_rec') {
4946 $fieldstatus = "suspended";
4947 }
4948 if ($elementTable == 'mailing') {
4949 $fieldstatus = "statut";
4950 }
4951 if ($elementTable == 'cronjob') {
4952 $fieldstatus = "status";
4953 }
4954 if ($elementTable == 'user') {
4955 $fieldstatus = "statut";
4956 }
4957 if ($elementTable == 'expensereport') {
4958 $fieldstatus = "fk_statut";
4959 }
4960 if ($elementTable == 'receptiondet_batch') {
4961 $fieldstatus = "status";
4962 }
4963 if ($elementTable == 'prelevement_bons') {
4964 $fieldstatus = "statut";
4965 }
4966 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4967 $fieldstatus = 'status';
4968 }
4969 // If still empty
4970 if (empty($fieldstatus)) {
4971 $fieldstatus = 'fk_statut';
4972 }
4973 }
4974
4975 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
4976 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
4977 // If status = 1 = validated and we update the main status field, we can update also fk_user_valid
4978 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields and on $fieldstatus
4979 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4980 $sql .= ", fk_user_valid = ".((int) $user->id);
4981 }
4982 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4983 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
4984 }
4985 if ($status == 1 && in_array($elementTable, array('inventory'))) {
4986 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
4987 }
4988 $sql .= " WHERE rowid = ".((int) $elementId);
4989 $sql .= " AND ".$this->db->sanitize($fieldstatus)." <> ".((int) $status); // We avoid update if status already correct
4990
4991 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4992 $resql = $this->db->query($sql);
4993 if ($resql) {
4994 $error = 0;
4995
4996 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
4997
4998 if ($nb_rows_affected > 0) {
4999 if (empty($trigkey)) {
5000 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5001 if ($this->element == 'supplier_proposal' && $status == 2) {
5002 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5003 }
5004 if ($this->element == 'supplier_proposal' && $status == 3) {
5005 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5006 }
5007 if ($this->element == 'supplier_proposal' && $status == 4) {
5008 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5009 }
5010 if ($this->element == 'fichinter' && $status == 3) {
5011 $trigkey = 'FICHINTER_CLASSIFY_DONE';
5012 }
5013 if ($this->element == 'fichinter' && $status == 2) {
5014 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5015 }
5016 if ($this->element == 'fichinter' && $status == 1) {
5017 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5018 }
5019 }
5020
5021 $this->context = array_merge($this->context, array('newstatus' => $status));
5022
5023 if ($trigkey && $trigkey != 'none') {
5024 $this->oldcopy = dol_clone($this);
5025
5026 // Call trigger
5027 $result = $this->call_trigger($trigkey, $user);
5028 if ($result < 0) {
5029 $error++;
5030 }
5031 // End call triggers
5032 }
5033 } else {
5034 // The status was probably already good. We do nothing more, no triggers.
5035 }
5036
5037 if (!$error) {
5038 $this->db->commit();
5039
5040 if (empty($savElementId)) {
5041 // If the element we update is $this (so $elementId was provided as null)
5042 if ($fieldstatus == 'dispute_status') {
5043 $this->dispute_status = $status;
5044 } elseif ($fieldstatus == 'tosell') {
5045 $this->status = $status;
5046 } elseif ($fieldstatus == 'tobuy') {
5047 $this->status_buy = $status; // @phpstan-ignore-line
5048 } elseif ($fieldstatus == 'tobatch') {
5049 $this->status_batch = $status; // @phpstan-ignore-line
5050 } else {
5051 $this->status = $status;
5052 }
5053 }
5054
5055 return 1;
5056 } else {
5057 $this->db->rollback();
5058 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
5059 return -1;
5060 }
5061 } else {
5062 $this->error = $this->db->lasterror();
5063 $this->db->rollback();
5064 return -1;
5065 }
5066 }
5067
5068
5076 public function getCanvas($id = 0, $ref = '')
5077 {
5078 if (empty($id) && empty($ref)) {
5079 return 0;
5080 }
5081 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5082 return 0; // To increase speed. Not enabled by default.
5083 }
5084
5085 // Clean parameters
5086 $ref = trim($ref);
5087
5088 $sql = "SELECT rowid, canvas";
5089 $sql .= " FROM ".$this->db->prefix().$this->table_element;
5090 $sql .= " WHERE entity IN (".getEntity($this->element).")";
5091 if (!empty($id)) {
5092 $sql .= " AND rowid = ".((int) $id);
5093 }
5094 if (!empty($ref)) {
5095 $sql .= " AND ref = '".$this->db->escape($ref)."'";
5096 }
5097
5098 $resql = $this->db->query($sql);
5099 if ($resql) {
5100 $obj = $this->db->fetch_object($resql);
5101 if ($obj) {
5102 $this->canvas = $obj->canvas;
5103 return 1;
5104 } else {
5105 return 0;
5106 }
5107 } else {
5108 dol_print_error($this->db);
5109 return -1;
5110 }
5111 }
5112
5113
5120 public function getSpecialCode($lineid)
5121 {
5122 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->table_element_line;
5123 $sql .= " WHERE rowid = ".((int) $lineid);
5124 $resql = $this->db->query($sql);
5125 if ($resql) {
5126 $row = $this->db->fetch_row($resql);
5127 return (!empty($row[0]) ? $row[0] : 0);
5128 }
5129
5130 return 0;
5131 }
5132
5141 public function isObjectUsed($id = 0, $entity = 0)
5142 {
5143 global $langs;
5144
5145 if (empty($id)) {
5146 $id = $this->id;
5147 }
5148
5149 // Check parameters
5150 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5151 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5152 return -1;
5153 }
5154
5155 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5156 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5157 $tmparray = array_keys($this->childtables);
5158 if (is_numeric($tmparray[0])) {
5159 $arraytoscan = array_flip($this->childtables);
5160 }
5161
5162 // Test if child exists
5163 $haschild = 0;
5164 foreach ($arraytoscan as $table => $element) {
5165 //print $id.'-'.$table.'-'.$elementname.'<br>';
5166
5167 // Check if module is enabled (to avoid error if tables of module not created)
5168 if (isset($element['enabled']) && !empty($element['enabled'])) {
5169 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5170 if (empty($enabled)) {
5171 continue;
5172 }
5173 }
5174
5175 // Check if element can be deleted
5176 $sql = "SELECT COUNT(*) as nb";
5177 $sql .= " FROM ".$this->db->prefix().$table." as c";
5178 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5179 $sql .= ", ".$this->db->prefix().$element['parent']." as p";
5180 }
5181 if (!empty($element['fk_element'])) {
5182 $sql .= " WHERE c.".$element['fk_element']." = ".((int) $id);
5183 } else {
5184 $sql .= " WHERE c.".$this->fk_element." = ".((int) $id);
5185 }
5186 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5187 $sql .= " AND c.".$element['parentkey']." = p.rowid";
5188 }
5189 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5190 $sql .= " AND c.".$element['parenttypefield']." = '".$this->db->escape($element['parenttypevalue'])."'";
5191 }
5192 if (!empty($entity)) {
5193 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5194 $sql .= " AND p.entity = ".((int) $entity);
5195 } else {
5196 $sql .= " AND c.entity = ".((int) $entity);
5197 }
5198 }
5199
5200 $resql = $this->db->query($sql);
5201 if ($resql) {
5202 $obj = $this->db->fetch_object($resql);
5203 if ($obj->nb > 0) {
5204 $langs->load("errors");
5205 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5206 $haschild += $obj->nb;
5207 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5208 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5209 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5210 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5211 } else { // new usage: $element['name']=Translation key
5212 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5213 }
5214 break; // We found at least one, we stop here
5215 }
5216 } else {
5217 $this->errors[] = $this->db->lasterror();
5218 return -1;
5219 }
5220 }
5221 if ($haschild > 0) {
5222 $this->errors[] = "ErrorRecordHasChildren";
5223 return $haschild;
5224 } else {
5225 return 0;
5226 }
5227 }
5228
5235 public function hasProductsOrServices($predefined = -1)
5236 {
5237 $nb = 0;
5238
5239 foreach ($this->lines as $key => $val) {
5240 $qualified = 0;
5241 if ($predefined == -1) {
5242 $qualified = 1;
5243 }
5244 if ($predefined == 1 && $val->fk_product > 0) {
5245 $qualified = 1;
5246 }
5247 if ($predefined == 0 && $val->fk_product <= 0) {
5248 $qualified = 1;
5249 }
5250 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5251 $qualified = 1;
5252 }
5253 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5254 $qualified = 1;
5255 }
5256 if ($qualified) {
5257 $nb++;
5258 }
5259 }
5260 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5261 return $nb;
5262 }
5263
5269 public function getTotalDiscount()
5270 {
5271 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5272 $total_discount = 0.00;
5273
5274 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5275 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5276 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
5277
5278 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5279 $resql = $this->db->query($sql);
5280 if ($resql) {
5281 $num = $this->db->num_rows($resql);
5282 $i = 0;
5283 while ($i < $num) {
5284 $obj = $this->db->fetch_object($resql);
5285
5286 $pu_ht = $obj->pu_ht;
5287 $qty = $obj->qty;
5288 $total_ht = $obj->total_ht;
5289
5290 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5291 $total_discount += $total_discount_line;
5292
5293 $i++;
5294 }
5295 }
5296
5297 //print $total_discount; exit;
5298 return (float) price2num($total_discount);
5299 }
5300
5301 return null;
5302 }
5303
5304
5311 public function getTotalWeightVolume()
5312 {
5313 $totalWeight = 0;
5314 $totalVolume = 0;
5315 // defined for shipment only
5316 $totalOrdered = 0;
5317 // defined for shipment only
5318 $totalToShip = 0;
5319
5320 if (empty($this->lines)) {
5321 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5322 }
5323
5324 foreach ($this->lines as $line) {
5325 if (isset($line->qty_asked)) {
5326 $totalOrdered += $line->qty_asked; // defined for shipment only
5327 }
5328 if (isset($line->qty_shipped)) {
5329 $totalToShip += $line->qty_shipped; // defined for shipment only
5330 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5331 if (empty($totalToShip)) {
5332 $totalToShip = 0;
5333 }
5334 $totalToShip += $line->qty; // defined for reception only
5335 }
5336
5337 // Define qty, weight, volume, weight_units, volume_units
5338 if ($this->element == 'shipping') {
5339 // for shipments
5340 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5341 } else {
5342 $qty = $line->qty ? $line->qty : 0;
5343 }
5344
5345 $weight = !empty($line->weight) ? $line->weight : 0;
5346 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5347 $volume = !empty($line->volume) ? $line->volume : 0;
5348 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5349
5350 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5351 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5352 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5353 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5354
5355 $weightUnit = 0;
5356 $volumeUnit = 0;
5357 if (!empty($weight_units)) {
5358 $weightUnit = $weight_units;
5359 }
5360 if (!empty($volume_units)) {
5361 $volumeUnit = $volume_units;
5362 }
5363
5364 if (empty($totalWeight)) {
5365 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5366 }
5367 if (empty($totalVolume)) {
5368 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5369 }
5370
5371 //var_dump($line->volume_units);
5372 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5373 $trueWeightUnit = pow(10, $weightUnit);
5374 $totalWeight += $weight * $qty * $trueWeightUnit;
5375 } else {
5376 if ($weight_units == 99) {
5377 // conversion 1 Pound = 0.45359237 KG
5378 $trueWeightUnit = 0.45359237;
5379 $totalWeight += $weight * $qty * $trueWeightUnit;
5380 } elseif ($weight_units == 98) {
5381 // conversion 1 Ounce = 0.0283495 KG
5382 $trueWeightUnit = 0.0283495;
5383 $totalWeight += $weight * $qty * $trueWeightUnit;
5384 } else {
5385 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5386 }
5387 }
5388 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5389 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5390 $trueVolumeUnit = pow(10, $volumeUnit);
5391 //print $line->volume;
5392 $totalVolume += $volume * $qty * $trueVolumeUnit;
5393 } else {
5394 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5395 }
5396 }
5397
5398 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5399 }
5400
5401
5407 public function setExtraParameters()
5408 {
5409 $this->db->begin();
5410
5411 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5412 $extraparams = dol_trunc($extraparams, 250);
5413
5414 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5415 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5416 $sql .= " WHERE rowid = ".((int) $this->id);
5417
5418 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5419 $resql = $this->db->query($sql);
5420 if (!$resql) {
5421 $this->error = $this->db->lasterror();
5422 $this->db->rollback();
5423 return -1;
5424 } else {
5425 $this->db->commit();
5426 return 1;
5427 }
5428 }
5429
5430
5431 // --------------------
5432 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5433 // --------------------
5434
5435 /* This is to show add lines */
5436
5446 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5447 {
5448 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5449
5450 // Line extrafield
5451 if (!is_object($extrafields)) {
5452 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5453 $extrafields = new ExtraFields($this->db);
5454 }
5455 $extrafields->fetch_name_optionals_label($this->table_element_line);
5456
5457 // Output template part (modules that overwrite templates must declare this into descriptor)
5458 // Use global variables + $dateSelector + $seller and $buyer
5459 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5460 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5461 foreach ($dirtpls as $module => $reldir) {
5462 if (!empty($module)) {
5463 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5464 } else {
5465 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5466 }
5467
5468 if (empty($conf->file->strict_mode)) {
5469 $res = @include $tpl;
5470 } else {
5471 $res = include $tpl; // for debug
5472 }
5473 if ($res) {
5474 break;
5475 }
5476 }
5477 }
5478
5479
5480
5481 /* This is to show array of line of details */
5482
5483
5498 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5499 {
5500 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5501 // TODO We should not use global var for this
5502 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5503
5504 // Define $usemargins (used by objectline_xxx.tpl.php files)
5505 $usemargins = 0;
5506 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5507 $usemargins = 1;
5508 }
5509
5510 $num = count($this->lines);
5511
5512 // Line extrafield
5513 if (!is_object($extrafields)) {
5514 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5515 $extrafields = new ExtraFields($this->db);
5516 }
5517 $extrafields->fetch_name_optionals_label($this->table_element_line);
5518
5519 if (method_exists($this, 'loadExpeditions')) {
5520 // 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.
5521 $this->loadExpeditions();
5522 }
5523
5524 $parameters = array();
5525 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5526 if (empty($reshook)) {
5527 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5528 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5529 if (empty($reshook)) {
5530 // Output template part (modules that overwrite templates must declare this into descriptor)
5531 // Use global variables + $dateSelector + $seller and $buyer
5532 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5533 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5534 foreach ($dirtpls as $module => $reldir) {
5535 $res = 0;
5536 if (!empty($module)) {
5537 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5538 } else {
5539 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5540 }
5541 if (file_exists($tpl)) {
5542 if (empty($conf->file->strict_mode)) {
5543 $res = @include $tpl;
5544 } else {
5545 $res = include $tpl; // for debug
5546 }
5547 }
5548 if ($res) {
5549 break;
5550 }
5551 }
5552 }
5553
5554 $i = 0;
5555
5556 print "<!-- begin printObjectLines() -->\n";
5557 foreach ($this->lines as $line) {
5558 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5559 //$line->fetch_optionals();
5560
5561 if (is_object($hookmanager)) {
5562 if (empty($line->fk_parent_line)) {
5563 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5564 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5565 } else {
5566 $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);
5567 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5568 }
5569 }
5570 if (empty($reshook)) {
5571 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5572 }
5573
5574 $i++;
5575 }
5576 print "<!-- end printObjectLines() -->\n";
5577 }
5578 }
5579
5597 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5598 {
5599 global $conf, $langs, $user, $object, $hookmanager; // used into tpl
5600 global $form;
5601 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5602
5603 // var used into tpl
5604 $text = '';
5605 $description = '';
5606
5607 // Line in view mode
5608 if ($action != 'editline' || $selected != $line->id) {
5609 // Product
5610 if (!empty($line->fk_product) && $line->fk_product > 0) {
5611 $product_static = new Product($this->db);
5612 $product_static->fetch($line->fk_product);
5613
5614 $product_static->ref = $line->ref; //can change ref in hook
5615 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5616
5617 $text = $product_static->getNomUrl(1);
5618
5619 // Define output language and label
5620 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5621 // @phan-suppress-next-line PhanUndeclaredProperty
5622 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5623 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5624 return;
5625 }
5626
5627 $prod = new Product($this->db);
5628 $prod->fetch($line->fk_product);
5629
5630 $outputlangs = $langs;
5631 $newlang = '';
5632 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5633 $newlang = GETPOST('lang_id', 'aZ09');
5634 }
5635 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5636 $newlang = $this->thirdparty->default_lang; // To use language of customer
5637 }
5638 if (!empty($newlang)) {
5639 $outputlangs = new Translate("", $conf);
5640 $outputlangs->setDefaultLang($newlang);
5641 }
5642
5643 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5644 } else {
5645 $label = $line->product_label;
5646 }
5647
5648 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5649 $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.
5650 }
5651
5652 // Recalculate unit price with tax if not defined
5653 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)
5654 // So we calculate an estimated value just to show something on screen
5655 if ($line->remise_percent != 100) {
5656 $line->subprice_ttc = (float) price2num($line->total_ttc / $line->qty / (1 - $line->remise_percent / 100), 'MU');
5657 } else {
5658 // Other method is less accurate
5659 $line->subprice_ttc = (float) price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5660 }
5661 }
5662 $line->pu_ttc = $line->subprice_ttc; // deprecated
5663
5664 // Output template part (modules that overwrite templates must declare this into descriptor)
5665 // Use global variables + $dateSelector + $seller and $buyer
5666 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5667
5668 $qty_shipped = 0;
5669 if (isset($this->expeditions[$line->id])) {
5670 $qty_shipped = $this->expeditions[$line->id];
5671 }
5672 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5673
5674 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5675 foreach ($dirtpls as $module => $reldir) {
5676 $res = 0;
5677 if (!empty($module)) {
5678 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5679 } else {
5680 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5681 }
5682 //var_dump($tpl);
5683 if (file_exists($tpl)) {
5684 if (empty($conf->file->strict_mode)) {
5685 $res = @include $tpl;
5686 } else {
5687 $res = include $tpl; // for debug
5688 }
5689 }
5690 if ($res) {
5691 break;
5692 }
5693 }
5694 }
5695
5696 // Line in update mode
5697 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5698 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5699
5700 $line->subprice_ttc = (float) price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5701 $line->pu_ttc = $line->subprice_ttc; // deprecated
5702
5703 // Output template part (modules that overwrite templates must declare this into descriptor)
5704 // Use global variables + $dateSelector + $seller and $buyer
5705 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5706 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5707 foreach ($dirtpls as $module => $reldir) {
5708 if (!empty($module)) {
5709 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5710 } else {
5711 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5712 }
5713
5714 if (empty($conf->file->strict_mode)) {
5715 $res = @include $tpl;
5716 } else {
5717 $res = include $tpl; // for debug
5718 }
5719 if ($res) {
5720 break;
5721 }
5722 }
5723 }
5724 }
5725
5726
5727 /* This is to show array of line of details of source object */
5728
5729
5740 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5741 {
5742 global $langs, $hookmanager, $form, $action;
5743
5744 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5745 print '<tr class="liste_titre">';
5746 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5747 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5748 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5749 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5750 if (isModEnabled("multicurrency")) {
5751 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5752 }
5753 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5754 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5755 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5756 }
5757 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5758 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5759 print '<td class="center">';
5760 print $form->showCheckAddButtons('checkforselect', 1);
5761 print '</td>';
5762 print '</tr>';
5763 $i = 0;
5764
5765 if (!empty($this->lines)) {
5766 foreach ($this->lines as $line) {
5767 $reshook = 0;
5768 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5769 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5770 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5771 if (!empty($line->fk_parent_line)) {
5772 $parameters['fk_parent_line'] = $line->fk_parent_line;
5773 }
5774 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5775 }
5776 if (empty($reshook)) {
5777 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5778 }
5779
5780 $i++;
5781 }
5782 }
5783 }
5784
5798 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5799 {
5800 global $langs, $conf;
5801
5802 //var_dump($line);
5803 if (!empty($line->date_start)) {
5804 // @phan-suppress-next-line PhanUndeclaredProperty
5805 $date_start = $line->date_start;
5806 } else {
5807 $date_start = $line->date_debut_prevue;
5808 if ($line->date_debut_reel) {
5809 $date_start = $line->date_debut_reel;
5810 }
5811 }
5812 if (!empty($line->date_end)) {
5813 // @phan-suppress-next-line PhanUndeclaredProperty
5814 $date_end = $line->date_end;
5815 } else {
5816 $date_end = $line->date_fin_prevue;
5817 if ($line->date_fin_reel) {
5818 $date_end = $line->date_fin_reel;
5819 }
5820 }
5821
5822 // Set thevalue into ->tpl[] array.
5823 $this->tpl['id'] = $line->id;
5824
5825 $this->tpl['label'] = '';
5826 if (!empty($line->fk_parent_line)) {
5827 $this->tpl['label'] .= img_picto('', 'rightarrow.png');
5828 }
5829
5830 if (((int) $line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5831 $discount = new DiscountAbsolute($this->db);
5832 if (property_exists($this, 'socid')) {
5833 $discount->fk_soc = $this->socid;
5834 $discount->socid = $this->socid;
5835 }
5836 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5837 } elseif (!empty($line->fk_product)) {
5838 $productstatic = new Product($this->db);
5839 $productstatic->id = $line->fk_product;
5840 $productstatic->ref = $line->ref;
5841 $productstatic->type = $line->fk_product_type;
5842 if (empty($productstatic->ref)) {
5843 $line->fetch_product();
5844 $productstatic = $line->product;
5845 }
5846
5847 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5848 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5849 // Dates
5850 if ($line->product_type == 1 && ($date_start || $date_end)) {
5851 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5852 }
5853 } else {
5854 $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"')));
5855 if (!empty($line->desc)) {
5856 $this->tpl['label'] .= $line->desc;
5857 } else {
5858 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5859 }
5860
5861 // Dates
5862 if ($line->product_type == 1 && ($date_start || $date_end)) {
5863 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5864 }
5865 }
5866
5867 if (!empty($line->desc)) {
5868 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5869 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5870 $discount = new DiscountAbsolute($this->db);
5871 $discount->fetch($line->fk_remise_except);
5872 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5873 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5874 $discount = new DiscountAbsolute($this->db);
5875 $discount->fetch($line->fk_remise_except);
5876 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5877 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5878 $discount = new DiscountAbsolute($this->db);
5879 $discount->fetch($line->fk_remise_except);
5880 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5881 } elseif ($line->desc == '(EXCESS PAID)') {
5882 $discount = new DiscountAbsolute($this->db);
5883 $discount->fetch($line->fk_remise_except);
5884 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5885 } else {
5886 $this->tpl['description'] = dol_trunc($line->desc, 60);
5887 }
5888 } else {
5889 $this->tpl['description'] = '&nbsp;';
5890 }
5891
5892 // VAT Rate
5893 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5894 $this->tpl['vat_rate'] .= (((int) $line->info_bits & 1) == 1) ? '*' : '';
5895 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5896 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5897 }
5898
5899 $this->tpl['price'] = price($line->subprice);
5900 $this->tpl['total_ht'] = price($line->total_ht);
5901 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5902 $this->tpl['qty'] = (((int) $line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5903 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5904 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
5905 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
5906 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
5907 }
5908 $this->tpl['remise_percent'] = (((int) $line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
5909
5910 // Is the line strike or not
5911 $this->tpl['strike'] = 0;
5912 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5913 $this->tpl['strike'] = 1;
5914 } elseif (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIAL_CODE) {
5915 // SUBTOTALS_SPECIAL_CODE is only defined when the subtotals module
5916 // is loaded (htdocs/subtotals/class/commonsubtotal.class.php:24).
5917 // Without the guard, printing origin lines from a PO into a vendor
5918 // invoice fatals (#37663). Other tpl files already follow this pattern.
5919 $this->tpl['strike'] = 1;
5920 }
5921
5922 // Output template part (modules that overwrite templates must declare this into descriptor)
5923 // Use global variables + $dateSelector + $seller and $buyer
5924 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5925 foreach ($dirtpls as $module => $reldir) {
5926 if (!empty($module)) {
5927 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5928 } else {
5929 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5930 }
5931
5932 if (empty($conf->file->strict_mode)) {
5933 $res = @include $tpl;
5934 } else {
5935 $res = include $tpl; // for debug
5936 }
5937 if ($res) {
5938 break;
5939 }
5940 }
5941 }
5942
5943
5944 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5956 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0, $notrigger = 0)
5957 {
5958 // phpcs:enable
5959 global $user;
5960 $this->db->begin();
5961
5962 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5963 $sql .= "resource_id";
5964 $sql .= ", resource_type";
5965 $sql .= ", element_id";
5966 $sql .= ", element_type";
5967 $sql .= ", busy";
5968 $sql .= ", mandatory";
5969 $sql .= ") VALUES (";
5970 $sql .= ((int) $resource_id);
5971 $sql .= ", '".$this->db->escape($resource_type)."'";
5972 $sql .= ", '".$this->db->escape((string) $this->id)."'";
5973 $sql .= ", '".$this->db->escape($this->element)."'";
5974 $sql .= ", '".$this->db->escape((string) $busy)."'";
5975 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
5976 $sql .= ")";
5977
5978 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5979 if ($this->db->query($sql)) {
5980 if (!$notrigger) {
5981 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_ADD_RESOURCE', $user);
5982 if ($result < 0) {
5983 $this->db->rollback();
5984 return -1;
5985 }
5986 }
5987 $this->db->commit();
5988 return 1;
5989 } else {
5990 $this->error = $this->db->lasterror();
5991 $this->db->rollback();
5992 return 0;
5993 }
5994 }
5995
5996 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6005 public function delete_resource($rowid, $element = '', $notrigger = 0)
6006 {
6007 // phpcs:enable
6008 global $user;
6009
6010 $this->db->begin();
6011
6012 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
6013 $sql .= " WHERE rowid = ".((int) $rowid);
6014
6015 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
6016
6017 $resql = $this->db->query($sql);
6018 if (!$resql) {
6019 $this->error = $this->db->lasterror();
6020 $this->db->rollback();
6021 return -1;
6022 } else {
6023 if (!$notrigger) {
6024 $result = $this->call_trigger(strtoupper($this->TRIGGER_PREFIX).'_DELETE_RESOURCE', $user);
6025 if ($result < 0) {
6026 $this->db->rollback();
6027 return -1;
6028 }
6029 }
6030 $this->db->commit();
6031 return 1;
6032 }
6033 }
6034
6035
6041 public function __clone()
6042 {
6043 // Force a copy of this->lines, otherwise it will point to same object.
6044 if (isset($this->lines) && is_array($this->lines)) {
6045 $nboflines = count($this->lines);
6046 for ($i = 0; $i < $nboflines; $i++) {
6047 if (is_object($this->lines[$i])) {
6048 $this->lines[$i] = clone $this->lines[$i];
6049 }
6050 }
6051 }
6052 }
6053
6067 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6068 {
6069 global $conf, $langs, $hookmanager, $action;
6070
6071 $srctemplatepath = '';
6072
6073 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
6074 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6075
6076 if (!empty($reshook)) {
6077 return $reshook;
6078 }
6079
6080 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
6081
6082 if (empty($modele)) {
6083 $this->error = 'BadValueForParameterModele';
6084 return -1;
6085 }
6086
6087 // Increase limit for PDF build
6088 $err = error_reporting();
6089 error_reporting(0);
6090 @set_time_limit(120);
6091 error_reporting($err);
6092
6093 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6094 $tmp = explode(':', $modele, 2);
6095 $saved_model = $modele;
6096 if (!empty($tmp[1])) {
6097 $modele = $tmp[0];
6098 $srctemplatepath = $tmp[1];
6099 }
6100
6101 // Search template files
6102 $file = '';
6103 $classname = '';
6104 $filefound = '';
6105 $dirmodels = array('/');
6106 if (is_array($conf->modules_parts['models'])) {
6107 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6108 }
6109 foreach ($dirmodels as $reldir) {
6110 foreach (array('doc', 'pdf') as $prefix) {
6111 if (in_array(get_class($this), array('Adherent'))) {
6112 // Member module use prefix_modele.class.php
6113 $file = $prefix."_".$modele.".class.php";
6114 } else {
6115 // Other module use prefix_modele.modules.php
6116 $file = $prefix."_".$modele.".modules.php";
6117 }
6118
6119 $file = dol_sanitizeFileName($file);
6120
6121 // We check if the file exists
6122 $file = dol_buildpath($reldir.$modelspath.$file, 0);
6123 if (file_exists($file)) {
6124 $filefound = $file;
6125 $classname = $prefix.'_'.$modele;
6126 break;
6127 }
6128 }
6129 if ($filefound) {
6130 break;
6131 }
6132 }
6133
6134 if ($filefound === '' || $classname === '') {
6135 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
6136 $this->errors[] = $this->error;
6137 dol_syslog($this->error, LOG_ERR);
6138 return -1;
6139 }
6140
6141 // Sanitize $filefound
6142 $filefound = dol_sanitizePathName($filefound);
6143
6144 // If generator was found
6145 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
6146
6147 require_once $filefound;
6148
6149 $obj = new $classname($this->db);
6150
6151 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
6152 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
6153 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
6154 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
6155 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
6156 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
6157 // ModelePDFStock|ModelePDFStockTransfer|
6158 // ModeleDon|ModelePDFTask|
6159 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
6160 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
6161 '@phan-var-force ModelePDFMember $obj';
6162 // '@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';
6163
6164 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6165 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6166 $varfortemplatedir = $obj->scandir;
6167 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6168 $dirtoscan = getDolGlobalString($varfortemplatedir);
6169
6170 $listoffiles = array();
6171
6172 // Now we add first model found in directories scanned
6173 $listofdir = explode(',', $dirtoscan);
6174 foreach ($listofdir as $key => $tmpdir) {
6175 $tmpdir = trim($tmpdir);
6176 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6177 if (!$tmpdir) {
6178 unset($listofdir[$key]);
6179 continue;
6180 }
6181 if (is_dir($tmpdir)) {
6182 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6183 if (count($tmpfiles)) {
6184 $listoffiles = array_merge($listoffiles, $tmpfiles);
6185 }
6186 }
6187 }
6188
6189 if (count($listoffiles)) {
6190 foreach ($listoffiles as $record) {
6191 $srctemplatepath = $record['fullname'];
6192 break;
6193 }
6194 }
6195 }
6196
6197 if (empty($srctemplatepath)) {
6198 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6199 return -1;
6200 }
6201 }
6202
6203 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6204 if (!dol_is_file($srctemplatepath)) {
6205 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6206 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6207 return -1;
6208 }
6209 }
6210
6211 // We save charset_output to restore it because write_file can change it if needed for
6212 // output format that does not support UTF8.
6213 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6214
6215 // update model_pdf in object
6216 $this->model_pdf = $saved_model;
6217
6218 if ($obj instanceof ModelePDFMember) {
6219 if ($this instanceof Adherent) {
6220 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6221 } else {
6222 $resultwritefile = -1;
6223 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6224 }
6225 } elseif ($obj instanceof ModeleDon) {
6226 // Only 3 arguments
6227 if ($this instanceof Don) {
6228 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6229 } else {
6230 $resultwritefile = -1;
6231 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6232 }
6233 } else {
6234 // TODO: Try to set type above again
6235 '@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';
6236 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams); // @phan-suppress-line-PhanTypeMismatchArgument
6237 }
6238 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6239
6240
6241 if ($resultwritefile > 0) {
6242 $outputlangs->charset_output = $sav_charset_output;
6243
6244 // We delete old preview
6245 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6246 dol_delete_preview($this);
6247
6248 // Index file in database
6249 if (!empty($obj->result['fullpath'])) {
6250 $destfull = $obj->result['fullpath'];
6251
6252 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6253 $update_main_doc_field = 0;
6254 if (!empty($obj->update_main_doc_field)) {
6255 $update_main_doc_field = 1;
6256 }
6257
6258 // Check that the file exists, before indexing it.
6259 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6260 if (dol_is_file($destfull)) {
6261 $this->indexFile($destfull, $update_main_doc_field);
6262 }
6263 } else {
6264 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);
6265 }
6266
6267 // Success in building document. We build meta file.
6268 dol_meta_create($this);
6269
6270 $this->warnings = $obj->warnings;
6271
6272 return 1;
6273 } else {
6274 $outputlangs->charset_output = $sav_charset_output;
6275 $this->error = $obj->error;
6276 $this->errors = $obj->errors;
6277 $this->warnings = $obj->warnings;
6278 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
6279 return -1;
6280 }
6281 }
6282
6292 public function indexFile($destfull, $update_main_doc_field)
6293 {
6294 global $conf, $user;
6295
6296 $upload_dir = dirname($destfull);
6297 $destfile = basename($destfull);
6298 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6299
6300 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6301 $filename = basename($destfile);
6302 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6303 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6304
6305 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6306 $ecmfile = new EcmFiles($this->db);
6307 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6308
6309 // Set the public "share" key
6310 $setsharekey = false;
6311 if (
6312 !empty($this->TRIGGER_PREFIX)
6313 && (getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_EXTERNAL_DOWNLOAD") || getDolGlobalInt($this->TRIGGER_PREFIX . "_ALLOW_ONLINESIGN"))
6314 ) {
6315 $setsharekey = true;
6316 }
6317 // TODO Remove case covered by trigger prefix
6318 if ($this->element == 'propal' || $this->element == 'proposal') {
6319 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6320 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6321 }
6322 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6323 $setsharekey = true;
6324 }
6325 }
6326 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6327 $setsharekey = true;
6328 }
6329 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6330 $setsharekey = true;
6331 }
6332 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6333 $setsharekey = true;
6334 }
6335 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6336 $setsharekey = true;
6337 }
6338 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6339 $setsharekey = true;
6340 }
6341
6342 if ($setsharekey) {
6343 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6344 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6345 $ecmfile->share = getRandomPassword(true);
6346 }
6347 }
6348
6349 if ($result > 0) {
6350 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6351 $ecmfile->fullpath_orig = '';
6352 $ecmfile->gen_or_uploaded = 'generated';
6353 $ecmfile->description = ''; // indexed content
6354 $ecmfile->keywords = ''; // keyword content
6355 $ecmfile->src_object_type = $this->table_element;
6356 $ecmfile->src_object_id = $this->id;
6357 $result = $ecmfile->update($user);
6358 if ($result < 0) {
6359 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6360 return -1;
6361 }
6362 } else {
6363 $ecmfile->entity = $conf->entity;
6364 $ecmfile->filepath = $rel_dir;
6365 $ecmfile->filename = $filename;
6366 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6367 $ecmfile->fullpath_orig = '';
6368 $ecmfile->gen_or_uploaded = 'generated';
6369 $ecmfile->description = ''; // indexed content
6370 $ecmfile->keywords = ''; // keyword content
6371 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6372 $ecmfile->src_object_id = $this->id;
6373
6374 $result = $ecmfile->create($user);
6375 if ($result < 0) {
6376 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6377 return -1;
6378 }
6379 }
6380
6381 /*$this->result['fullname']=$destfull;
6382 $this->result['filepath']=$ecmfile->filepath;
6383 $this->result['filename']=$ecmfile->filename;*/
6384 //var_dump($obj->update_main_doc_field);exit;
6385
6386 if ($update_main_doc_field && !empty($this->table_element)) {
6387 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6388 $sql .= " WHERE rowid = ".((int) $this->id);
6389
6390 $resql = $this->db->query($sql);
6391 if (!$resql) {
6392 dol_print_error($this->db);
6393 return -1;
6394 } else {
6395 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6396 }
6397 }
6398 }
6399
6400 return 1;
6401 }
6402
6411 public function addThumbs($file, $quality = 50)
6412 {
6413 $file_osencoded = dol_osencode($file);
6414
6415 if (file_exists($file_osencoded)) {
6416 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6417
6418 $tmparraysize = getDefaultImageSizes();
6419 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6420 $maxheightsmall = $tmparraysize['maxheightsmall'];
6421 $maxwidthmini = $tmparraysize['maxwidthmini'];
6422 $maxheightmini = $tmparraysize['maxheightmini'];
6423 //$quality = $tmparraysize['quality'];
6424
6425 // Create small thumbs for company (Ratio is near 16/9)
6426 // Used on logon for example
6427 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6428
6429 // Create mini thumbs for company (Ratio is near 16/9)
6430 // Used on menu or for setup page for example
6431 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6432 }
6433 }
6434
6442 public function delThumbs($file)
6443 {
6444 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6445 dol_delete_file($imgThumbName);
6446 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6447 dol_delete_file($imgThumbName);
6448 }
6449
6450
6451 /* Functions common to commonobject and commonobjectline */
6452
6453 /* For default values */
6454
6468 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6469 {
6470 /* TODO Remove this. Must use now something like:
6471 $note_private = GETPOST('note_private', 'restricthtml');
6472 if (!GETPOSTISSET('note_private') && empty($note_private) && !empty($objectsrc) [&& othercondtion]) {
6473 $note_private = $objectsrc->note_private;
6474 }
6475 */
6476
6477 // If param here has been posted, we use this value first.
6478 if (GETPOSTISSET($fieldname)) {
6479 return GETPOST($fieldname, $type, 3);
6480 }
6481
6482 if (isset($alternatevalue)) {
6483 return $alternatevalue;
6484 }
6485
6486 $newelement = $this->element;
6487 if ($newelement == 'facture') {
6488 $newelement = 'invoice';
6489 }
6490 if ($newelement == 'commande') {
6491 $newelement = 'order';
6492 }
6493 if (empty($newelement)) {
6494 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6495 return '';
6496 }
6497
6498 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6499 //var_dump($keyforfieldname);
6500 if (getDolGlobalString($keyforfieldname)) {
6501 return getDolGlobalString($keyforfieldname);
6502 }
6503
6504 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6505 // store content into $conf->cache['overwrite_default']
6506
6507 return '';
6508 }
6509
6510
6511 /* Functions for data in other language */
6512
6513
6523 {
6524 // To avoid SQL errors. Probably not the better solution though
6525 if (!$this->element) {
6526 return 0;
6527 }
6528 if (!($this->id > 0)) {
6529 return 0;
6530 }
6531 if (is_array($this->array_languages)) {
6532 return 1;
6533 }
6534
6535 $this->array_languages = array();
6536
6537 $element = $this->element;
6538 if ($element == 'categorie') {
6539 $element = 'categories'; // For compatibility
6540 }
6541
6542 // Request to get translation values for object
6543 $sql = "SELECT rowid, property, lang , value";
6544 $sql .= " FROM ".$this->db->prefix()."object_lang";
6545 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6546 $sql .= " AND fk_object = ".((int) $this->id);
6547
6548 $resql = $this->db->query($sql);
6549 if ($resql) {
6550 $numrows = $this->db->num_rows($resql);
6551 if ($numrows) {
6552 $i = 0;
6553 while ($i < $numrows) {
6554 $obj = $this->db->fetch_object($resql);
6555 $key = $obj->property;
6556 $value = $obj->value;
6557 $codelang = $obj->lang;
6558 $type = $this->fields[$key]['type'];
6559
6560 // we can add this attribute to object
6561 if (preg_match('/date/', $type)) {
6562 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6563 } else {
6564 $this->array_languages[$key][$codelang] = $value;
6565 }
6566
6567 $i++;
6568 }
6569 }
6570
6571 $this->db->free($resql);
6572
6573 if ($numrows) {
6574 return $numrows;
6575 } else {
6576 return 0;
6577 }
6578 } else {
6579 dol_print_error($this->db);
6580 return -1;
6581 }
6582 }
6583
6591 public function setValuesForExtraLanguages($onlykey = '')
6592 {
6593 // Get extra fields
6594 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6595 $tmparray = explode('-', $postfieldkey);
6596 if ($tmparray[0] != 'field') {
6597 continue;
6598 }
6599
6600 $element = $tmparray[1];
6601 $key = $tmparray[2];
6602 $codelang = $tmparray[3];
6603 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6604
6605 if (!empty($onlykey) && $key != $onlykey) {
6606 continue;
6607 }
6608 if ($element != $this->element) {
6609 continue;
6610 }
6611
6612 $key_type = $this->fields[$key]['type'];
6613
6614 $enabled = 1;
6615 if (isset($this->fields[$key]['enabled'])) {
6616 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6617 }
6618 /*$perms = 1;
6619 if (isset($this->fields[$key]['perms']))
6620 {
6621 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6622 }*/
6623 if (empty($enabled)) {
6624 continue;
6625 }
6626 //if (empty($perms)) continue;
6627
6628 if (in_array($key_type, array('date'))) {
6629 // Clean parameters
6630 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6631 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6632 } elseif (in_array($key_type, array('datetime'))) {
6633 // Clean parameters
6634 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6635 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6636 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6637 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6638 if (!empty($value_arr)) {
6639 $value_key = implode(',', $value_arr);
6640 } else {
6641 $value_key = '';
6642 }
6643 } elseif (in_array($key_type, array('price', 'double'))) {
6644 $value_arr = GETPOST($postfieldkey, 'alpha');
6645 $value_key = price2num($value_arr);
6646 } else {
6647 $value_key = GETPOST($postfieldkey);
6648 if (in_array($key_type, array('link')) && $value_key == '-1') {
6649 $value_key = '';
6650 }
6651 }
6652
6653 $this->array_languages[$key][$codelang] = $value_key;
6654
6655 /*if ($nofillrequired) {
6656 $langs->load('errors');
6657 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6658 return -1;
6659 }*/
6660 }
6661
6662 return 1;
6663 }
6664
6665
6666 /* Functions for extrafields */
6667
6674 public function fetchNoCompute($id)
6675 {
6676 global $conf;
6677
6678 $savDisableCompute = $conf->disable_compute;
6679 $conf->disable_compute = 1;
6680
6681 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6682
6683 $conf->disable_compute = $savDisableCompute;
6684
6685 return $ret;
6686 }
6687
6688 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6698 public function fetch_optionals($rowid = null, $optionsArray = null)
6699 {
6700 // phpcs:enable
6701 global $conf, $extrafields;
6702
6703 if (empty($rowid)) {
6704 $rowid = $this->id;
6705 }
6706 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6707 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6708 }
6709
6710 // To avoid SQL errors. Probably not the better solution though
6711 if (!$this->table_element) {
6712 return 0;
6713 }
6714
6715 $this->array_options = array();
6716
6717 if (!is_array($optionsArray)) {
6718 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6719 if (!isset($extrafields) || !is_object($extrafields)) {
6720 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6721 $extrafields = new ExtraFields($this->db);
6722 }
6723
6724 // Load array of extrafields for elementype = $this->table_element
6725 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6726 $extrafields->fetch_name_optionals_label($this->table_element);
6727 }
6728 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6729 } else {
6730 global $extrafields;
6731 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6732 }
6733
6734 $table_element = $this->table_element;
6735 if ($table_element == 'categorie') {
6736 $table_element = 'categories'; // For compatibility
6737 }
6738
6739 // Request to get complementary values
6740 if (is_array($optionsArray) && count($optionsArray) > 0) {
6741 $sql = "SELECT rowid";
6742 foreach ($optionsArray as $name => $label) {
6743 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6744 $sql .= ", ".$this->db->sanitize($name);
6745 }
6746 // use geo sql fonction to read as text
6747 if (!empty($extrafields->attributes[$this->table_element]['type'][$name]) && in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6748 // TODO Add an abstraction method in the database driver
6749 $sql .= ", ST_AsWKT(".$name.") as ".$this->db->sanitize($name);
6750 }
6751 }
6752 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6753 $sql .= " WHERE fk_object = ".((int) $rowid);
6754
6755 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6756 $resql = $this->db->query($sql);
6757 if ($resql) {
6758 $numrows = $this->db->num_rows($resql);
6759 if ($numrows) {
6760 $tab = $this->db->fetch_array($resql);
6761
6762 foreach ($tab as $key => $value) {
6763 // 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)
6764 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6765 // we can add this attribute to object
6766 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6767 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6768 $this->array_options["options_".$key] = $this->db->jdate($value);
6769 } else {
6770 $this->array_options["options_".$key] = $value;
6771 }
6772
6773 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6774 }
6775 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6776 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6777 $this->array_options["options_".$key] = dolDecrypt($value);
6778 }
6779 }
6780 }
6781 } else {
6786 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6787 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6788 $this->array_options['options_' . $key] = null;
6789 }
6790 }
6791 }
6792
6793 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6794 // in the element's extrafields table)
6795 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6796 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6797 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6798 //var_dump($conf->disable_compute);
6799 if (empty($conf->disable_compute)) {
6800 global $objectoffield; // We set a global variable to $objectoffield so
6801 $objectoffield = $this; // we can use it inside computed formula
6802 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6803 }
6804 }
6805 }
6806 }
6807
6808 $this->db->free($resql);
6809
6810 if ($numrows) {
6811 return $numrows;
6812 } else {
6813 return 0;
6814 }
6815 } else {
6816 $this->errors[] = $this->db->lasterror;
6817 return -1;
6818 }
6819 }
6820 return 0;
6821 }
6822
6829 public function deleteExtraFields()
6830 {
6831 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6832 return 0;
6833 }
6834
6835 $this->db->begin();
6836
6837 $table_element = $this->table_element;
6838 if ($table_element == 'categorie') {
6839 $table_element = 'categories'; // For compatibility
6840 }
6841
6842 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6843
6844 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6845
6846 $resql = $this->db->query($sql_del);
6847 if (!$resql) {
6848 $this->error = $this->db->lasterror();
6849 $this->db->rollback();
6850 return -1;
6851 } else {
6852 $this->db->commit();
6853 return 1;
6854 }
6855 }
6856
6867 public function insertExtraFields($trigger = '', $userused = null)
6868 {
6869 global $langs, $user;
6870
6871 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6872 return 0;
6873 }
6874
6875 if (empty($userused)) {
6876 $userused = $user;
6877 }
6878
6879 $error = 0;
6880
6881 // Check parameters
6882 $langs->load('admin');
6883 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6884 $extrafields = new ExtraFields($this->db);
6885 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6886
6887 // Eliminate copied source object extra fields that do not exist in target object
6888 $new_array_options = array();
6889 foreach ($this->array_options as $key => $value) {
6890 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6891 $new_array_options[$key] = $value;
6892 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6893 $new_array_options['options_'.$key] = $value;
6894 }
6895 }
6896
6897 foreach ($new_array_options as $key => $value) {
6898 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6899 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6900 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6901 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6902 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6903 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6904 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6905 $attributeEmptyOnClone = $extrafields->attributes[$this->table_element]['emptyonclone'][$attributeKey];
6906
6907 // If we clone, we have to clean unique extrafields to prevent duplicates.
6908 // If we clone, we have to clean extrafields having "empty on clone" option on.
6909 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6910 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && (!empty($attributeUnique) || !empty($attributeEmptyOnClone))) {
6911 $new_array_options[$key] = null;
6912 continue;
6913 }
6914
6915 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6916 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6917 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6918 $new_array_options[$key] = null;
6919 continue;
6920 }
6921
6922 // Similar code than into insertExtraFields
6923 if ($attributeRequired) {
6924 $v = $this->array_options[$key];
6925 if (ExtraFields::isEmptyValue($v, $attributeType)) {
6926 $langs->load("errors");
6927 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6928 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6929 return -1;
6930 }
6931 }
6932
6933 if (!empty($attrfieldcomputed)) {
6934 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6935 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
6936 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
6937 $new_array_options[$key] = $value;
6938 } else {
6939 $new_array_options[$key] = null;
6940 }
6941 continue;
6942 }
6943
6944 switch ($attributeType) {
6945 case 'int':
6946 case 'duration':
6947 case 'stars':
6948 if (!is_numeric($value) && $value != '') {
6949 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6950 return -1;
6951 } elseif ($value === '') {
6952 $new_array_options[$key] = null;
6953 }
6954 break;
6955 case 'boolean':
6956 if ($value === '' || $value === false || $value === null) {
6957 $new_array_options[$key] = null;
6958 }
6959 break;
6960 case 'price':
6961 case 'double':
6962 $value = price2num($value);
6963 if (!is_numeric($value) && $value != '') {
6964 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6965 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6966 return -1;
6967 } elseif ($value == '') {
6968 $value = null;
6969 }
6970 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6971 $new_array_options[$key] = $value;
6972 break;
6973 /*case 'select': // Not required, we chose value='0' for undefined values
6974 if ($value=='-1')
6975 {
6976 $this->array_options[$key] = null;
6977 }
6978 break;*/
6979 case 'password':
6980 $algo = '';
6981 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6982 // If there is an encryption choice, we use it to encrypt data before insert
6983 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6984 $algo = reset($tmparrays);
6985 if ($algo != '') {
6986 //global $action; // $action may be 'create', 'update', 'update_extras'...
6987 //var_dump($action);
6988 //var_dump($this->oldcopy);exit;
6989 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
6990 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6991 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6992 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6993 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6994 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6995 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6996 } else {
6997 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6998 }
6999 } else {
7000 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7001 }
7002 } else {
7003 // If value has changed
7004 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7005 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7006 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7007 } else {
7008 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7009 }
7010 } else {
7011 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7012 }
7013 }
7014 } else {
7015 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7016 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7017 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
7018 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
7019 } else {
7020 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7021 }
7022 }
7023 } else {
7024 // No encryption
7025 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7026 }
7027 } else { // Common usage
7028 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7029 }
7030 break;
7031 case 'date':
7032 case 'datetime':
7033 // If data is a string instead of a timestamp, we convert it
7034 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7035 $this->array_options[$key] = strtotime($this->array_options[$key]);
7036 }
7037 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7038 break;
7039 case 'datetimegmt':
7040 // If data is a string instead of a timestamp, we convert it
7041 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7042 $this->array_options[$key] = strtotime($this->array_options[$key]);
7043 }
7044 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7045 break;
7046 case 'link':
7047 $param_list = array_keys($attributeParam['options']);
7048 // 0 : ObjectName
7049 // 1 : classPath
7050 $InfoFieldList = explode(":", $param_list[0]);
7051 dol_include_once($InfoFieldList[1]);
7052 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7053 if ($value == '-1') { // -1 is key for no defined in combo list of objects
7054 $new_array_options[$key] = '';
7055 } elseif ($value) {
7056 $object = new $InfoFieldList[0]($this->db);
7057 '@phan-var-force CommonObject $object';
7058
7059 $objectId = 0;
7060
7061 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
7062 if (is_numeric($value)) {
7063 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
7064 } else {
7065 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
7066 }
7067
7068 $obj = $this->db->getRow($sqlFetchObject);
7069
7070 // getRow() returns an object on success, int 0 when the query succeeded but
7071 // returned no row, and false on SQL failure. Testing "!== false" let the 0
7072 // through as a success: $obj->rowid on an int is null, $res was set to 1 and
7073 // null was stored in the column while a success was reported.
7074 if (is_object($obj)) {
7075 $objectId = $obj->rowid;
7076 $res = 1;
7077 } else {
7078 $res = -1;
7079 }
7080
7081 if ($res > 0) {
7082 $new_array_options[$key] = $objectId;
7083 } else {
7084 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7085 return -1;
7086 }
7087 }
7088 } else {
7089 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7090 }
7091 break;
7092 case 'checkbox':
7093 case 'chkbxlst':
7094 if (is_array($this->array_options[$key])) {
7095 $new_array_options[$key] = implode(',', $this->array_options[$key]);
7096 } else {
7097 $new_array_options[$key] = $this->array_options[$key];
7098 }
7099 break;
7100 }
7101 }
7102
7103 // Set array $sqlColumnValues (SQL field name in extrafield table => value)
7104 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
7105
7106 foreach ($new_array_options as $key => $newValue) {
7107 $attributeKey = substr($key, 8); // Remove 'options_' prefix
7108 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7109 if ($attributeType === 'separate') {
7110 // separator extrafields don't have data per object so they don't have a comlumn in the database
7111 continue;
7112 }
7113 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
7114 // Add field of attribute
7115 if (! $geoDataType) {
7116 // not a geodata type
7117 if ($newValue != '') {
7118 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
7119 } else {
7120 $sqlColumnValues[$attributeKey] = 'null';
7121 }
7122 continue;
7123 }
7124 if (empty($newValue)) {
7125 $sqlColumnValues[$attributeKey] = 'null';
7126 continue;
7127 }
7128 if (preg_match('/error/i', $newValue)) {
7129 dol_syslog('Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request', LOG_WARNING);
7130 $sqlColumnValues[$attributeKey] = 'null';
7131 continue;
7132 }
7133
7134 // Geodata type: Text must be a WKT string. Examples:
7135 // - point => "POINT(15 20)"
7136 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7137 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7138 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7139 $sqlColumnValues[$key] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')";
7140 }
7141
7142 $this->db->begin();
7143
7144 $table_element = $this->table_element;
7145 if ($table_element == 'categorie') {
7146 $table_element = 'categories'; // For compatibility
7147 }
7148 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7149
7150 dol_syslog(get_class($this)."::insertExtraFields update or insert record line", LOG_DEBUG);
7151
7152 $linealreadyfound = 0;
7153 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7154 $resql = $this->db->query($sql);
7155 if ($resql) {
7156 $tmpobj = $this->db->fetch_object($resql);
7157 if ($tmpobj) {
7158 $linealreadyfound = $tmpobj->nb;
7159 }
7160 }
7161
7162 // if the extrafields row already exists for the object, we update it
7163 if ($linealreadyfound) {
7164 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7165 if (empty($sqlColumnValues)) {
7166 // No column of the target element to update. This happens when the object was created from
7167 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7168 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7169 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7170 // that would produce an invalid SQL statement.
7171 $this->db->commit();
7172 return 1;
7173 }
7174 $sqlColumnValueString = implode(
7175 ',',
7180 array_map(function ($key) use ($sqlColumnValues) {
7181 return "{$key} = {$sqlColumnValues[$key]}";
7182 }, array_keys($sqlColumnValues))
7183 );
7184 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7185 } else {
7186 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7187 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7188 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7189 if (isset($sqlColumnValues[$key])) {
7190 // extrafield value already provided: no need to add it
7191 continue;
7192 }
7193 // default value: empty string, except for 'int', 'double' and 'price'
7194 $sqlColumnValues[$key] = "''";
7195 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7196 $sqlColumnValues[$key] = 0;
7197 }
7198 }
7199 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7200 $sqlValues = implode(',', array_values($sqlColumnValues));
7201 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7202 }
7203
7204 $resql = $this->db->query($sql);
7205 if (!$resql) {
7206 $this->error = $this->db->lasterror();
7207 $error++;
7208 }
7209
7210 // Update also the user of last modification in parent table
7211 if (!$error && !empty($this->fields['fk_user_modif'])) {
7212 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7213 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7214 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7215 $this->db->query($sql);
7216 }
7217
7218 if (!$error && $trigger) {
7219 // Call trigger
7220 $this->context = array('extrafieldaddupdate' => 1);
7221 $result = $this->call_trigger($trigger, $userused);
7222 if ($result < 0) {
7223 $error++;
7224 }
7225 // End call trigger
7226 }
7227
7228 if ($error) {
7229 $this->db->rollback();
7230 return -1;
7231 } else {
7232 $this->db->commit();
7233 return 1;
7234 }
7235 }
7236
7248 public function insertExtraLanguages($trigger = '', $userused = null)
7249 {
7250 global $langs, $user;
7251
7252 if (empty($userused)) {
7253 $userused = $user;
7254 }
7255
7256 $error = 0;
7257
7258 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7259 return 0; // For avoid conflicts if trigger used
7260 }
7261
7262 if (is_array($this->array_languages)) {
7263 $new_array_languages = $this->array_languages;
7264
7265 foreach ($new_array_languages as $key => $value) {
7266 $attributeKey = $key;
7267 $attributeType = $this->fields[$attributeKey]['type'];
7268 $attributeLabel = $this->fields[$attributeKey]['label'];
7269
7270 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7271 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7272
7273 switch ($attributeType) {
7274 case 'int':
7275 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7276 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7277 return -1;
7278 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7279 $new_array_languages[$key] = null;
7280 }
7281 break;
7282 case 'double':
7283 $value = price2num((string) $value);
7284 if (!is_numeric($value) && $value != '') {
7285 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7286 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7287 return -1;
7288 } elseif ($value == '') {
7289 $new_array_languages[$key] = null;
7290 } else {
7291 $new_array_languages[$key] = $value;
7292 }
7293 break;
7294 /*case 'select': // Not required, we chose value='0' for undefined values
7295 if ($value=='-1')
7296 {
7297 $this->array_options[$key] = null;
7298 }
7299 break;*/
7300 }
7301 }
7302
7303 $this->db->begin();
7304
7305 $table_element = $this->table_element;
7306 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7307 $table_element = 'categories'; // For compatibility
7308 }
7309
7310 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7311
7312 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7313 foreach ($langcodearray as $langcode => $value) {
7314 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7315 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7316 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7317 $this->db->query($sql_del);
7318
7319 if ($value !== '') {
7320 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7321 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7322 $sql .= ")";
7323
7324 $resql = $this->db->query($sql);
7325 if (!$resql) {
7326 $this->error = $this->db->lasterror();
7327 $error++;
7328 break;
7329 }
7330 }
7331 }
7332 }
7333
7334 if (!$error && $trigger) {
7335 // Call trigger
7336 $this->context = array('extralanguagesaddupdate' => 1);
7337 $result = $this->call_trigger($trigger, $userused);
7338 if ($result < 0) {
7339 $error++;
7340 }
7341 // End call trigger
7342 }
7343
7344 if ($error) {
7345 $this->db->rollback();
7346 return -1;
7347 } else {
7348 $this->db->commit();
7349 return 1;
7350 }
7351 } else {
7352 return 0;
7353 }
7354 }
7355
7366 public function updateExtraField($key, $trigger = null, $userused = null)
7367 {
7368 global $langs, $user, $hookmanager;
7369
7370 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7371 return 0;
7372 }
7373
7374 if (empty($userused)) {
7375 $userused = $user;
7376 }
7377
7378 $error = 0;
7379
7380 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7381 // Check parameters
7382 $langs->load('admin');
7383 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7384 $extrafields = new ExtraFields($this->db);
7385 $extrafields->fetch_name_optionals_label($this->table_element);
7386 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7387 // Extrafield not defined for this object type: nothing to update
7388 return 0;
7389 }
7390 $value = $this->array_options["options_".$key];
7391
7392 $attributeKey = $key;
7393 $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
7394 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
7395 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
7396 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
7397 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7398 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
7399
7400 // Similar code than into insertExtraFields
7401 if ($attributeRequired) {
7402 $mandatorypb = false;
7403 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7404 $mandatorypb = true;
7405 }
7406 if ($this->array_options["options_".$key] === '') {
7407 $mandatorypb = true;
7408 }
7409 if ($mandatorypb) {
7410 $langs->load("errors");
7411 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7412 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7413 return -1;
7414 }
7415 }
7416
7417 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7418 $new_array_options = $this->array_options;
7419
7420 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7421 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7422 if (!empty($attrfieldcomputed)) {
7423 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7424 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7425 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7426
7427 $new_array_options["options_".$key] = $value;
7428
7429 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7430 } else {
7431 $new_array_options["options_".$key] = null;
7432
7433 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7434 }
7435 }
7436
7437 switch ($attributeType) {
7438 case 'int':
7439 if (!is_numeric($value) && $value != '') {
7440 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7441 return -1;
7442 } elseif ($value === '') {
7443 $new_array_options["options_".$key] = null;
7444
7445 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7446 }
7447 break;
7448 case 'price':
7449 case 'double':
7450 $value = price2num($value);
7451 if (!is_numeric($value) && $value != '') {
7452 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7453 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7454 return -1;
7455 } elseif ($value === '') {
7456 $value = null;
7457 }
7458 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7459 $new_array_options["options_".$key] = $value;
7460
7461 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7462 break;
7463 /*case 'select': // Not required, we chose value='0' for undefined values
7464 if ($value=='-1')
7465 {
7466 $new_array_options["options_".$key] = $value;
7467
7468 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7469 }
7470 break;*/
7471 case 'password':
7472 $algo = '';
7473 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7474 // If there is an encryption choice, we use it to encrypt data before insert
7475 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7476 $algo = reset($tmparrays);
7477 if ($algo != '') {
7478 //global $action; // $action may be 'create', 'update', 'update_extras'...
7479 //var_dump($action);
7480 //var_dump($this->oldcopy);exit;
7481 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7482 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
7483 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7484 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.
7485 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7486 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7487 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7488 } else {
7489 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7490 }
7491 } else {
7492 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7493 }
7494 } else {
7495 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7496 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7497 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7498 } else {
7499 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7500 }
7501 } else {
7502 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7503 }
7504 }
7505 } else {
7506 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7507 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7508 } else {
7509 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7510 }
7511 }
7512 } else {
7513 // No encryption
7514 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7515 }
7516 } else { // Common usage
7517 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7518 }
7519
7520 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7521 break;
7522 case 'date':
7523 case 'datetime':
7524 if (empty($this->array_options["options_".$key])) {
7525 $new_array_options["options_".$key] = null;
7526
7527 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7528 } else {
7529 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7530 }
7531 break;
7532 case 'datetimegmt':
7533 if (empty($this->array_options["options_".$key])) {
7534 $new_array_options["options_".$key] = null;
7535
7536 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7537 } else {
7538 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7539 }
7540 break;
7541 case 'boolean':
7542 if (empty($this->array_options["options_".$key])) {
7543 $new_array_options["options_".$key] = null;
7544
7545 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7546 }
7547 break;
7548 case 'link':
7549 if ($this->array_options["options_".$key] === '') {
7550 $new_array_options["options_".$key] = null;
7551
7552 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7553 }
7554 break;
7555 /*
7556 case 'link':
7557 $param_list = array_keys($attributeParam['options']);
7558 // 0 : ObjectName
7559 // 1 : classPath
7560 $InfoFieldList = explode(":", $param_list[0]);
7561 dol_include_once($InfoFieldList[1]);
7562 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7563 {
7564 if ($value == '-1') // -1 is key for no defined in combo list of objects
7565 {
7566 $new_array_options[$key] = '';
7567 } elseif ($value) {
7568 $object = new $InfoFieldList[0]($this->db);
7569 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7570 else $res = $object->fetch(0, $value); // For compatibility
7571
7572 if ($res > 0) $new_array_options[$key] = $object->id;
7573 else {
7574 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7575 $this->db->rollback();
7576 return -1;
7577 }
7578 }
7579 } else {
7580 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7581 }
7582 break;
7583 */
7584 case 'checkbox':
7585 case 'chkbxlst':
7586 $new_array_options = array();
7587 if (is_array($this->array_options["options_".$key])) {
7588 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7589 } else {
7590 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7591 }
7592
7593 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7594 break;
7595 }
7596
7597 $this->db->begin();
7598
7599 $linealreadyfound = 0;
7600
7601 // 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)
7602 $table_element = $this->table_element;
7603 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7604 $table_element = 'categories'; // For compatibility
7605 }
7606
7607 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7608 $resql = $this->db->query($sql);
7609 if ($resql) {
7610 $tmpobj = $this->db->fetch_object($resql);
7611 if ($tmpobj) {
7612 $linealreadyfound = $tmpobj->nb;
7613 }
7614 }
7615
7616 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7617 if ($linealreadyfound) {
7618 if ($this->array_options["options_".$key] === null) {
7619 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
7620 } else {
7621 // TODO What about if field is type int or float ($attributeType = price, int, ...) ?
7622 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7623 }
7624 $sql .= " WHERE fk_object = ".((int) $this->id);
7625
7626 $resql = $this->db->query($sql);
7627 if (!$resql) {
7628 $error++;
7629 $this->error = $this->db->lasterror();
7630 }
7631 } else {
7632 $result = $this->insertExtraFields('', $user);
7633 if ($result < 0) {
7634 $error++;
7635 }
7636 }
7637
7638 // Update also the user of last modification in parent table
7639 if (!$error && !empty($this->fields['fk_user_modif'])) {
7640 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
7641 $sql .= " SET fk_user_modif = ".(int) $userused->id;
7642 $sql .= " WHERE ".(empty($this->table_rowid) ? 'rowid' : $this->db->sanitize($this->table_rowid))." = ".((int) $this->id);
7643 $this->db->query($sql);
7644 }
7645
7646 if (!$error) {
7647 $parameters = array('key' => $key);
7648 global $action;
7649 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7650 if ($reshook < 0) {
7651 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7652 }
7653 }
7654
7655 if (!$error && $trigger) {
7656 // Call trigger
7657 $this->context = array('extrafieldupdate' => 1);
7658 $result = $this->call_trigger($trigger, $userused);
7659 if ($result < 0) {
7660 $error++;
7661 }
7662 // End call trigger
7663 }
7664
7665 if ($error) {
7666 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7667 $this->db->rollback();
7668 return -1;
7669 } else {
7670 $this->db->commit();
7671 return 1;
7672 }
7673 } else {
7674 return 0;
7675 }
7676 }
7677
7684 public function getExtraField($key)
7685 {
7686 return $this->array_options['options_'.$key] ?? null;
7687 }
7688
7696 public function setExtraField($key, $value)
7697 {
7698 $this->array_options['options_'.$key] = $value;
7699 }
7700
7711 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7712 {
7713 global $user;
7714
7715 if (empty($userused)) {
7716 $userused = $user;
7717 }
7718
7719 //$error = 0;
7720
7721 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7722 return 0; // For avoid conflicts if trigger used
7723 }
7724
7725 return 0;
7726 }
7727
7728
7743 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7744 {
7745 global $conf, $langs, $form;
7746
7747
7748 // 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...)
7749
7750 if (!is_object($form)) {
7751 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7752 $form = new Form($this->db);
7753 }
7754
7755 if (!empty($this->fields)) {
7756 $val = $this->fields[$key];
7757 }
7758
7759 // Validation tests and output
7760 $fieldValidationErrorMsg = '';
7761 $validationClass = '';
7762 $fieldValidationErrorMsg = $this->getFieldError($key);
7763 if (!empty($fieldValidationErrorMsg)) {
7764 $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
7765 } else {
7766 $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
7767 }
7768
7769 //$valuemultiselectinput = array();
7770 $out = '';
7771 $type = '';
7772 $isDependList = 0;
7773 $param = array();
7774 $param['options'] = array();
7775 $reg = array();
7776 // @phan-suppress-next-line PhanTypeMismatchProperty
7777 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7778 // Because we work on extrafields
7779 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7780 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7781 $type = 'link';
7782 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7783 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7784 $type = 'link';
7785 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7786 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7787 $type = 'link';
7788 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7789 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7790 $type = 'sellist';
7791 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7792 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7793 $type = 'sellist';
7794 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7795 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7796 $type = 'sellist';
7797 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7798 $param['options'] = array($reg[1] => 'N');
7799 $type = 'chkbxlst';
7800 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7801 $param['options'] = array();
7802 $type = 'varchar';
7803 $size = $reg[1];
7804 } elseif (preg_match('/varchar/', $val['type'])) {
7805 $param['options'] = array();
7806 $type = 'varchar';
7807 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7808 $param['options'] = array();
7809 $type = 'stars';
7810 $size = $reg[1];
7811 } else {
7812 $param['options'] = array();
7813 $type = $this->fields[$key]['type'];
7814 }
7815 //var_dump($type); var_dump($param['options']);
7816
7817 // Special case that force options and type ($type can be integer, varchar, ...)
7818 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7819 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7820 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7821 if (empty($this->fields[$key]['multiinput'])) {
7822 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7823 }
7824 }
7825
7826 $label = $this->fields[$key]['label'];
7827 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7828 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7829 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7830 // @phan-suppress-next-line PhanTypeMismatchProperty
7831 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7832 // @phan-suppress-next-line PhanTypeMismatchProperty
7833 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7834 // @phan-suppress-next-line PhanTypeMismatchProperty
7835 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7836 // @phan-suppress-next-line PhanTypeMismatchProperty
7837 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7838 // @phan-suppress-next-line PhanTypeMismatchProperty
7839 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7840
7841 // @phan-suppress-next-line PhanTypeMismatchProperty
7842 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7843 // @phan-suppress-next-line PhanTypeMismatchProperty
7844 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7845 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7846
7847 $objectid = $this->id;
7848
7849 if ($computed) {
7850 if (!preg_match('/^search_/', $keyprefix)) {
7851 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7852 } else {
7853 return '';
7854 }
7855 }
7856
7857 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7858 if (empty($morecss) && !empty($val['css'])) {
7859 $morecss = $val['css'];
7860 } elseif (empty($morecss)) {
7861 if ($type == 'date') {
7862 $morecss = 'minwidth100imp';
7863 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7864 $morecss = 'minwidth200imp';
7865 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7866 $morecss = 'maxwidth75';
7867 } elseif ($type == 'url') {
7868 $morecss = 'minwidth400';
7869 } elseif ($type == 'boolean') {
7870 $morecss = '';
7871 } else {
7872 if (is_numeric($size) && round((float) $size) < 12) {
7873 $morecss = 'minwidth100';
7874 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7875 $morecss = 'minwidth200';
7876 } else {
7877 $morecss = 'minwidth400';
7878 }
7879 }
7880 }
7881
7882 // Add validation state class
7883 if (!empty($validationClass)) {
7884 $morecss .= $validationClass;
7885 }
7886
7887 if (in_array($type, array('date'))) {
7888 $tmp = explode(',', $size);
7889 $newsize = $tmp[0];
7890 $showtime = 0;
7891
7892 // Do not show current date when field not required (see selectDate() method)
7893 if (!$required && $value == '') {
7894 $value = '-1';
7895 }
7896
7897 // TODO Must also support $moreparam
7898 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7899 } elseif (in_array($type, array('datetime'))) {
7900 $tmp = explode(',', $size);
7901 $newsize = $tmp[0];
7902 $showtime = 1;
7903
7904 // Do not show current date when field not required (see selectDate() method)
7905 if (!$required && $value == '') {
7906 $value = '-1';
7907 }
7908
7909 // TODO Must also support $moreparam
7910 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7911 } elseif (in_array($type, array('duration'))) {
7912 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7913 } elseif (in_array($type, array('int', 'integer'))) {
7914 $tmp = explode(',', $size);
7915 $newsize = $tmp[0];
7916 $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' : '').'>';
7917 } elseif (in_array($type, array('real'))) {
7918 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7919 } elseif (preg_match('/varchar/', (string) $type)) {
7920 $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' : '').'>';
7921 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7922 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7923 } elseif (preg_match('/^text/', (string) $type)) {
7924 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7925 if (!empty($param['options'])) {
7926 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7927 //var_dump($param['options']);
7928 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7929 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7930 $out .= "<script>";
7931 $out .= '
7932 function handlemultiinputdisabling(htmlname){
7933 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
7934 multiinput = $("#"+htmlname+"_multiinput");
7935 multiinput.find("option").each(function(){
7936 tmpval = $("#"+htmlname).val();
7937 tmpvalarray = tmpval.split("\n");
7938 valtotest = $(this).val();
7939 if(tmpvalarray.includes(valtotest)){
7940 $(this).prop("disabled",true);
7941 } else {
7942 if($(this).prop("disabled") == true){
7943 console.log(valtotest)
7944 $(this).prop("disabled", false);
7945 }
7946 }
7947 });
7948 }
7949
7950 $(document).ready(function () {
7951 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
7952 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
7953 tmpvalarray = tmpval.split(",");
7954 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
7955 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
7956 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
7957 if(tmpval == ""){
7958 tmpval = valtotest;
7959 } else {
7960 tmpval = tmpval + "\n" + valtotest;
7961 }
7962 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
7963 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7964 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
7965 } else {
7966 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
7967 }
7968 });
7969 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
7970 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7971 });
7972 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7973 })';
7974 $out .= "</script>";
7975 $value = str_replace(',', "\n", $value);
7976 }
7977 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7978 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
7979 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
7980 } else {
7981 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7982 }
7983 } elseif (preg_match('/^html/', (string) $type)) {
7984 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7985 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7986 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
7987 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
7988 } else {
7989 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7990 }
7991 } elseif ($type == 'boolean') {
7992 $checked = '';
7993 if (!empty($value)) {
7994 $checked = ' checked value="1" ';
7995 } else {
7996 $checked = ' value="1" ';
7997 }
7998 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
7999 } elseif ($type == 'price') {
8000 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8001 $value = price($value);
8002 }
8003 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
8004 } elseif ($type == 'stars') {
8005 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
8006 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
8007 $i = 1;
8008 while ($i <= $size) {
8009 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8010 $i++;
8011 }
8012 $out .= '</div>';
8013 $out .= '<script>
8014 jQuery(function($) { /* commonobject.class.php 1 */
8015 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
8016 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
8017 container.find(".star").each(function() {
8018 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8019 });
8020 container.find(".star").on("mouseover", function() {
8021 let selectedStar = $(this).data("value");
8022 container.find(".star").each(function() {
8023 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8024 });
8025 });
8026 container.on("mouseout", function() {
8027 container.find(".star").each(function() {
8028 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8029 });
8030 });
8031 container.find(".star").off("click").on("click", function() {
8032 selectedStars = $(this).data("value");
8033 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
8034 selectedStars = 0;
8035 }
8036 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
8037 container.find(".star").each(function() {
8038 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8039 });
8040 });
8041 });
8042 </script>';
8043 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
8044 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
8045 $value = price($value);
8046 }
8047 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
8048 } elseif ($type == 'select') { // combo list
8049 $out = '';
8050 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8051 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8052 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8053 }
8054
8055 $tmpselect = '';
8056 $nbchoice = 0;
8057
8058 foreach ($param['options'] as $keyb => $valb) {
8059 if ((string) $keyb == '') {
8060 continue;
8061 }
8062 if (strpos($valb, "|") !== false) {
8063 list($valb, $parent) = explode('|', $valb);
8064 }
8065 $nbchoice++;
8066 $tmpselect .= '<option value="'.$keyb.'"';
8067 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
8068 if (!empty($parent)) {
8069 $isDependList = 1;
8070 }
8071 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
8072 $tmpselect .= '>'.$langs->trans($valb).'</option>';
8073 }
8074
8075 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8076 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8077 $out .= '<option value="0">&nbsp;</option>';
8078 }
8079 $out .= $tmpselect;
8080 $out .= '</select>';
8081 } elseif ($type == 'sellist') {
8082 $out = '';
8083 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8084 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8085 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
8086 }
8087
8088 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
8089 if (is_array($param['options'])) {
8090 $tmpparamoptions = array_keys($param['options']);
8091 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8092
8093 $InfoFieldList = explode(":", $paramoptions[0], 5);
8094 // 0 : tableName
8095 // 1 : label field name
8096 // 2 : key fields name (if different of rowid)
8097 // optional parameters...
8098 // 3 : key field parent (for dependent lists). How this is used ?
8099 // 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".
8100 // 5 : string category type. This replace the filter.
8101 // 6 : ids categories list separated by comma for category root. This replace the filter.
8102 // 7 : sort field
8103
8104 // If there is filter
8105 if (! empty($InfoFieldList[4])) {
8106 $pos = 0;
8107 $parenthesisopen = 0;
8108 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8109 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8110 $parenthesisopen++;
8111 }
8112 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8113 $parenthesisopen--;
8114 }
8115 $pos++;
8116 }
8117 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8118 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8119 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8120 $InfoFieldList[4] = $tmpbefore;
8121 if ($tmpafter !== '') {
8122 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8123 }
8124 //var_dump($InfoFieldList);
8125
8126 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8127 $reg = array();
8128 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8129 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8130 }
8131
8132 //var_dump($InfoFieldList);
8133 }
8134
8135 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8136
8137 $parentName = '';
8138 $parentField = '';
8139
8140 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8141
8142 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8143 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8144 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8145 } else {
8146 $keyList = $InfoFieldList[2].' as rowid';
8147 }
8148 }
8149 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8150 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8151 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8152 $keyList .= ', main.'.$parentField;
8153 } else {
8154 $keyList .= ', '.$parentField;
8155 }
8156 }
8157
8158 $filter_categorie = false;
8159 if (count($InfoFieldList) > 5) {
8160 if ($InfoFieldList[0] == 'categorie') {
8161 $filter_categorie = true;
8162 }
8163 }
8164
8165 if (!$filter_categorie) {
8166 $fields_label = isset($InfoFieldList[1]) ? explode('|', $InfoFieldList[1]) : array();
8167 if (!empty($fields_label)) {
8168 $keyList .= ', ';
8169 $keyList .= implode(', ', $fields_label);
8170 }
8171
8172 $sqlwhere = '';
8173 $sql = "SELECT " . $keyList;
8174 $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
8175
8176 if (!empty($InfoFieldList[4])) {
8177 // can use SELECT request
8178 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8179 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8180 }
8181
8182 // current object id can be use into filter
8183 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8184 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8185 } else {
8186 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8187 }
8188
8189 // We have to join on extrafield table
8190 $errstr = '';
8191 if (strpos($InfoFieldList[4], 'extra') !== false) {
8192 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8193 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8194 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8195 } else {
8196 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8197 }
8198 } else {
8199 $sqlwhere .= ' WHERE 1=1';
8200 }
8201
8202 // Add Usf filter on second line
8203 /*
8204 if ($Usf) {
8205 $errorstr = '';
8206 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8207 if (!$errorstr) {
8208 $sqlwhere .= $sqlusf;
8209 } else {
8210 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8211 }
8212 }
8213 */
8214
8215 // Some tables may have field, some other not. For the moment we disable it.
8216 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8217 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8218 }
8219 $sql .= $sqlwhere;
8220
8221 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8222 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8223 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8224 } else {
8225 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8226 }
8227 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8228 // print $sql;
8229
8230 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8231 $resql = $this->db->query($sql);
8232 if ($resql) {
8233 $out .= '<option value="0">&nbsp;</option>';
8234 $num = $this->db->num_rows($resql);
8235 $i = 0;
8236 while ($i < $num) {
8237 $labeltoshow = '';
8238 $obj = $this->db->fetch_object($resql);
8239
8240 // Several field into label (eq table:code|libelle:rowid)
8241 $notrans = false;
8242 $fields_label = explode('|', $InfoFieldList[1]);
8243 if (count($fields_label) > 1) {
8244 $notrans = true;
8245 foreach ($fields_label as $field_toshow) {
8246 $labeltoshow .= $obj->$field_toshow . ' ';
8247 }
8248 } else {
8249 $labeltoshow = $obj->{$InfoFieldList[1]};
8250 }
8251 $labeltoshow = dol_trunc($labeltoshow, 45);
8252
8253 if ($value == $obj->rowid) {
8254 foreach ($fields_label as $field_toshow) {
8255 $translabel = $langs->trans($obj->$field_toshow);
8256 if ($translabel != $obj->$field_toshow) {
8257 $labeltoshow = dol_trunc($translabel) . ' ';
8258 } else {
8259 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8260 }
8261 }
8262 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8263 } else {
8264 if (!$notrans) {
8265 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8266 if ($translabel != $obj->{$InfoFieldList[1]}) {
8267 $labeltoshow = dol_trunc($translabel, 18);
8268 } else {
8269 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8270 }
8271 }
8272 if (empty($labeltoshow)) {
8273 $labeltoshow = '(not defined)';
8274 }
8275 if ($value == $obj->rowid) {
8276 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8277 }
8278
8279 if (!empty($InfoFieldList[3]) && $parentField) {
8280 $parent = $parentName . ':' . $obj->{$parentField};
8281 $isDependList = 1;
8282 }
8283
8284 $out .= '<option value="' . $obj->rowid . '"';
8285 $out .= ($value == $obj->rowid ? ' selected' : '');
8286 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8287 $out .= '>' . $labeltoshow . '</option>';
8288 }
8289
8290 $i++;
8291 }
8292 $this->db->free($resql);
8293 } else {
8294 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8295 }
8296 } else {
8297 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8298 $categcode = $InfoFieldList[5];
8299 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8300 $tmpcategory = new Categorie($this->db);
8301 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8302 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8303 }
8304
8305 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8306 $out .= '<option value="0">&nbsp;</option>';
8307 foreach ($data as $data_key => $data_value) {
8308 $out .= '<option value="' . $data_key . '"';
8309 $out .= ($value == $data_key ? ' selected' : '');
8310 $out .= '>' . $data_value . '</option>';
8311 }
8312 }
8313 }
8314 $out .= '</select>';
8315 } elseif ($type == 'checkbox') {
8316 $value_arr = explode(',', $value);
8317 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8318 } elseif ($type == 'radio') {
8319 $out = '';
8320 foreach ($param['options'] as $keyopt => $valopt) {
8321 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8322 $out .= ' value="'.$keyopt.'"';
8323 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8324 $out .= ($value == $keyopt ? 'checked' : '');
8325 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8326 }
8327 } elseif ($type == 'chkbxlst') {
8328 if (is_array($value)) {
8329 $value_arr = $value;
8330 } else {
8331 $value_arr = explode(',', $value);
8332 }
8333
8334 if (is_array($param['options'])) {
8335 $tmpparamoptions = array_keys($param['options']);
8336 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8337
8338 $InfoFieldList = explode(":", $paramoptions[0], 5);
8339 // 0 : tableName
8340 // 1 : label field name
8341 // 2 : key fields name (if different of rowid)
8342 // optional parameters...
8343 // 3 : key field parent (for dependent lists). How this is used ?
8344 // 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".
8345 // 5 : string category type. This replace the filter.
8346 // 6 : ids categories list separated by comma for category root. This replace the filter.
8347 // 7 : sort field
8348
8349 // If there is a filter
8350 if (! empty($InfoFieldList[4])) {
8351 $pos = 0;
8352 $parenthesisopen = 0;
8353 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8354 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8355 $parenthesisopen++;
8356 }
8357 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8358 $parenthesisopen--;
8359 }
8360 $pos++;
8361 }
8362 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8363 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8364 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8365 $InfoFieldList[4] = $tmpbefore;
8366 if ($tmpafter !== '') {
8367 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8368 }
8369
8370 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8371 $reg = array();
8372 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8373 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8374 }
8375
8376 //var_dump($InfoFieldList);
8377 }
8378
8379 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8380
8381 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8382
8383 $parentName = '';
8384 $parentField = '';
8385
8386 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8387
8388 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8389 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8390 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8391 } else {
8392 $keyList = $InfoFieldList[2].' as rowid';
8393 }
8394 }
8395 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8396 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8397 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8398 $keyList .= ', main.'.$parentField;
8399 } else {
8400 $keyList .= ', '.$parentField;
8401 }
8402 }
8403
8404 $filter_categorie = false;
8405 if (count($InfoFieldList) > 5) {
8406 if ($InfoFieldList[0] == 'categorie') {
8407 $filter_categorie = true;
8408 }
8409 }
8410
8411 // Common filter
8412 if (!$filter_categorie) {
8413 $fields_label = explode('|', $InfoFieldList[1]);
8414 if (is_array($fields_label)) {
8415 $keyList .= ', ';
8416 $keyList .= implode(', ', $fields_label);
8417 }
8418
8419 $sqlwhere = '';
8420 $sql = "SELECT " . $keyList;
8421 $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8422
8423 if (!empty($InfoFieldList[4])) {
8424 // can use SELECT request
8425 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8426 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8427 }
8428
8429 // current object id can be use into filter
8430 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8431 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8432 } else {
8433 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8434 }
8435
8436 // We have to join on extrafield table
8437 $errstr = '';
8438 if (strpos($InfoFieldList[4], 'extra') !== false) {
8439 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8440 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8441 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8442 } else {
8443 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8444 }
8445 } else {
8446 $sqlwhere .= ' WHERE 1=1';
8447 }
8448
8449 // Add Usf filter on second line
8450 /*
8451 if ($Usf) {
8452 $errorstr = '';
8453 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8454 if (!$errorstr) {
8455 $sqlwhere .= $sqlusf;
8456 } else {
8457 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8458 }
8459 }
8460 */
8461
8462 // Some tables may have field, some other not. For the moment we disable it.
8463 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8464 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8465 }
8466 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8467 // print $sql;
8468
8469 $sql .= $sqlwhere;
8470
8471 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8472
8473 $resql = $this->db->query($sql);
8474 if ($resql) {
8475 $num = $this->db->num_rows($resql);
8476 $i = 0;
8477
8478 $data = array();
8479
8480 while ($i < $num) {
8481 $labeltoshow = '';
8482 $obj = $this->db->fetch_object($resql);
8483
8484 $notrans = false;
8485 // Several field into label (eq table:code|libelle:rowid)
8486 $fields_label = explode('|', $InfoFieldList[1]);
8487 if (count($fields_label) > 1) {
8488 $notrans = true;
8489 foreach ($fields_label as $field_toshow) {
8490 $labeltoshow .= $obj->$field_toshow . ' ';
8491 }
8492 } else {
8493 $labeltoshow = $obj->{$InfoFieldList[1]};
8494 }
8495 $labeltoshow = dol_trunc($labeltoshow, 45);
8496
8497 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8498 foreach ($fields_label as $field_toshow) {
8499 $translabel = $langs->trans($obj->$field_toshow);
8500 if ($translabel != $obj->$field_toshow) {
8501 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8502 } else {
8503 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8504 }
8505 }
8506
8507 $data[$obj->rowid] = $labeltoshow;
8508 } else {
8509 if (!$notrans) {
8510 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8511 if ($translabel != $obj->{$InfoFieldList[1]}) {
8512 $labeltoshow = dol_trunc($translabel, 18);
8513 } else {
8514 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8515 }
8516 }
8517 if (empty($labeltoshow)) {
8518 $labeltoshow = '(not defined)';
8519 }
8520
8521 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8522 $data[$obj->rowid] = $labeltoshow;
8523 }
8524
8525 if (!empty($InfoFieldList[3]) && $parentField) {
8526 $parent = $parentName . ':' . $obj->{$parentField};
8527 $isDependList = 1;
8528 }
8529
8530 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8531 }
8532
8533 $i++;
8534 }
8535 $this->db->free($resql);
8536
8537 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8538 } else {
8539 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8540 }
8541 } else {
8542 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8543 $categcode = $InfoFieldList[5];
8544 if (is_numeric($categcode)) { // deprecated: must use the category code instead of id. For backward compatibility.
8545 $tmpcategory = new Categorie($this->db);
8546 $MAP_ID_TO_CODE = array_flip($tmpcategory->MAP_ID);
8547 $categcode = $MAP_ID_TO_CODE[(int) $categcode];
8548 }
8549
8550 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8551 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8552 }
8553 }
8554 } elseif ($type == 'link') {
8555 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8556 // Filter can contains some ':' inside.
8557 $param_list = array_keys($param['options']);
8558 $param_list_array = explode(':', $param_list[0], 4);
8559
8560 $showempty = (($required && $default != '') ? 0 : 1);
8561
8562 if (!preg_match('/search_/', $keyprefix)) {
8563 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8564 // @phan-suppress-next-line PhanTypeMismatchProperty
8565 if (!empty($this->fields[$key]['picto'])) {
8566 $morecss .= ' widthcentpercentminusxx';
8567 } else {
8568 $morecss .= ' widthcentpercentminusx';
8569 }
8570 } else {
8571 // @phan-suppress-next-line PhanTypeMismatchProperty
8572 if (!empty($this->fields[$key]['picto'])) {
8573 $morecss .= ' widthcentpercentminusx';
8574 }
8575 }
8576 }
8577
8578 // $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.
8579
8580 // $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.
8581
8582 $objectfield = $val;
8583
8584 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8585 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8586
8587 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8588 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8589 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8590 list($class, $classfile) = explode(':', $param_list[0]);
8591 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8592 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8593 } else {
8594 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8595 }
8596 $paramforthenewlink = '';
8597 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8598 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8599 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8600 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8601 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8602 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8603 $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>';
8604 }
8605 }
8606 } elseif ($type == 'password') {
8607 // If prefix is 'search_', field is used as a filter, we use a common text field.
8608 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8609 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8610 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8611 } else {
8612 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8613 }
8614 } elseif ($type == 'array') {
8615 $newval = $val;
8616 $newval['type'] = 'varchar(256)';
8617
8618 $out = '';
8619 if (!empty($value)) {
8620 foreach ($value as $option) {
8621 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8622 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8623 }
8624 }
8625 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8626
8627 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8628 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8629
8630 if (!empty($conf->use_javascript_ajax)) {
8631 $out .= '
8632 <script nonce="'.getNonce().'">
8633 $(document).ready(function() {
8634 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8635 $("'.dol_escape_js($newInput).'").insertBefore(this);
8636 });
8637
8638 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8639 $(this).parent().remove();
8640 });
8641 });
8642 </script>';
8643 }
8644 }
8645 if (!empty($hidden)) {
8646 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8647 }
8648
8649 if ($isDependList == 1) {
8650 $out .= $this->getJSListDependancies('_common');
8651 }
8652 /* Add comments
8653 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8654 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8655 */
8656
8657 // Display error message for field
8658 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8659 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8660 }
8661
8662 return $out;
8663 }
8664
8678 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8679 {
8680 global $conf, $langs, $form;
8681
8682 // 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...)
8683
8684 if (!is_object($form)) {
8685 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8686 $form = new Form($this->db);
8687 }
8688
8689 //$label = empty($val['label']) ? '' : $val['label'];
8690 $type = empty($val['type']) ? '' : $val['type'];
8691 $size = empty($val['css']) ? '' : $val['css'];
8692 $reg = array();
8693
8694 // Convert var to be able to share same code than showOutputField of extrafields
8695 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8696 $type = 'varchar'; // convert varchar(xx) int varchar
8697 $size = $reg[1];
8698 } elseif (preg_match('/varchar/', $type)) {
8699 $type = 'varchar'; // convert varchar(xx) int varchar
8700 }
8701 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8702 // @phan-suppress-next-line PhanTypeMismatchProperty
8703 if (empty($this->fields[$key]['multiinput'])) {
8704 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8705 }
8706 }
8707 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8708 $type = 'link';
8709 }
8710
8711 $default = empty($val['default']) ? '' : $val['default'];
8712 $computed = empty($val['computed']) ? '' : $val['computed'];
8713 $unique = empty($val['unique']) ? '' : $val['unique'];
8714 $required = empty($val['required']) ? '' : $val['required'];
8715 $param = array();
8716 $param['options'] = array();
8717
8718 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8719 $param['options'] = $val['arrayofkeyval'];
8720 }
8721 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8722 $type = 'link';
8723 $stringforoptions = $reg[1].':'.$reg[2];
8724 // Special case: Force addition of getnomurlparam1 to -1 for users
8725 if ($reg[1] == 'User') {
8726 $stringforoptions .= ':#getnomurlparam1=-1';
8727 }
8728 $param['options'] = array($stringforoptions => $stringforoptions);
8729 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8730 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8731 $type = 'sellist';
8732 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8733 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8734 $type = 'sellist';
8735 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8736 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8737 $type = 'sellist';
8738 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8739 $param['options'] = array($reg[1] => 'N');
8740 $type = 'chkbxlst';
8741 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8742 $param['options'] = array();
8743 $type = 'stars';
8744 $size = $reg[1];
8745 }
8746
8747 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8748 $list = (empty($val['list']) ? '' : $val['list']);
8749 $help = (empty($val['help']) ? '' : $val['help']);
8750 $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)
8751
8752 if ($hidden) {
8753 return '';
8754 }
8755
8756 // If field is a computed field, value must become result of compute
8757 if ($computed) {
8758 // Make the eval of compute string
8759 //var_dump($computed);
8760 $value = dol_eval((string) $computed, 1, 0, '2');
8761 }
8762
8763 if (empty($morecss)) {
8764 if ($type == 'date') {
8765 $morecss = 'minwidth100imp';
8766 } elseif ($type == 'datetime' || $type == 'timestamp') {
8767 $morecss = 'minwidth200imp';
8768 } elseif (in_array($type, array('int', 'double', 'price'))) {
8769 $morecss = 'maxwidth75';
8770 } elseif ($type == 'url') {
8771 $morecss = 'minwidth400';
8772 } elseif ($type == 'boolean') {
8773 $morecss = '';
8774 } else {
8775 if (is_numeric($size) && round((float) $size) < 12) {
8776 $morecss = 'minwidth100';
8777 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8778 $morecss = 'minwidth200';
8779 } else {
8780 $morecss = 'minwidth400';
8781 }
8782 }
8783 }
8784
8785 // Format output value differently according to properties of field
8786 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8787 // @phan-suppress-next-line PhanTypeMismatchProperty
8788 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.
8789 $value = $this->getNomUrl(1, '', 0, '', 1);
8790 }
8791 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8792 $value = $this->getLibStatut(3);
8793 } elseif ($type == 'date') {
8794 if (!empty($value)) {
8795 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8796 } else {
8797 $value = '';
8798 }
8799 } elseif ($type == 'datetime' || $type == 'timestamp') {
8800 if (!empty($value)) {
8801 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8802 } else {
8803 $value = '';
8804 }
8805 } elseif ($type == 'duration') {
8806 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8807 if (!is_null($value) && $value !== '') {
8808 $value = convertSecondToTime((int) $value, 'allhourmin');
8809 }
8810 } elseif ($type == 'double' || $type == 'real') {
8811 if (!is_null($value) && $value !== '') {
8812 $value = price($value);
8813 }
8814 } elseif ($type == 'boolean') {
8815 $checked = '';
8816 if (!empty($value)) {
8817 $checked = ' checked ';
8818 }
8819 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8820 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8821 } else {
8822 $value = yn($value ? 1 : 0);
8823 }
8824 } elseif ($type == 'mail' || $type == 'email') {
8825 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8826 } elseif ($type == 'url') {
8827 $value = dol_print_url((string) $value, '_blank', 32, 1);
8828 } elseif ($type == 'phone') {
8829 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8830 } elseif ($type == 'ip') {
8831 $value = dol_print_ip((string) $value, 0);
8832 } elseif ($type == 'stars') {
8833 $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 : '').'>';
8834 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8835 $i = 1;
8836 while ($i <= $size) {
8837 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8838 $i++;
8839 }
8840 $value .= '</div>';
8841 $value .= '<script>
8842 $(document).ready(function() { /* commonobject.class.php 2 */
8843 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8844 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8845 container.find(".star").each(function() {
8846 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8847 });
8848 container.find(".star").on("mouseover", function() {
8849 let selectedStar = $(this).data("value");
8850 container.find(".star").each(function() {
8851 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8852 });
8853 });
8854 container.on("mouseout", function() {
8855 container.find(".star").each(function() {
8856 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8857 });
8858 });
8859 container.find(".star").off("click").on("click", function() {
8860 selectedStars = $(this).data("value");
8861 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8862 selectedStars = 0;
8863 }
8864 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8865 container.find(".star").each(function() {
8866 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8867 });
8868 $.ajax({
8869 url: "ajax/'.$this->element.'.php",
8870 method: "POST",
8871 data: {
8872 objectId: "'.$this->id.'",
8873 field: "'.$keyprefix.$key.$keysuffix.'",
8874 value: selectedStars,
8875 token: "'.newToken().'"
8876 },
8877 success: function(response) {
8878 var res = JSON.parse(response);
8879 console[res.status === "success" ? "log" : "error"](res.message);
8880 },
8881 error: function(xhr, status, error) {
8882 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8883 }
8884 });
8885 });
8886 });
8887 </script>';
8888 } elseif ($type == 'price') {
8889 if (!is_null($value) && $value !== '') {
8890 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8891 }
8892 } elseif ($type == 'select') {
8893 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8894 if (strpos($value, "|") !== false) {
8895 $value = $langs->trans(explode('|', $value)[0]);
8896 } elseif (! is_numeric($value)) {
8897 $value = $langs->trans($value);
8898 }
8899 } elseif ($type == 'sellist') {
8900 $param_list = array_keys($param['options']);
8901 $InfoFieldList = explode(":", $param_list[0]);
8902
8903 $selectkey = "rowid";
8904 $keyList = 'rowid';
8905
8906 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8907 $selectkey = $InfoFieldList[2];
8908 $keyList = $InfoFieldList[2].' as rowid';
8909 }
8910
8911 $fields_label = explode('|', $InfoFieldList[1]);
8912 if (is_array($fields_label)) {
8913 $keyList .= ', ';
8914 $keyList .= implode(', ', $fields_label);
8915 }
8916
8917 $filter_categorie = false;
8918 if (count($InfoFieldList) > 5) {
8919 if ($InfoFieldList[0] == 'categorie') {
8920 $filter_categorie = true;
8921 }
8922 }
8923
8924 $sql = "SELECT ".$keyList;
8925 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8926 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8927 $sql .= ' as main';
8928 }
8929 if ($selectkey == 'rowid' && empty($value)) {
8930 $sql .= " WHERE ".$selectkey." = 0";
8931 } elseif ($selectkey == 'rowid') {
8932 $sql .= " WHERE ".$selectkey." = ".((int) $value);
8933 } else {
8934 $sql .= " WHERE ".$selectkey." = '".$this->db->escape((string) $value)."'";
8935 }
8936
8937 //$sql.= ' AND entity = '.$conf->entity;
8938
8939 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
8940 $resql = $this->db->query($sql);
8941 if ($resql) {
8942 if (!$filter_categorie) {
8943 $value = ''; // value was used, so now we reset it to use it to build final output
8944 $numrows = $this->db->num_rows($resql);
8945 if ($numrows) {
8946 $obj = $this->db->fetch_object($resql);
8947
8948 // Several field into label (eq table:code|libelle:rowid)
8949 $fields_label = explode('|', $InfoFieldList[1]);
8950
8951 if (is_array($fields_label) && count($fields_label) > 1) {
8952 foreach ($fields_label as $field_toshow) {
8953 $translabel = '';
8954 if (!empty($obj->$field_toshow)) {
8955 $translabel = $langs->trans($obj->$field_toshow);
8956 }
8957 if ($translabel != $field_toshow) {
8958 $value .= dol_trunc($translabel, 18) . ' ';
8959 } else {
8960 $value .= $obj->$field_toshow . ' ';
8961 }
8962 }
8963 } else {
8964 $translabel = '';
8965 if (!empty($obj->{$InfoFieldList[1]})) {
8966 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8967 }
8968 if ($translabel != $obj->{$InfoFieldList[1]}) {
8969 $value = dol_trunc($translabel, 18);
8970 } else {
8971 $value = $obj->{$InfoFieldList[1]};
8972 }
8973 }
8974 }
8975 } else {
8976 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8977
8978 $toprint = array();
8979 $obj = $this->db->fetch_object($resql);
8980 $c = new Categorie($this->db);
8981 $c->fetch($obj->rowid);
8982 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8983 foreach ($ways as $way) {
8984 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8985 }
8986 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8987 }
8988 } else {
8989 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8990 }
8991 } elseif ($type == 'radio') {
8992 $value = $param['options'][(string) $value];
8993 } elseif ($type == 'checkbox') {
8994 $value_arr = explode(',', (string) $value);
8995 $value = '';
8996 if (is_array($value_arr) && count($value_arr) > 0) {
8997 $toprint = array();
8998 foreach ($value_arr as $keyval => $valueval) {
8999 if (!empty($valueval)) {
9000 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
9001 }
9002 }
9003 if (!empty($toprint)) {
9004 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9005 }
9006 }
9007 } elseif ($type == 'chkbxlst') {
9008 $value_arr = (isset($value) ? explode(',', $value) : array());
9009
9010 $param_list = array_keys($param['options']);
9011 $InfoFieldList = explode(":", $param_list[0]);
9012
9013 $selectkey = "rowid";
9014 $keyList = 'rowid';
9015
9016 if (count($InfoFieldList) >= 3) {
9017 $selectkey = $InfoFieldList[2];
9018 $keyList = $InfoFieldList[2].' as rowid';
9019 }
9020
9021 $fields_label = explode('|', $InfoFieldList[1]);
9022 if (is_array($fields_label)) {
9023 $keyList .= ', ';
9024 $keyList .= implode(', ', $fields_label);
9025 }
9026
9027 $filter_categorie = false;
9028 if (count($InfoFieldList) > 5) {
9029 if ($InfoFieldList[0] == 'categorie') {
9030 $filter_categorie = true;
9031 }
9032 }
9033
9034 $sql = "SELECT ".$keyList;
9035 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
9036 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
9037 $sql .= ' as main';
9038 }
9039 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
9040 // $sql.= ' AND entity = '.$conf->entity;
9041
9042 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
9043 $resql = $this->db->query($sql);
9044 if ($resql) {
9045 if (!$filter_categorie) {
9046 $value = ''; // value was used, so now we reset it to use it to build final output
9047 $toprint = array();
9048 while ($obj = $this->db->fetch_object($resql)) {
9049 // Several field into label (eq table:code|libelle:rowid)
9050 $fields_label = explode('|', $InfoFieldList[1]);
9051 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9052 if (is_array($fields_label) && count($fields_label) > 1) {
9053 foreach ($fields_label as $field_toshow) {
9054 $translabel = '';
9055 if (!empty($obj->$field_toshow)) {
9056 $translabel = $langs->trans($obj->$field_toshow);
9057 }
9058 if ($translabel != $field_toshow) {
9059 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9060 } else {
9061 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
9062 }
9063 }
9064 } else {
9065 $translabel = '';
9066 if (!empty($obj->{$InfoFieldList[1]})) {
9067 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
9068 }
9069 if ($translabel != $obj->{$InfoFieldList[1]}) {
9070 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
9071 } else {
9072 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
9073 }
9074 }
9075 }
9076 }
9077 } else {
9078 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9079
9080 $toprint = array();
9081 while ($obj = $this->db->fetch_object($resql)) {
9082 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
9083 $c = new Categorie($this->db);
9084 $c->fetch($obj->rowid);
9085 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9086 foreach ($ways as $way) {
9087 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
9088 }
9089 }
9090 }
9091 }
9092 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
9093 } else {
9094 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
9095 }
9096 } elseif ($type == 'link') {
9097 $out = '';
9098
9099 // only if something to display (perf)
9100 if ($value) {
9101 $param_list = array_keys($param['options']);
9102 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
9103 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
9104
9105 $InfoFieldList = explode(":", $param_list[0]);
9106
9107 $classname = $InfoFieldList[0];
9108 $classpath = $InfoFieldList[1];
9109
9110 // Set $getnomurlparam1 et getnomurlparam2
9111 $getnomurlparam = 3;
9112 $getnomurlparam2 = '';
9113 $regtmp = array();
9114 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
9115 $getnomurlparam = $regtmp[1];
9116 }
9117 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
9118 $getnomurlparam2 = $regtmp[1];
9119 }
9120
9121 if (!empty($classpath)) {
9122 dol_include_once($InfoFieldList[1]);
9123
9124 if ($classname && !class_exists($classname)) {
9125 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9126 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
9127 $element_prop = getElementProperties($classname);
9128 if ($element_prop) {
9129 $classname = $element_prop['classname'];
9130 }
9131 }
9132
9133
9134 if ($classname && class_exists($classname)) {
9135 $object = new $classname($this->db);
9136 '@phan-var-force CommonObject $object';
9137 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
9138 '@phan-var-force Product $object';
9139 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
9140 } else {
9141 $result = $object->fetch($value);
9142 }
9143 if ($result > 0) {
9144 if ($object->element === 'product') {
9145 '@phan-var-force Product $object';
9146 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
9147 if (isset($val['get_name_url_params'])) {
9148 $get_name_url_params = explode(':', $val['get_name_url_params']);
9149 if (!empty($get_name_url_params)) {
9150 $param_num_max = count($get_name_url_param_arr) - 1;
9151 foreach ($get_name_url_params as $param_num => $param_value) {
9152 if ($param_num > $param_num_max) {
9153 break;
9154 }
9155 $get_name_url_param_arr[$param_num] = $param_value;
9156 }
9157 }
9158 }
9159
9163 $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]);
9164 } else {
9165 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9166 }
9167 } else {
9168 $value = '';
9169 }
9170 }
9171 } else {
9172 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9173 return 'Error bad setup of extrafield';
9174 }
9175 } else {
9176 $value = '';
9177 }
9178 } elseif ($type == 'password') {
9179 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9180 //$value = preg_replace('/./i', '*', $value);
9181 } elseif ($type == 'array') {
9182 if (is_array($value)) {
9183 $value = implode('<br>', $value);
9184 } else {
9185 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9186 return 'Error unexpected result from code evaluation';
9187 }
9188 } else { // text|html|varchar
9189 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9190 $value = str_replace(',', "\n", $value);
9191 }
9192 $value = dol_htmlentitiesbr((string) $value);
9193 }
9194
9195 //print $type.'-'.$size.'-'.$value;
9196 $out = $value;
9197
9198 return is_null($out) ? '' : $out;
9199 }
9200
9207 public function clearFieldError($fieldKey)
9208 {
9209 $this->error = '';
9210 unset($this->validateFieldsErrors[$fieldKey]);
9211 }
9212
9220 public function setFieldError($fieldKey, $msg = '')
9221 {
9222 global $langs;
9223 if (empty($msg)) {
9224 $msg = $langs->trans("UnknownError");
9225 }
9226
9227 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9228 }
9229
9236 public function getFieldError($fieldKey)
9237 {
9238 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9239 return $this->validateFieldsErrors[$fieldKey];
9240 }
9241 return '';
9242 }
9243
9252 public function validateField($fields, $fieldKey, $fieldValue)
9253 {
9254 global $langs;
9255
9256 if (!class_exists('Validate')) {
9257 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9258 }
9259
9260 $this->clearFieldError($fieldKey);
9261
9262 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9263 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9264 return false;
9265 }
9266
9267 $val = $fields[$fieldKey];
9268
9269 $param = array();
9270 $param['options'] = array();
9271 $type = $val['type'];
9272
9273 $required = false;
9274 if (isset($val['notnull']) && $val['notnull'] === 1) {
9275 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9276 $required = true;
9277 }
9278
9279 $maxSize = 0;
9280 $minSize = 0;
9281
9282 //
9283 // PREPARE Elements
9284 //
9285 $reg = array();
9286
9287 // Convert var to be able to share same code than showOutputField of extrafields
9288 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9289 $type = 'varchar'; // convert varchar(xx) int varchar
9290 $maxSize = (int) $reg[1];
9291 } elseif (preg_match('/varchar/', $type)) {
9292 $type = 'varchar'; // convert varchar(xx) int varchar
9293 }
9294
9295 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9296 $type = 'select';
9297 }
9298
9299 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9300 $type = 'link';
9301 }
9302
9303 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9304 $param['options'] = $val['arrayofkeyval'];
9305 }
9306
9307 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9308 $type = 'link';
9309 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9310 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9311 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9312 $type = 'sellist';
9313 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9314 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9315 $type = 'sellist';
9316 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9317 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9318 $type = 'sellist';
9319 }
9320
9321 //
9322 // TEST Value
9323 //
9324
9325 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9326 $validate = new Validate($this->db, $langs);
9327
9328
9329 // little trick : to perform tests with good performances sort tests by quick to low
9330
9331 //
9332 // COMMON TESTS
9333 //
9334
9335 // Required test and empty value
9336 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9337 $this->setFieldError($fieldKey, $validate->error);
9338 return false;
9339 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9340 // if no value sent and the field is not mandatory, no need to perform tests
9341 return true;
9342 }
9343
9344 // MAX Size test
9345 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9346 $this->setFieldError($fieldKey, $validate->error);
9347 return false;
9348 }
9349
9350 // MIN Size test
9351 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9352 $this->setFieldError($fieldKey, $validate->error);
9353 return false;
9354 }
9355
9356 //
9357 // TESTS for TYPE
9358 //
9359
9360 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9361 if (!$validate->isTimestamp($fieldValue)) {
9362 $this->setFieldError($fieldKey, $validate->error);
9363 return false;
9364 } else {
9365 return true;
9366 }
9367 } elseif ($type == 'duration') {
9368 if (!$validate->isDuration($fieldValue)) {
9369 $this->setFieldError($fieldKey, $validate->error);
9370 return false;
9371 } else {
9372 return true;
9373 }
9374 } elseif (in_array($type, array('double', 'real', 'price'))) {
9375 // is numeric
9376 if (!$validate->isNumeric($fieldValue)) {
9377 $this->setFieldError($fieldKey, $validate->error);
9378 return false;
9379 } else {
9380 return true;
9381 }
9382 } elseif ($type == 'boolean') {
9383 if (!$validate->isBool($fieldValue)) {
9384 $this->setFieldError($fieldKey, $validate->error);
9385 return false;
9386 } else {
9387 return true;
9388 }
9389 } elseif ($type == 'mail' || $type == 'email') {
9390 if (!$validate->isEmail($fieldValue)) {
9391 $this->setFieldError($fieldKey, $validate->error);
9392 return false;
9393 }
9394 } elseif ($type == 'url') {
9395 if (!$validate->isUrl($fieldValue)) {
9396 $this->setFieldError($fieldKey, $validate->error);
9397 return false;
9398 } else {
9399 return true;
9400 }
9401 } elseif ($type == 'phone') {
9402 if (!$validate->isPhone($fieldValue)) {
9403 $this->setFieldError($fieldKey, $validate->error);
9404 return false;
9405 } else {
9406 return true;
9407 }
9408 } elseif ($type == 'select' || $type == 'radio') {
9409 if (!isset($param['options'][$fieldValue])) {
9410 $this->error = $langs->trans('RequireValidValue');
9411 return false;
9412 } else {
9413 return true;
9414 }
9415 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9416 $param_list = array_keys($param['options']);
9417 $InfoFieldList = explode(":", $param_list[0]);
9418 $value_arr = explode(',', $fieldValue);
9419 $value_arr = array_map(array($this->db, 'escape'), $value_arr);
9420
9421 $selectkey = "rowid";
9422 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9423 $selectkey = $InfoFieldList[2];
9424 }
9425
9426 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9427 $this->setFieldError($fieldKey, $validate->error);
9428 return false;
9429 } else {
9430 return true;
9431 }
9432 } elseif ($type == 'link') {
9433 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9434 $InfoFieldList = explode(":", $param_list[0]);
9435 $classname = $InfoFieldList[0];
9436 $classpath = $InfoFieldList[1];
9437 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9438 $lastIsFetchableError = $validate->error;
9439
9440 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9441 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9442 return true;
9443 }
9444
9445 $this->setFieldError($fieldKey, $lastIsFetchableError);
9446 return false;
9447 } else {
9448 return true;
9449 }
9450 }
9451
9452 // if no test failed all is ok
9453 return true;
9454 }
9455
9469 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9470 {
9471 global $db, $conf, $langs, $action, $form, $hookmanager;
9472
9473 if (!is_object($form)) {
9474 $form = new Form($db);
9475 }
9476 if (!is_object($extrafields)) {
9477 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9478 return 'Bad parameter extrafields for showOptionals';
9479 }
9480 if (!is_array($extrafields->attributes[$this->table_element])) {
9481 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9482 }
9483
9484 $out = '';
9485
9486 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9487 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9488 $hookResPrint = $hookmanager->resPrint;
9489
9490 if (empty($reshook)) {
9491 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) {
9492 $out .= "\n";
9493 $out .= '<!-- commonobject:showOptionals --> ';
9494 $out .= "\n";
9495
9496 $nbofextrafieldsshown = 0;
9497 $e = 0; // var to manage the modulo (odd/even)
9498
9499 $lastseparatorkeyfound = '';
9500 $extrafields_collapse_num = '';
9501 $extrafields_collapse_num_old = '';
9502 $i = 0;
9503
9504 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9505 $i++;
9506
9507 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9508 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9509 continue;
9510 }
9511
9512 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9513 $enabled = 1;
9514 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9515 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9516 }
9517 if (empty($enabled)) {
9518 continue;
9519 }
9520
9521 $visibility = 1;
9522 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9523 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9524 }
9525
9526 $perms = 1;
9527 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9528 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9529 }
9530
9531 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9532 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9533 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9534 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9535 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9536 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9537 $ef_name = 'options_' . $key;
9538 $ef_value = $this->array_options[$ef_name] ?? '';
9539 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . dolPrintHTMLForAttribute($ef_value) . '" />' . "\n"; // If trouble to preserve content, we can try dol_htmlentities() instead, but real solution is to remove completely the hidden field (see previous TODO).
9540 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9541 } elseif ($mode == 'view' && empty($visibility)) {
9542 continue;
9543 }
9544 if (empty($perms)) {
9545 continue;
9546 }
9547
9548 // Load language if required
9549 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9550 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9551 }
9552
9553 $colspan = 0;
9554 $value = null;
9555 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9556 if (array_key_exists('cols', $params)) {
9557 $colspan = $params['cols'];
9558 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9559 $reg = array();
9560 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9561 $colspan = $reg[1];
9562 } else {
9563 $colspan = $params['colspan'];
9564 }
9565 }
9566 }
9567 $colspan = intval($colspan);
9568
9569 switch ($mode) {
9570 case "view":
9571 $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
9572 break;
9573 case "create":
9574 case "edit":
9575 // We get the value of property found with GETPOST so it takes into account:
9576 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9577 $check = 'alphanohtml';
9578 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9579 $check = 'restricthtml';
9580 }
9581 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9582 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9583 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9584 if (is_array($getposttemp)) {
9585 // $getposttemp is an array but following code expects a comma separated string
9586 $value = implode(",", $getposttemp);
9587 } else {
9588 $value = $getposttemp;
9589 }
9590 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9591 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9592 } else {
9593 $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.
9594 }
9595 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9596 break;
9597 }
9598
9599 $nbofextrafieldsshown++;
9600
9601 // Output value of the current field
9602 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9603 $extrafields_collapse_num = $key;
9604 /*
9605 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9606 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9607 $extrafield_param_list = array_keys($extrafield_param['options']);
9608
9609 if (count($extrafield_param_list) > 0) {
9610 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9611
9612 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9613 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9614 $extrafields_collapse_num = $key;
9615 }
9616 }
9617 }
9618 */
9619
9620 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9621 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9622
9623 $lastseparatorkeyfound = $key;
9624 } else {
9625 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9626
9627 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9628 $csstyle = '';
9629 if (is_array($params) && count($params) > 0) {
9630 if (array_key_exists('class', $params)) {
9631 $class .= $params['class'].' ';
9632 }
9633 if (array_key_exists('style', $params)) {
9634 $csstyle = $params['style'];
9635 }
9636 }
9637
9638 // add html5 elements
9639 $domData = ' data-element="extrafield"';
9640 $domData .= ' data-targetelement="'.$this->element.'"';
9641 $domData .= ' data-targetid="'.$this->id.'"';
9642
9643 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9644 if ($display_type == 'card') {
9645 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9646 $colspan = 0;
9647 }
9648
9649 if ($action == 'selectlines') {
9650 $colspan++;
9651 }
9652 }
9653
9654 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9655 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9656 $force_values_on_change_company = (
9657 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9658 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9659 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9660 );
9661 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9662 $value = $this->array_options['options_'.$key] ?? $value;
9663 }
9664
9665 // Convert date into timestamp format (value in memory must be a timestamp)
9666 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9667 $datenotinstring = null;
9668 if (array_key_exists('options_'.$key, $this->array_options)) {
9669 $datenotinstring = $this->array_options['options_'.$key];
9670 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9671 $datenotinstring = $this->db->jdate($datenotinstring);
9672 }
9673 }
9674 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9675 $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;
9676 }
9677 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9678 $datenotinstring = null;
9679 if (array_key_exists('options_'.$key, $this->array_options)) {
9680 $datenotinstring = $this->array_options['options_'.$key];
9681 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9682 $datenotinstring = $this->db->jdate($datenotinstring);
9683 }
9684 }
9685 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9686 $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;
9687 }
9688 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9689 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9690 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9691 $value = price2num($value);
9692 } elseif (isset($this->array_options['options_'.$key])) {
9693 $value = $this->array_options['options_'.$key];
9694 }
9695 }
9696
9697 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9698 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9699 if ($action == 'create' || $mode == 'create') {
9700 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9701 }
9702 }
9703
9704 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9705 if ($action == 'create') {
9706 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9707 }
9708 }
9709
9710
9711 $labeltoshow = $langs->trans($label);
9712 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9713 if ($display_type == 'card') {
9714 $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.' >';
9715 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9716 $out .= '<td></td>';
9717 }
9718 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9719 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9720 $out .= ' tdtop';
9721 }
9722 } elseif ($display_type == 'line') {
9723 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9724 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9725 }
9726 //$out .= "titlefield";
9727 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9728 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9729 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9730 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9731 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9732 $out .= ' fieldrequired';
9733 }
9734 }
9735 $out .= '">';
9736 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9737 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9738 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9739 } else {
9740 $out .= $labeltoshow;
9741 }
9742 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9743 $out .= '&nbsp;<span style="color: red">*</span>';
9744 }
9745 } else {
9746 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9747 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9748 } else {
9749 $out .= $labeltoshow;
9750 }
9751 }
9752
9753 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9754
9755 // Second column
9756 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9757 if ($display_type == 'card') {
9758 // 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
9759 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9760 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9761 $out .= '>';
9762 } elseif ($display_type == 'line') {
9763 $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].'">';
9764 }
9765
9766 switch ($mode) {
9767 case "view":
9768 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9769 break;
9770 case "create":
9771 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9772 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9773 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9774 }
9775 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9776 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9777 break;
9778 case "edit":
9779 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9780 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9781 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9782 }
9783 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9784 break;
9785 }
9786
9787 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9788 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9789 $e++;
9790 }
9791 }
9792 $out .= "\n";
9793 // Add code to manage list depending on others
9794 if (!empty($conf->use_javascript_ajax)) {
9795 $out .= $this->getJSListDependancies();
9796 }
9797
9798 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9799
9800 if (empty($nbofextrafieldsshown)) {
9801 $out = '';
9802 }
9803 }
9804 }
9805
9806 $out .= $hookResPrint;
9807
9808 return $out;
9809 }
9810
9815 public function getJSListDependancies($type = '_extra')
9816 {
9817 $out = '
9818 <script nonce="'.getNonce().'">
9819 jQuery(document).ready(function() {
9820 function showOptions'.$type.'(child_list, parent_list, orig_select)
9821 {
9822 var val = $("select[name=\""+parent_list+"\"]").val();
9823 var parentVal = parent_list + ":" + val;
9824 if(typeof val == "string"){
9825 if(val != "") {
9826 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9827 $("select[name=\""+child_list+"\"] option[parent]").remove();
9828 $("select[name=\""+child_list+"\"]").append(options);
9829 } else {
9830 var options = orig_select.find("option[parent]").clone();
9831 $("select[name=\""+child_list+"\"] option[parent]").remove();
9832 $("select[name=\""+child_list+"\"]").append(options);
9833 }
9834 } else if(val > 0) {
9835 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9836 $("select[name=\""+child_list+"\"] option[parent]").remove();
9837 $("select[name=\""+child_list+"\"]").append(options);
9838 } else {
9839 var options = orig_select.find("option[parent]").clone();
9840 $("select[name=\""+child_list+"\"] option[parent]").remove();
9841 $("select[name=\""+child_list+"\"]").append(options);
9842 }
9843 }
9844 function setListDependencies'.$type.'() {
9845 jQuery("select option[parent]").parent().each(function() {
9846 var orig_select = {};
9847 var child_list = $(this).attr("name");
9848 orig_select[child_list] = $(this).clone();
9849 var parent = $(this).find("option[parent]:first").attr("parent");
9850 var infos = parent.split(":");
9851 var parent_list = infos[0];
9852
9853 //Hide daughters lists
9854 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9855 $("#"+child_list).hide();
9856 //Show mother lists
9857 } else if ($("#"+parent_list).val() != 0){
9858 $("#"+parent_list).show();
9859 }
9860 //Show the child list if the parent list value is selected
9861 $("select[name=\""+parent_list+"\"]").click(function() {
9862 if ($(this).val() != 0){
9863 $("#"+child_list).show()
9864 }
9865 });
9866
9867 //When we change parent list
9868 $("select[name=\""+parent_list+"\"]").change(function() {
9869 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9870 //Select the value 0 on child list after a change on the parent list
9871 $("#"+child_list).val(0).trigger("change");
9872 //Hide child lists if the parent value is set to 0
9873 if ($(this).val() == 0){
9874 $("#"+child_list).hide();
9875 }
9876 });
9877 });
9878 }
9879
9880 setListDependencies'.$type.'();
9881 });
9882 </script>'."\n";
9883 return $out;
9884 }
9885
9891 public function getRights()
9892 {
9893 global $user;
9894
9895 $module = empty($this->module) ? '' : $this->module;
9896 $element = $this->element;
9897
9898 if ($element == 'facturerec') {
9899 $element = 'facture';
9900 } elseif ($element == 'invoice_supplier_rec') {
9901 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9902 } elseif ($module && $user->hasRight($module, $element)) {
9903 // for modules built with ModuleBuilder
9904 return $user->hasRight($module, $element);
9905 }
9906
9907 return isset($user->rights->$element) ? $user->rights->$element : null;
9908 }
9909
9922 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9923 {
9924 global $hookmanager;
9925
9926 $parameters = array(
9927 'origin_id' => $origin_id,
9928 'dest_id' => $dest_id,
9929 'tables' => $tables,
9930 );
9931 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
9932 if ($reshook) {
9933 return true; // replacement code
9934 } elseif ($reshook < 0) {
9935 return $ignoreerrors === 1; // failure
9936 } // reshook = 0 => execute normal code
9937
9938 foreach ($tables as $table) {
9939 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
9940
9941 if (!$dbs->query($sql)) {
9942 if ($ignoreerrors) {
9943 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.
9944 }
9945 //$this->errors = $db->lasterror();
9946 return false;
9947 }
9948 }
9949
9950 return true;
9951 }
9952
9965 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9966 {
9967 foreach ($tables as $table) {
9968 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
9969
9970 if (!$dbs->query($sql)) {
9971 if ($ignoreerrors) {
9972 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.
9973 }
9974 //$this->errors = $db->lasterror();
9975 return false;
9976 }
9977 }
9978
9979 return true;
9980 }
9981
9994 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
9995 {
9996 global $conf;
9997
9998 $buyPrice = 0;
9999
10000 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
10001 // When ForceBuyingPriceIfNull is set
10002 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
10003 } else {
10004 // Get cost price for margin calculation
10005 if (!empty($fk_product) && $fk_product > 0) {
10006 $result = 0;
10007 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
10008 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10009 $product = new Product($this->db);
10010 $result = $product->fetch($fk_product);
10011 if ($result <= 0) {
10012 $this->errors[] = 'ErrorProductIdDoesNotExists';
10013 return -1;
10014 }
10015 if ($product->cost_price > 0) {
10016 $buyPrice = $product->cost_price;
10017 } elseif ($product->pmp > 0) {
10018 $buyPrice = $product->pmp;
10019 }
10020 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
10021 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
10022 $product = new Product($this->db);
10023 $result = $product->fetch($fk_product);
10024 if ($result <= 0) {
10025 $this->errors[] = 'ErrorProductIdDoesNotExists';
10026 return -1;
10027 }
10028 if ($product->pmp > 0) {
10029 $buyPrice = $product->pmp;
10030 }
10031 }
10032
10033 if (empty($buyPrice) && in_array(getDolGlobalString('MARGIN_TYPE'), array('1', 'pmp', 'costprice'))) {
10034 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
10035 $productFournisseur = new ProductFournisseur($this->db);
10036 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
10037 $buyPrice = $productFournisseur->fourn_unitprice;
10038 } elseif ($result < 0) {
10039 $this->errors[] = $productFournisseur->error;
10040 return -2;
10041 }
10042 }
10043 }
10044 }
10045
10046 return (float) $buyPrice;
10047 }
10048
10056 public function getDataToShowPhoto($modulepart, $imagesize)
10057 {
10058 // See getDataToShowPhoto() implemented by Product for example.
10059 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
10060 }
10061
10062
10063 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10083 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')
10084 {
10085 // phpcs:enable
10086 global $user, $langs;
10087
10088 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
10089 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
10090
10091 $sortfield = 'position_name';
10092 $sortorder = 'asc';
10093
10094 $dir = $sdir.'/';
10095 $pdir = '/';
10096
10097 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10098 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
10099
10100 // For backward compatibility
10101 if ($modulepart == 'product') {
10102 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
10103 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10104 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10105 }
10106 }
10107 if ($modulepart == 'category') {
10108 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10109 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
10110 }
10111
10112 // Defined relative dir to DOL_DATA_ROOT
10113 $relativedir = '';
10114 if ($dir) {
10115 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
10116 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
10117 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
10118 }
10119
10120 $dirthumb = $dir.'thumbs/';
10121 $pdirthumb = $pdir.'thumbs/';
10122
10123 $return = '<!-- Photo -->'."\n";
10124 $nbphoto = 0;
10125
10126 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
10127
10128 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
10129 {
10130 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
10131 $filearray=array_merge($filearray, $filearrayold);
10132 }*/
10133
10134 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
10135 '@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';
10136
10137 if (count($filearray)) {
10138 if ($sortfield && $sortorder) {
10139 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
10140 }
10141
10142 foreach ($filearray as $key => $val) {
10143 $photo = '';
10144 $file = $val['name'];
10145
10146 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
10147 if (image_format_supported($file) >= 0) {
10148 $nbphoto++;
10149 $photo = $file;
10150 $viewfilename = $file;
10151
10152 if ($size == 1 || $size == 'small') { // Format vignette
10153 // Find name of thumb file
10154 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
10155 if (!dol_is_file($dirthumb.$photo_vignette)) {
10156 // The thumb does not exists, so we will use the original file
10157 $dirthumb = $dir;
10158 $pdirthumb = $pdir;
10159 $photo_vignette = basename($file);
10160 }
10161
10162 // Get filesize of original file
10163 $imgarray = dol_getImageSize($dir.$photo);
10164
10165 if ($nbbyrow > 0) {
10166 if ($nbphoto == 1) {
10167 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10168 }
10169
10170 if ($nbphoto % $nbbyrow == 1) {
10171 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10172 }
10173 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10174 } elseif ($nbbyrow < 0) {
10175 $return .= '<div class="inline-block">'."\n";
10176 }
10177
10178 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10179 if (empty($nolink)) {
10180 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10181 if ($urladvanced) {
10182 $return .= '<a href="'.$urladvanced.'">';
10183 } else {
10184 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10185 }
10186 }
10187
10188 // Show image (width height=$maxHeight)
10189 // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
10190 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10191 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10192 if ($overwritetitle) {
10193 if (is_numeric($overwritetitle)) {
10194 $alt = '';
10195 } else {
10196 $alt = $overwritetitle;
10197 }
10198 }
10199 if (empty($cache) && !empty($val['label'])) {
10200 // label is md5 of file
10201 // use it in url to say we want to cache this version of the file
10202 $cache = $val['label'];
10203 }
10204 if ($usesharelink) {
10205 if (array_key_exists('share', $val) && $val['share']) {
10206 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10207 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10208 $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).'">';
10209 } else {
10210 $return .= '<!-- Show original file -->';
10211 $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).'">';
10212 }
10213 } else {
10214 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10215 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10216 }
10217 } else {
10218 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10219 $return .= '<!-- Show thumb -->';
10220 $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).'">';
10221 } else {
10222 $return .= '<!-- Show original file -->';
10223 $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).'">';
10224 }
10225 }
10226
10227 if (empty($nolink)) {
10228 $return .= '</a>';
10229 }
10230
10231 if ($showfilename) {
10232 $return .= '<br>'.$viewfilename;
10233 }
10234 if ($showaction) {
10235 $return .= '<br>';
10236 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10237 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10238 $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>';
10239 }
10240 // Special case for product
10241 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10242 // Link to resize
10243 $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; ';
10244
10245 // Link to delete
10246 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10247 $return .= img_delete().'</a>';
10248 }
10249 }
10250 $return .= "\n";
10251
10252 if ($nbbyrow > 0) {
10253 $return .= '</td>';
10254 if (($nbphoto % $nbbyrow) == 0) {
10255 $return .= '</tr>';
10256 }
10257 } elseif ($nbbyrow < 0) {
10258 $return .= '</div>'."\n";
10259 }
10260 }
10261
10262 if (empty($size)) { // Format origine
10263 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10264
10265 if ($showfilename) {
10266 $return .= '<br>'.$viewfilename;
10267 }
10268 if ($showaction) {
10269 // Special case for product
10270 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10271 // Link to resize
10272 $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; ';
10273
10274 // Link to delete
10275 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10276 $return .= img_delete().'</a>';
10277 }
10278 }
10279 }
10280
10281 // On continue ou on arrete de boucler ?
10282 if ($nbmax && $nbphoto >= $nbmax) {
10283 break;
10284 }
10285 }
10286 }
10287
10288 if ($size == 1 || $size == 'small') {
10289 if ($nbbyrow > 0) {
10290 // Ferme tableau
10291 while ($nbphoto % $nbbyrow) {
10292 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10293 $nbphoto++;
10294 }
10295
10296 if ($nbphoto) {
10297 $return .= '</table>';
10298 }
10299 }
10300 }
10301 }
10302
10303 $this->nbphoto = $nbphoto;
10304
10305 return $return;
10306 }
10307
10308
10315 protected function isArray($info)
10316 {
10317 if (is_array($info)) {
10318 if (isset($info['type']) && $info['type'] == 'array') {
10319 return true;
10320 } else {
10321 return false;
10322 }
10323 }
10324 return false;
10325 }
10326
10333 public function isDate($info)
10334 {
10335 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10336 return true;
10337 }
10338 return false;
10339 }
10340
10347 public function isDuration($info)
10348 {
10349 if (is_array($info)) {
10350 if (isset($info['type']) && ($info['type'] == 'duration')) {
10351 return true;
10352 } else {
10353 return false;
10354 }
10355 } else {
10356 return false;
10357 }
10358 }
10359
10366 public function isInt($info)
10367 {
10368 if (is_array($info)) {
10369 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10370 return true;
10371 } else {
10372 return false;
10373 }
10374 } else {
10375 return false;
10376 }
10377 }
10378
10385 public function isFloat($info)
10386 {
10387 if (is_array($info)) {
10388 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10389 return true;
10390 } else {
10391 return false;
10392 }
10393 }
10394 return false;
10395 }
10396
10403 public function isText($info)
10404 {
10405 if (is_array($info)) {
10406 if (isset($info['type']) && $info['type'] == 'text') {
10407 return true;
10408 } else {
10409 return false;
10410 }
10411 }
10412 return false;
10413 }
10414
10421 protected function canBeNull($info)
10422 {
10423 if (is_array($info)) {
10424 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10425 return true;
10426 } else {
10427 return false;
10428 }
10429 }
10430 return true;
10431 }
10432
10439 protected function isForcedToNullIfZero($info)
10440 {
10441 if (is_array($info)) {
10442 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10443 return true;
10444 } else {
10445 return false;
10446 }
10447 }
10448 return false;
10449 }
10450
10457 protected function isIndex($info)
10458 {
10459 if (is_array($info)) {
10460 if (array_key_exists('index', $info) && $info['index'] == true) {
10461 return true;
10462 } else {
10463 return false;
10464 }
10465 }
10466 return false;
10467 }
10468
10469
10478 protected function setSaveQuery()
10479 {
10480 global $conf;
10481
10482 $queryarray = array();
10483 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10484 // Depending on field type ('datetime', ...)
10485 if ($this->isDate($info)) {
10486 if (empty($this->{$field})) {
10487 $queryarray[$field] = null;
10488 } else {
10489 $queryarray[$field] = $this->db->idate($this->{$field});
10490 }
10491 } elseif ($this->isDuration($info)) {
10492 // $this->{$field} may be null, '', 0, '0', 123, '123'
10493 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10494 if (!isset($this->{$field})) {
10495 if (!empty($info['default'])) {
10496 $queryarray[$field] = $info['default'];
10497 } else {
10498 $queryarray[$field] = 0;
10499 }
10500 } else {
10501 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10502 }
10503 } else {
10504 $queryarray[$field] = null;
10505 }
10506 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10507 if ($field == 'entity' && is_null($this->{$field})) {
10508 $queryarray[$field] = ((int) $conf->entity);
10509 } else {
10510 // $this->{$field} may be null, '', 0, '0', 123, '123'
10511 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10512 if (!isset($this->{$field})) {
10513 $queryarray[$field] = 0;
10514 } elseif ($this->isInt($info)) {
10515 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10516 } elseif ($this->isFloat($info)) {
10517 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10518 }
10519 } else {
10520 $queryarray[$field] = null;
10521 }
10522 }
10523 } else {
10524 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10525 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10526 $queryarray[$field] = $this->{$field};
10527 }
10528
10529 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10530 unset($queryarray[$field]);
10531 }
10532 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10533 $queryarray[$field] = null; // May force 0 to null
10534 }
10535 }
10536
10537 return $queryarray;
10538 }
10539
10546 public function setVarsFromFetchObj(&$obj)
10547 {
10548 global $db;
10549
10550 foreach ($this->fields as $field => $info) {
10551 if ($this->isDate($info)) {
10552 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') {
10553 $this->$field = '';
10554 } else {
10555 $this->$field = $db->jdate($obj->$field);
10556 }
10557 } elseif ($this->isInt($info)) {
10558 if ($field == 'rowid') {
10559 $this->id = (int) $obj->$field;
10560 } else {
10561 if ($this->isForcedToNullIfZero($info)) {
10562 if (empty($obj->$field)) {
10563 $this->$field = null;
10564 } else {
10565 $this->$field = (int) $obj->$field;
10566 }
10567 } else {
10568 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10569 $this->$field = (int) $obj->$field;
10570 } else {
10571 $this->$field = null;
10572 }
10573 }
10574 }
10575 } elseif ($this->isFloat($info)) {
10576 if ($this->isForcedToNullIfZero($info)) {
10577 if (empty($obj->$field)) {
10578 $this->$field = null;
10579 } else {
10580 $this->$field = (float) $obj->$field;
10581 }
10582 } else {
10583 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10584 $this->$field = (float) $obj->$field;
10585 } else {
10586 $this->$field = null;
10587 }
10588 }
10589 } else {
10590 $this->$field = isset($obj->$field) ? $obj->$field : null;
10591 }
10592 }
10593
10594 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10595 if (!isset($this->fields['ref']) && isset($this->id)) {
10596 $this->ref = (string) $this->id;
10597 }
10598 }
10599
10604 public function emtpyObjectVars()
10605 {
10606 foreach ($this->fields as $field => $arr) {
10607 $this->$field = null;
10608 }
10609 }
10610
10618 public function getFieldList($alias = '', $excludefields = array())
10619 {
10620 $keys = array_keys($this->fields);
10621 if (!empty($alias)) {
10622 $keys_with_alias = array();
10623 foreach ($keys as $fieldname) {
10624 if (!empty($excludefields)) {
10625 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10626 continue;
10627 }
10628 }
10629 $keys_with_alias[] = $alias . '.' . $fieldname;
10630 }
10631 return implode(', ', $keys_with_alias);
10632 } else {
10633 return implode(', ', $keys);
10634 }
10635 }
10636
10644 protected function quote($value, $fieldsentry)
10645 {
10646 if (is_null($value)) {
10647 return 'NULL';
10648 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10649 return price2num((string) $value);
10650 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10651 return (int) $value;
10652 } elseif ($fieldsentry['type'] == 'boolean') {
10653 if ($value) {
10654 return 'true';
10655 } else {
10656 return 'false';
10657 }
10658 } else {
10659 return "'".$this->db->escape($value)."'";
10660 }
10661 }
10662
10663
10671 public function createCommon(User $user, $notrigger = 0)
10672 {
10673 global $conf;
10674 //global $langs; // Should be able to work with $langs loaded
10675
10676 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10677
10678 $error = 0;
10679
10680 $now = dol_now();
10681
10682 $fieldvalues = $this->setSaveQuery();
10683
10684 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10685
10686 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10687 $fieldvalues['date_creation'] = $this->db->idate($now);
10688 $this->date_creation = $this->db->idate($now);
10689 }
10690 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10691 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10692 $fieldvalues['fk_user_creat'] = $user->id;
10693 $this->fk_user_creat = $user->id;
10694 }
10695 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10696 $fieldvalues['user_creation_id'] = $user->id;
10697 $this->user_creation_id = $user->id;
10698 }
10699 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10700 // @phan-suppress-next-line PhanUndeclaredProperty
10701 $tmparray = dol_hash($this->pass, '0', 0, 1);
10702 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10703 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10704 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10705 }
10706 }
10707 if (array_key_exists('ref', $fieldvalues)) {
10708 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10709 }
10710 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
10711 $fieldvalues['tms'] = $this->db->idate($now);
10712 }
10713
10714 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10715
10716 $keys = array();
10717 $values = array(); // Array to store string forged for SQL syntax
10718 foreach ($fieldvalues as $k => $v) {
10719 $keys[$k] = $k;
10720 $value = $this->fields[$k];
10721 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10722 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10723 }
10724
10725 // Clean and check mandatory
10726 foreach ($keys as $key) {
10727 if (!isset($this->fields[$key])) {
10728 continue;
10729 }
10730 $key_fields = $this->fields[$key];
10731
10732 // If field is an implicit foreign key field (so type = 'integer:...')
10733 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10734 $values[$key] = '';
10735 }
10736 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10737 $values[$key] = '';
10738 }
10739
10740 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10741 $error++;
10742 global $langs;
10743 if (empty($langs)) {
10744 require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
10745 $langs = new Translate('', $conf);
10746 $langs->setDefaultLang();
10747 $langs->load("errors");
10748 }
10749 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10750 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10751 }
10752
10753 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10754 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && array_key_exists('default', $key_fields) && !is_null($key_fields['default'])) {
10755 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10756 }
10757
10758 // If field is an implicit foreign key field (so type = 'integer:...')
10759 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10760 if (isset($key_fields['default'])) {
10761 $values[$key] = ((int) $key_fields['default']);
10762 } else {
10763 $values[$key] = 'null';
10764 }
10765 }
10766 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10767 $values[$key] = 'null';
10768 }
10769 }
10770
10771 if ($error) {
10772 return -1;
10773 }
10774
10775 $this->db->begin();
10776
10777 if (!$error) {
10778 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10779 $sql .= " (".implode(", ", $keys).')';
10780 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10781
10782 $res = $this->db->query($sql);
10783 if (!$res) {
10784 $error++;
10785 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10786 $this->errors[] = "ErrorRefAlreadyExists";
10787 } else {
10788 $this->errors[] = $this->db->lasterror();
10789 }
10790 }
10791 }
10792
10793 if (!$error) {
10794 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10795 }
10796
10797 // If we have a field ref with a default value of (PROV)
10798 if (!$error) {
10799 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10800 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)') {
10801 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10802 $resqlupdate = $this->db->query($sql);
10803
10804 if ($resqlupdate === false) {
10805 $error++;
10806 $this->errors[] = $this->db->lasterror();
10807 } else {
10808 $this->ref = '(PROV'.$this->id.')';
10809 }
10810 }
10811 }
10812
10813 // Create extrafields
10814 if (!$error) {
10815 $result = $this->insertExtraFields();
10816 if ($result < 0) {
10817 $error++;
10818 }
10819 }
10820
10821 // Create lines
10822 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10823 foreach ($this->lines as $line) {
10824 $keyforparent = $this->fk_element;
10825 $line->$keyforparent = $this->id;
10826
10827 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10828 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10829 if (!is_object($line)) {
10830 $line = (object) $line;
10831 }
10832
10833 $result = 0;
10834 if (method_exists($line, 'insert')) {
10835 $result = $line->insert($user, 1);
10836 } elseif (method_exists($line, 'create')) {
10837 $result = $line->create($user, 1);
10838 }
10839 if ($result < 0) {
10840 $this->error = $line->error;
10841 $this->db->rollback();
10842 return -1;
10843 }
10844 }
10845 }
10846
10847 // Triggers
10848 if (!$error && !$notrigger) {
10849 // Call triggers
10850 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10851 if ($result < 0) {
10852 $error++;
10853 }
10854 // End call triggers
10855 }
10856
10857 // Commit or rollback
10858 if ($error) {
10859 $this->db->rollback();
10860 return -1;
10861 } else {
10862 $this->db->commit();
10863 return $this->id;
10864 }
10865 }
10866
10867
10877 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
10878 {
10879 if (empty($id) && empty($ref) && empty($morewhere)) {
10880 return -1;
10881 }
10882
10883 $fieldlist = $this->getFieldList('t');
10884 if (empty($fieldlist)) {
10885 return 0;
10886 }
10887
10888 $sql = "SELECT ".$fieldlist;
10889 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
10890
10891 if (!empty($id)) {
10892 $sql .= ' WHERE t.rowid = '.((int) $id);
10893 } elseif (!empty($ref)) {
10894 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
10895 } else {
10896 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
10897 }
10898 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
10899 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
10900 }
10901 if ($morewhere) {
10902 $sql .= $morewhere;
10903 }
10904 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
10905
10906 $res = $this->db->query($sql);
10907 if ($res) {
10908 $obj = $this->db->fetch_object($res);
10909 if ($obj) {
10910 $this->setVarsFromFetchObj($obj);
10911
10912 // Retrieve all extrafield
10913 // fetch optionals attributes and labels
10914 if (empty($noextrafields)) {
10915 $result = $this->fetch_optionals();
10916 if ($result < 0) {
10917 $this->error = $this->db->lasterror();
10918 $this->errors[] = $this->error;
10919 return -4;
10920 }
10921 }
10922
10923 return $this->id;
10924 } else {
10925 return 0;
10926 }
10927 } else {
10928 $this->error = $this->db->lasterror();
10929 $this->errors[] = $this->error;
10930 return -1;
10931 }
10932 }
10933
10941 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
10942 {
10943 $objectlineclassname = get_class($this).'Line';
10944 if (!class_exists($objectlineclassname)) {
10945 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
10946 return -1;
10947 }
10948
10949 $objectline = new $objectlineclassname($this->db);
10950 '@phan-var-force CommonObjectLine $objectline';
10951
10952 $sql = "SELECT ".$objectline->getFieldList('l');
10953 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
10954 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
10955 if ($morewhere) {
10956 $sql .= $morewhere;
10957 }
10958 if (isset($objectline->fields['position'])) {
10959 $sql .= $this->db->order('position', 'ASC');
10960 }
10961
10962 $resql = $this->db->query($sql);
10963 if ($resql) {
10964 $num_rows = $this->db->num_rows($resql);
10965 $i = 0;
10966 $this->lines = array();
10967 while ($i < $num_rows) {
10968 $obj = $this->db->fetch_object($resql);
10969 if ($obj) {
10970 $newline = new $objectlineclassname($this->db);
10971 '@phan-var-force CommonObjectLine $newline';
10972 $newline->setVarsFromFetchObj($obj);
10973
10974 // Load also extrafields for the line
10975 if (empty($noextrafields)) {
10976 $newline->fetch_optionals();
10977 }
10978
10979 $this->lines[] = $newline;
10980 }
10981 $i++;
10982 }
10983
10984 return 1;
10985 } else {
10986 $this->error = $this->db->lasterror();
10987 $this->errors[] = $this->error;
10988 return -1;
10989 }
10990 }
10991
10999 public function updateCommon(User $user, $notrigger = 0)
11000 {
11001 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
11002
11003 $error = 0;
11004
11005 $now = dol_now();
11006
11007 // $this->oldcopy should have been set by the caller of update
11008 //if (empty($this->oldcopy)) {
11009 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
11010 // $this->oldcopy = dol_clone($this, 2);
11011 //}
11012
11013 $fieldvalues = $this->setSaveQuery();
11014
11015 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
11016
11017 if (array_key_exists('date_modification', $fieldvalues)) {
11018 $fieldvalues['date_modification'] = $this->db->idate($now);
11019 }
11020 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
11021 $fieldvalues['tms'] = $this->db->idate($now);
11022 }
11023 if (array_key_exists('fk_user_modif', $fieldvalues)) {
11024 $fieldvalues['fk_user_modif'] = $user->id;
11025 }
11026 if (array_key_exists('user_modification_id', $fieldvalues)) {
11027 $fieldvalues['user_modification_id'] = $user->id;
11028 }
11029 // @phan-suppress-next-line PhanUndeclaredProperty
11030 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
11031 // @phan-suppress-next-line PhanUndeclaredProperty
11032 $tmparray = dol_hash($this->pass, '0', 0, 1);
11033 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
11034 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
11035 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
11036 }
11037 }
11038 if (array_key_exists('ref', $fieldvalues)) {
11039 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
11040 }
11041
11042 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
11043
11044 // Add quotes and escape on fields with type string
11045 $keys = array();
11046 $values = array();
11047 $tmp = array();
11048 foreach ($fieldvalues as $k => $v) {
11049 $keys[$k] = $k;
11050 $value = $this->fields[$k];
11051 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11052 $values[$k] = $this->quote($v, $value);
11053 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
11054 // Clean values for text with selectbox
11055 $v = preg_replace('/\s/', ',', $v);
11056 $v = preg_replace('/,+/', ',', $v);
11057 }
11058 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
11059 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
11060 }
11061
11062 // Clean and check mandatory fields
11063 foreach ($keys as $key) {
11064 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
11065 $values[$key] = ''; // This is an implicit foreign key field
11066 }
11067 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
11068 $values[$key] = ''; // This is an explicit foreign key field
11069 }
11070
11071 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
11072 /*
11073 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
11074 {
11075 $error++;
11076 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
11077 }*/
11078 }
11079
11080 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
11081
11082 $this->db->begin();
11083
11084 if (!$error) {
11085 $res = $this->db->query($sql);
11086 if (!$res) {
11087 $error++;
11088 $this->errors[] = $this->db->lasterror();
11089 }
11090 }
11091
11092 // Update extrafield
11093 if (!$error) {
11094 $result = $this->insertExtraFields(); // This update extrafields
11095 if ($result < 0) {
11096 $error++;
11097 }
11098 }
11099
11100 // Triggers
11101 if (!$error && !$notrigger) {
11102 // Call triggers
11103 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
11104 if ($result < 0) {
11105 $error++;
11106 } //Do also here what you must do to rollback action if trigger fail
11107 // End call triggers
11108 }
11109
11110 // Commit or rollback
11111 if ($error) {
11112 $this->db->rollback();
11113 return -1;
11114 } else {
11115 $this->db->commit();
11116 return $this->id;
11117 }
11118 }
11119
11128 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
11129 {
11130 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
11131
11132 $error = 0;
11133
11134 $this->db->begin();
11135
11136 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
11137 foreach ($this->childtables as $table) {
11138 $sql = "DELETE FROM ".$this->db->prefix().$table." WHERE ".$this->fk_element." = ".((int) $this->id);
11139 $resql = $this->db->query($sql);
11140 if (!$resql) {
11141 $this->error = $this->db->lasterror();
11142 $this->errors[] = $this->error;
11143 $this->db->rollback();
11144 return -1;
11145 }
11146 }
11147 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
11148 $objectisused = $this->isObjectUsed($this->id);
11149 if (!empty($objectisused)) {
11150 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
11151 $this->error = 'ErrorRecordHasChildren';
11152 $this->errors[] = $this->error;
11153 $this->db->rollback();
11154 return 0;
11155 }
11156 }
11157
11158 // Delete cascade first
11159 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
11160 foreach ($this->childtablesoncascade as $tabletodelete) {
11161 $deleteFromObject = explode(':', $tabletodelete, 4);
11162 if (count($deleteFromObject) >= 2) {
11163 $className = str_replace('@', '', $deleteFromObject[0]);
11164 $filePath = $deleteFromObject[1];
11165 $columnName = $deleteFromObject[2];
11166 $filter = '';
11167 if (!empty($deleteFromObject[3])) {
11168 $filter = $deleteFromObject[3];
11169 }
11170
11171 if (dol_include_once($filePath)) {
11172 $childObject = new $className($this->db);
11173 if (method_exists($childObject, 'deleteByParentField')) {
11174 '@phan-var-force CommonObject $childObject';
11175 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11176 if ($result < 0) {
11177 $error++;
11178 $this->errors[] = $childObject->error;
11179 break;
11180 }
11181 } else {
11182 $error++;
11183 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11184 break;
11185 }
11186 } else {
11187 $error++;
11188 $this->errors[] = 'Cannot include child class file '.$filePath;
11189 break;
11190 }
11191 } else {
11192 // Delete record in child table
11193 $sql = "DELETE FROM ".$this->db->prefix().$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
11194
11195 $resql = $this->db->query($sql);
11196 if (!$resql) {
11197 $error++;
11198 $this->error = $this->db->lasterror();
11199 $this->errors[] = $this->error;
11200 break;
11201 }
11202 }
11203 }
11204 }
11205
11206 if (!$error) {
11207 if (!$notrigger) {
11208 // Call triggers
11209 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11210 if ($result < 0) {
11211 $error++;
11212 } // Do also here what you must do to rollback action if trigger fail
11213 // End call triggers
11214 }
11215 }
11216
11217 // Delete llx_ecm_files
11218 if (!$error) {
11219 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11220 if (!$res) {
11221 $error++;
11222 }
11223 }
11224
11225 if (!$error) {
11226 $dir = getMultidirOutput($this)."/".dol_sanitizeFileName($this->ref);
11227 // For remove dir
11228 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11229 if (dol_is_dir($dir)) {
11230 if (!dol_delete_dir_recursive($dir)) {
11231 $this->errors[] = 'ErrorFailToDeleteDir';
11232 }
11233 }
11234 }
11235
11236 // Delete linked object
11237 $res = $this->deleteObjectLinked();
11238 if ($res < 0) {
11239 $error++;
11240 }
11241
11242 if (!$error && !empty($this->isextrafieldmanaged)) {
11243 $result = $this->deleteExtraFields();
11244 if ($result < 0) {
11245 $error++;
11246 }
11247 }
11248
11249 if (!$error) {
11250 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11251
11252 $resql = $this->db->query($sql);
11253 if (!$resql) {
11254 $error++;
11255 $this->errors[] = $this->db->lasterror();
11256 }
11257 }
11258
11259 // Commit or rollback
11260 if ($error) {
11261 $this->db->rollback();
11262 return -1;
11263 } else {
11264 $this->db->commit();
11265 return 1;
11266 }
11267 }
11268
11280 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11281 {
11282 global $user;
11283
11284 $error = 0;
11285 $deleted = 0;
11286
11287 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11288
11289 if (!empty($parentId) && !empty($parentField)) {
11290 if (empty($this->table_element)) {
11291 $this->error = 'Property table_element for object is not defined';
11292 $this->errors[] = $this->error;
11293 $error++;
11294 return -1;
11295 }
11296 if (!method_exists($this, 'fetch')) {
11297 $this->error = 'Method fetch for object is not defined';
11298 $this->errors[] = $this->error;
11299 $error++;
11300 return -1;
11301 }
11302 if (!method_exists($this, 'delete')) {
11303 $this->error = 'Method delete for object is not defined';
11304 $this->errors[] = $this->error;
11305 $error++;
11306 return -1;
11307 }
11308
11309 $this->db->begin();
11310
11311 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11312 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11313
11314 // Manage filter
11315 $errormessage = '';
11316 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11317 if ($errormessage) {
11318 $this->errors[] = $errormessage;
11319 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11320 return -1;
11321 }
11322
11323 $resql = $this->db->query($sql);
11324 if (!$resql) {
11325 $this->errors[] = $this->db->lasterror();
11326 $error++;
11327 } else {
11328 while ($obj = $this->db->fetch_object($resql)) {
11329 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11330 if ($result < 0) {
11331 $error++;
11332 $this->errors[] = $this->error;
11333 } else {
11334 $result = $this->delete($user); // @phpstan-ignore-line
11335 if ($result < 0) {
11336 $error++;
11337 $this->errors[] = $this->error;
11338 } else {
11339 $deleted++;
11340 }
11341 }
11342 }
11343 }
11344
11345 if (empty($error)) {
11346 $this->db->commit();
11347 return $deleted;
11348 } else {
11349 $this->error = implode(', ', $this->errors);
11350 $this->db->rollback();
11351 return $error * -1;
11352 }
11353 }
11354
11355 return $deleted;
11356 }
11357
11365 public function isLineOfObject($lineid)
11366 {
11367 if (!($this->id > 0) || empty($this->table_element_line) || empty($this->fk_element)) {
11368 return true;
11369 }
11370
11371 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->db->sanitize($this->table_element_line);
11372 $sql .= " WHERE rowid = ".((int) $lineid)." AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11373 $resql = $this->db->query($sql);
11374
11375 return ($resql && $this->db->num_rows($resql) > 0);
11376 }
11377
11386 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11387 {
11388 $error = 0;
11389
11390 $tmpforobjectclass = get_class($this);
11391 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11392
11393 if ($this->id > 0 && !empty($this->fk_element)) {
11394 // The line must belong to this object
11395 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
11396 $sql .= " WHERE rowid = ".((int) $idline)." AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11397 $resql = $this->db->query($sql);
11398 if (!$resql || !$this->db->num_rows($resql)) {
11399 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
11400 return -1;
11401 }
11402 }
11403
11404 $this->db->begin();
11405
11406 // Call trigger
11407 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11408 if ($result < 0) {
11409 $error++;
11410 }
11411 // End call triggers
11412
11413 if (empty($error)) {
11414 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11415 $sql .= " WHERE rowid = ".((int) $idline);
11416
11417 $resql = $this->db->query($sql);
11418 if (!$resql) {
11419 $this->error = "Error ".$this->db->lasterror();
11420 $error++;
11421 }
11422 }
11423
11424 if (empty($error)) {
11425 // Remove extrafields
11426 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11427 '@phan-var-force CommonObjectLine $tmpobjectline';
11428 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11429 $tmpobjectline->id = $idline;
11430 $result = $tmpobjectline->deleteExtraFields();
11431 if ($result < 0) {
11432 $error++;
11433 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11434 }
11435 }
11436 }
11437
11438 if (empty($error)) {
11439 $this->db->commit();
11440 return 1;
11441 } else {
11442 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11443 $this->db->rollback();
11444 return -1;
11445 }
11446 }
11447
11448
11459 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11460 {
11461 $error = 0;
11462
11463 $this->db->begin();
11464
11465 $statusfield = 'status';
11466 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11467 $statusfield = 'fk_statut';
11468 }
11469
11470 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11471 $sql .= " SET ".$statusfield." = ".((int) $status);
11472 $sql .= " WHERE rowid = ".((int) $this->id);
11473
11474 if ($this->db->query($sql)) {
11475 if (!$error) {
11476 $this->oldcopy = clone $this;
11477 }
11478
11479 if (!$error && !$notrigger) {
11480 // Call trigger
11481 $result = $this->call_trigger($triggercode, $user);
11482 if ($result < 0) {
11483 $error++;
11484 }
11485 }
11486
11487 if (!$error) {
11488 $this->status = $status;
11489 if (property_exists($this, 'statut')) { // For backward compatibility
11490 $this->statut = $status;
11491 }
11492 $this->db->commit();
11493 return 1;
11494 } else {
11495 $this->db->rollback();
11496 return -1;
11497 }
11498 } else {
11499 $this->error = $this->db->error();
11500 $this->db->rollback();
11501 return -1;
11502 }
11503 }
11504
11511 public function initAsSpecimenCommon()
11512 {
11513 global $user;
11514
11515 $this->id = 0;
11516 $this->specimen = 1;
11517 $fields = array(
11518 'label' => 'This is label',
11519 'ref' => 'ABCD1234',
11520 'description' => 'This is a description',
11521 'qty' => 123.12,
11522 'note_public' => 'Public note',
11523 'note_private' => 'Private note',
11524 'date_creation' => (dol_now() - 3600 * 48),
11525 'date_modification' => (dol_now() - 3600 * 24),
11526 'fk_user_creat' => $user->id,
11527 'fk_user_modif' => $user->id,
11528 'date' => dol_now(),
11529 );
11530 foreach ($fields as $key => $value) {
11531 if (array_key_exists($key, $this->fields)) {
11532 $this->{$key} = $value; // @phpstan-ignore-line
11533 }
11534 }
11535
11536 // Force values to default values when known
11537 if (property_exists($this, 'fields')) {
11538 foreach ($this->fields as $key => $value) {
11539 // If fields are already set, do nothing
11540 if (array_key_exists($key, $fields)) {
11541 continue;
11542 }
11543
11544 if (!empty($value['default'])) {
11545 $this->$key = $value['default'];
11546 }
11547 }
11548 }
11549
11550 return 1;
11551 }
11552
11553
11554 /* Part for comments */
11555
11561 public function fetchComments()
11562 {
11563 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11564
11565 $comment = new Comment($this->db);
11566 $result = $comment->fetchAllFor($this->element, $this->id);
11567 if ($result < 0) {
11568 $this->setErrorsFromObject($comment);
11569 return -1;
11570 } else {
11571 $this->comments = $comment->comments;
11572 }
11573 return count($this->comments);
11574 }
11575
11581 public function getNbComments()
11582 {
11583 return count($this->comments);
11584 }
11585
11592 public function trimParameters($parameters)
11593 {
11594 if (!is_array($parameters)) {
11595 return;
11596 }
11597 foreach ($parameters as $parameter) {
11598 if (isset($this->$parameter)) {
11599 $this->$parameter = trim($this->$parameter);
11600 }
11601 }
11602 }
11603
11604 /* Part for categories/tags */
11605
11616 public function getCategoriesCommon($type_categ)
11617 {
11618 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11619
11620 // Get current categories
11621 $c = new Categorie($this->db);
11622 $existing = $c->containing($this->id, $type_categ, 'id');
11623
11624 return $existing;
11625 }
11626
11639 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11640 {
11641 // Handle single category
11642 if (!is_array($categories)) {
11643 $categories = array($categories);
11644 }
11645
11646 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11647
11648 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11649
11650 if (empty($type_categ)) {
11651 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11652 return -1;
11653 }
11654
11655 // Get current categories
11656 $c = new Categorie($this->db);
11657 $existing = $c->containing($this->id, $type_categ, 'id');
11658 // containing() returns an integer < 0 on error (e.g. when the categorie_xxx table does not exist).
11659 // Normalize to an empty array to avoid array_diff()/foreach() warnings below.
11660 if (!is_array($existing)) {
11661 $existing = array();
11662 }
11663 if ($remove_existing) {
11664 // Diff
11665 $to_del = array_diff($existing, $categories);
11666 $to_add = array_diff($categories, $existing);
11667 } else {
11668 $to_del = array(); // Nothing to delete
11669 $to_add = array_diff($categories, $existing);
11670 }
11671
11672 $error = 0;
11673 $ok = 0;
11674
11675 // Process
11676 foreach ($to_del as $del) {
11677 if ($c->fetch($del) > 0) {
11678 $result = $c->del_type($this, $type_categ);
11679 if ($result < 0) {
11680 $error++;
11681 $this->error = $c->error;
11682 $this->errors = $c->errors;
11683 break;
11684 } else {
11685 $ok += $result;
11686 }
11687 }
11688 }
11689 foreach ($to_add as $add) {
11690 if ($c->fetch($add) > 0) {
11691 $result = $c->add_type($this, $type_categ);
11692 if ($result < 0) {
11693 $error++;
11694 $this->error = $c->error;
11695 $this->errors = $c->errors;
11696 break;
11697 } else {
11698 $ok += $result;
11699 }
11700 }
11701 }
11702
11703 return $error ? (-1 * $error) : $ok;
11704 }
11705
11714 public function cloneCategories($fromId, $toId, $type = '')
11715 {
11716 $this->db->begin();
11717
11718 if (empty($type)) {
11719 $type = $this->table_element;
11720 }
11721
11722 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11723 $categorystatic = new Categorie($this->db);
11724
11725 $tablename = $this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11726 $fkname = 'fk_' . (empty($categorystatic->MAP_CAT_FK[$type]) ? $type : $categorystatic->MAP_CAT_FK[$type]);
11727
11728 $sql = "INSERT INTO ".$tablename." (fk_categorie, ".$fkname.")";
11729 $sql .= " SELECT fk_categorie, $toId FROM ".$tablename;
11730 $sql .= " WHERE ".$fkname." = ".((int) $fromId);
11731
11732 if (!$this->db->query($sql)) {
11733 $this->error = $this->db->lasterror();
11734 $this->db->rollback();
11735 return -1;
11736 }
11737
11738 $this->db->commit();
11739 return 1;
11740 }
11741
11748 public function deleteEcmFiles($mode = 0)
11749 {
11750 global $conf;
11751
11752 $this->db->begin();
11753
11754 // Delete in database with mode 0
11755 if ($mode == 0) {
11756 switch ($this->element) {
11757 case 'propal':
11758 $element = 'propale';
11759 break;
11760 case 'product':
11761 $element = 'produit';
11762 break;
11763 case 'order_supplier':
11764 $element = 'fournisseur/commande';
11765 break;
11766 case 'invoice_supplier':
11767 // Special cases that need to use get_exdir to get real dir of object
11768 // In future, all object should use this to define path of documents.
11769 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11770 break;
11771 case 'shipping':
11772 $element = 'expedition/sending';
11773 break;
11774 case 'task':
11775 case 'project_task':
11776 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11777
11778 $project_result = $this->fetchProject();
11779 if ($project_result >= 0) {
11780 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11781 }
11782 // no break
11783 case 'contrat':
11784 $element = 'contract';
11785 break;
11786 default:
11787 $element = $this->element;
11788 }
11789 '@phan-var-force string $element';
11790
11791 // Delete ecm_files_extrafields with mode 0 (using name)
11792 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11793 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11794 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11795 $sql .= ")";
11796
11797 if (!$this->db->query($sql)) {
11798 $this->error = $this->db->lasterror();
11799 $this->db->rollback();
11800 return false;
11801 }
11802
11803 // Delete ecm_files with mode 0 (using name)
11804 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11805 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11806 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11807
11808 if (!$this->db->query($sql)) {
11809 $this->error = $this->db->lasterror();
11810 $this->db->rollback();
11811 return false;
11812 }
11813 }
11814
11815 // Delete in database with mode 1
11816 if ($mode == 1) {
11817 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11818 $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).")";
11819 $resql = $this->db->query($sql);
11820 if (!$resql) {
11821 $this->error = $this->db->lasterror();
11822 $this->db->rollback();
11823 return false;
11824 }
11825
11826 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11827 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11828 $resql = $this->db->query($sql);
11829 if (!$resql) {
11830 $this->error = $this->db->lasterror();
11831 $this->db->rollback();
11832 return false;
11833 }
11834 }
11835
11836 $this->db->commit();
11837 return true;
11838 }
11839
11848 public function checkActiveProductInLines($status = 'onsale')
11849 {
11850 global $langs;
11851
11852 if (isModEnabled('product') || isModEnabled('service')) {
11853 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
11854
11855 $ret = true;
11856 $tmpproduct = new Product($this->db);
11857 foreach ($this->lines as $line) {
11858 if ($line->fk_product > 0) {
11859 $tmpproduct->fetch($line->fk_product);
11860 $statustotest = ($status == 'onsale' ? 'status' : 'status_buy');
11861 if (!$tmpproduct->$statustotest) {
11862 $langs->load('products');
11863 $statuskey4lang = ($status == 'onsale' ? 'ProductStatusNotOnSell' : 'ProductStatusNotOnBuy');
11864 $ret = false;
11865 $this->errors[] = $langs->trans('ProductRef').' '.$tmpproduct->ref.' '.$langs->trans($statuskey4lang);
11866 break;
11867 }
11868 }
11869 }
11870 if (!$ret) {
11871 $this->error = 'ErrorOneLineContainsADisactivatedProduct';
11872 }
11873 return $ret;
11874 } else {
11875 return true;
11876 }
11877 }
11878
11885 public function setErrorWithoutLog($message)
11886 {
11887 $this->error = $message;
11888 $this->errors[] = $message;
11889 }
11890
11898 public function setErrorWithLog($message, $loglevel = LOG_ERR)
11899 {
11900 $this->setErrorWithoutLog($message);
11901 dol_syslog($message, $loglevel);
11902 }
11903}
$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:475
$c
Definition line.php:331
$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.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
fetchObjectFrom($table, $field, $key, $element=null)
Load object from specific field.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
static deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Function used to remove all items linked to an object id in association table.
setFieldError($fieldKey, $msg='')
set validation error message a field
validateField($fields, $fieldKey, $fieldValue)
Return validation test result for a field.
getDataToShowPhoto($modulepart, $imagesize)
Function used to get the logos or photos of an object.
setMulticurrencyCode($code)
Change the multicurrency code.
emtpyObjectVars()
Sets all object fields to null.
fetch_projet()
Load the project with id $this->fk_project into this->project.
getIdContact($source, $code, $status=0)
Return id of contacts for a source and a contact code.
setExtraField($key, $value)
Convenience method for setting the value of an extrafield without actually updating it in the databas...
setDocModel($user, $modelpdf)
Set last model used by doc generator.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
getExtraField($key)
Convenience method for retrieving the value of an extrafield without actually fetching it from the da...
updateExtraField($key, $trigger=null, $userused=null)
Update 1 extra field value for the current object.
setPaymentTerms($id, $deposit_percent=null)
Change the payments terms.
isFloat($info)
Function test if type is float.
setBankAccount($fk_account, $notrigger=0, $userused=null)
Change the bank account.
setExtraParameters()
Set extra parameters.
delete_resource($rowid, $element='', $notrigger=0)
Delete a link to resource line.
setErrorsFromObject($object)
setErrorsFromObject
setShippingMethod($shipping_method_id, $notrigger=0, $userused=null)
Change the shipping method.
update_note_public($note)
Update public note (kept for backward compatibility)
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
getSpecialCode($lineid)
Get special code of a line.
clearObjectLinkedCache()
Clear the cache saying that all linked object were already loaded.
update_ref_ext($ref_ext)
Update external ref of element.
fetchOneLike($ref)
Looks for an object with ref matching the wildcard provided It does only work when $this->table_ref_f...
hasProductsOrServices($predefined=-1)
Function to say how many lines object contains.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
updateRangOfLine($rowid, $rang)
Update position of line (rang)
getDefaultCreateValueFor($fieldname, $alternatevalue=null, $type='alphanohtml')
Return the default value to use for a field when showing the create form of object.
add_element_resource($resource_id, $resource_type, $busy=0, $mandatory=0, $notrigger=0)
Add resources to the current object : add entry into llx_element_resources Need $this->element & $thi...
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
createCommon(User $user, $notrigger=0)
Create object in the database.
getTotalWeightVolume()
Return into unit=0, the calculated total of weight and volume of all lines * qty Calculate by adding ...
update_note($note, $suffix='', $notrigger=0)
Update note of element.
getFullAddress($withcountry=0, $sep="\n", $withregion=0, $extralangcode='')
Return full address of contact.
fetch_project()
Load the project with id $this->fk_project into this->project.
getFieldList($alias='', $excludefields=array())
Function to concat keys of fields.
getNbComments()
Return nb comments already posted.
setVarsFromFetchObj(&$obj)
Function to load data from a SQL pointer into properties of current object $this.
printObjectLines($action, $seller, $buyer, $selected=0, $dateSelector=0, $defaulttpldir='/core/tpl')
Return HTML table for object lines TODO Move this into an output class file (htmlline....
getChildrenOfLine($id, $includealltree=0)
Get children of line.
updateCommon(User $user, $notrigger=0)
Update object into database.
updateExtraLanguages($key, $trigger=null, $userused=null)
Update an extra language value for the current object.
deleteExtraFields()
Delete all extra fields values for the current object.
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
deprecatedProperties()
Provide list of deprecated properties and replacements.
setSaveQuery()
Function to return the array of data key-value from the ->fields and all the ->properties of an objec...
setValuesForExtraLanguages($onlykey='')
Fill array_options property of object by extrafields value (using for data sent by forms) Used for ex...
insertExtraLanguages($trigger='', $userused=null)
Add/Update all extra languages values for the current object.
setTransportMode($id)
Change the transport mode methods.
isArray($info)
Function test if type is array.
isInt($info)
Function test if type is integer.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
delete_contact($rowid, $notrigger=0)
Delete a link to contact line.
updateLineUp($rowid, $rang)
Update position of line up (rang)
fetch_user($userid)
Load the user with id $userid into this->user.
errorsToString()
Method to output saved errors.
getListContactId($source='external')
Return list of id of contacts of object.
setWarehouse($warehouse_id)
Change the warehouse.
setDeliveryAddress($id)
Define delivery address.
fetchBarCode()
Load data for barcode into properties ->barcode_type* Properties ->barcode_type that is id of barcode...
getTotalDiscount()
Function that returns the total amount HT of discounts applied for all lines.
initAsSpecimenCommon()
Initialise object with example values Id must be 0 if object instance is a specimen.
printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected=0, $extrafields=null, $defaulttpldir='/core/tpl')
Return HTML content of a detail line TODO Move this into an output class file (htmlline....
copy_linked_contact($objFrom, $source='internal')
Copy contact from one element to current.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
getValueFrom($table, $id, $field)
Getter generic.
trimParameters($parameters)
Trim object parameters.
fetch_product()
Load the product with id $this->fk_product into this->product.
isIndex($info)
Function test if is indexed.
quote($value, $fieldsentry)
Add quote to field value if necessary.
formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir='/core/tpl')
Show add free and predefined products/services form.
fetchComments()
Load comments linked with current task.
line_down($rowid, $fk_parent_line=true)
Update a line to have a higher rank.
setValueFrom($field, $value, $table='', $id=null, $format='', $id_field='', $fuser=null, $trigkey='', $fk_user_field='fk_user_modif')
Setter generic.
fetch_contact($contactid=null)
Load object contact with id=$this->contact_id into $this->contact.
setRetainedWarrantyPaymentTerms($id)
Change the retained warranty payments terms.
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 log it.
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:171
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:262
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.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getPictoForType($key, $morecss='')
Return the picto for a data type.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into javascript code.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) 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.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
getDictionaryValue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
Return the value of a filed into a dictionary for the record $id.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png, bmp and webp)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:136
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:132
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.
dolEncrypt($chain, $key='', $ciphering='', $forceseed='')
Encode a string with a symmetric encryption.
dolDecrypt($chain, $key='')
Decode a string with a symmetric encryption.