dolibarr 22.0.5
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-2025 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 *
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation; either version 3 of the License, or
27 * (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36 */
37
44require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
45
51abstract class CommonObject
52{
53 use DolDeprecationHandler;
54
55 const TRIGGER_PREFIX = ''; // to be overridden in child class implementations, i.e. 'BILL', 'TASK', 'PROPAL', etc.
56
60 public $module;
61
65 public $db;
66
70 public $id;
71
75 public $entity;
76
81 public $error;
82
86 public $errorhidden;
87
91 public $errors = array();
92
96 private $validateFieldsErrors = array();
97
101 public $element;
102
107 public $fk_element;
108
114 public $element_for_permission;
115
119 public $table_element;
120
124 public $table_element_line = '';
125
130 public $ismultientitymanaged;
131
135 public $import_key;
136
140 public $array_options = array();
141
142
146 public $fields = array();
147
153 public $array_languages = null; // Value is array() when load already tried
154
158 public $contacts_ids;
159
163 public $contacts_ids_internal;
164
168 public $linked_objects;
169
173 public $linkedObjectsIds;
174
178 public $linkedObjects;
179
183 private $linkedObjectsFullLoaded = array();
184
188 public $oldcopy;
189
193 public $oldref;
194
198 protected $table_ref_field = '';
199
203 public $restrictiononfksoc = 0;
204
205
206 // The following vars are used by some objects only.
207 // We keep these properties in CommonObject in order to provide common methods using them.
208
212 public $context = array();
213
217 public $actionmsg;
221 public $actionmsg2;
222
226 public $canvas;
227
232 public $project;
233
238 public $fk_project;
239
245 private $projet;
246
252 public $fk_projet;
253
258 public $contact;
259
264 public $contact_id;
265
270 public $thirdparty;
271
276 public $user;
277
282 public $origin_type;
283
288 public $origin_id;
289
294
300 public $origin;
301
310 private $expedition;
311
317 private $livraison;
318
324 private $commandeFournisseur;
325
326
330 public $ref;
331
335 public $ref_ext;
336
340 public $ref_previous;
341
345 public $ref_next;
346
350 public $newref;
351
358 public $statut;
359
368 public $status;
369
374 public $country;
375
380 public $country_id;
381
386 public $country_code;
387
392 public $state;
393
398 public $state_id;
399
404 public $state_code;
405
410 public $region_id;
411
416 public $region_code;
417
422 public $region;
423
424
429 public $barcode_type;
430
435 public $barcode_type_code;
436
441 public $barcode_type_label;
442
447 public $barcode_type_coder;
448
453 public $mode_reglement_id;
454
459 public $cond_reglement_id;
460
464 public $demand_reason_id;
465
470 public $transport_mode_id;
471
479 private $cond_reglement; // Private to call DolDeprecationHandler
483 protected $depr_cond_reglement; // Internal value for deprecation
484
490 public $fk_delivery_address;
491
496 public $shipping_method_id;
497
502 public $shipping_method;
503
504 // Multicurrency
508 public $fk_multicurrency;
509
514 public $multicurrency_code;
515
520 public $multicurrency_tx;
521
525 public $multicurrency_total_ht;
526
530 public $multicurrency_total_tva;
531
535 public $multicurrency_total_localtax1; // not in database
536
540 public $multicurrency_total_localtax2; // not in database
541
545 public $multicurrency_total_ttc;
546
547
552 public $model_pdf;
553
558 public $last_main_doc;
559
565 public $fk_bank;
566
571 public $fk_account;
572
577 public $note_public;
578
583 public $note_private;
584
590 public $note;
591
596 public $total_ht;
597
602 public $total_tva;
603
608 public $total_localtax1;
609
614 public $total_localtax2;
615
620 public $total_ttc;
621
622
626 public $lines;
627
631 public $actiontypecode;
632
637 public $comments = array();
638
642 public $name;
643
647 public $lastname;
648
652 public $firstname;
653
658 public $civility_id;
659
663 public $civility_code;
664
665 // Dates
669 public $date_creation;
670
674 public $date_validation;
675
679 public $date_modification;
680
686 public $tms;
687
693 private $date_update;
694
698 public $date_cloture;
699
704 public $user_author;
705
710 public $user_creation;
711
715 public $user_creation_id;
716
721 public $user_valid;
722
727 public $user_validation;
728
732 public $user_validation_id;
733
737 public $user_closing_id;
738
743 public $user_modification;
744
748 public $user_modification_id;
749
754 public $fk_user_creat;
755
760 public $fk_user_modif;
761
762
766 public $next_prev_filter;
767
771 public $specimen = 0;
772
776 public $sendtoid;
777
782 public $alreadypaid;
783
788 public $totalpaid;
789
793 public $labelStatus = array();
794
798 public $labelStatusShort = array();
799
803 public $tpl;
804
805
809 public $showphoto_on_popup;
810
814 public $nb = array();
815
819 public $nbphoto;
820
824 public $output;
825
829 public $extraparams = array();
830
834 protected $childtables = array();
835
841 protected $childtablesoncascade = array();
842
846 public $product;
847
851 public $cond_reglement_supplier_id;
852
858 public $deposit_percent;
859
860
864 public $retained_warranty_fk_cond_reglement;
865
869 public $warehouse_id;
870
874 public $isextrafieldmanaged = 0;
875
876
877 // No constructor as it is an abstract class
878
884 protected function deprecatedProperties()
885 {
886 return array(
887 'alreadypaid' => 'totalpaid',
888 'cond_reglement' => 'depr_cond_reglement',
889 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
890 'commandeFournisseur' => 'origin_object',
891 'expedition' => 'origin_object',
892 'fk_project' => 'fk_project',
893 'livraison' => 'origin_object',
894 'projet' => 'project',
895 'statut' => 'status',
896 );
897 }
898
899
910 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
911 {
912 global $db, $conf;
913
914 $sql = "SELECT rowid";
915 $sql .= " FROM ".$db->prefix().$element;
916 $sql .= " WHERE entity IN (".getEntity($element).")";
917
918 if ($id > 0) {
919 $sql .= " AND rowid = ".((int) $id);
920 } elseif ($ref) {
921 $sql .= " AND ref = '".$db->escape($ref)."'";
922 } elseif ($ref_ext) {
923 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
924 } else {
925 $error = 'ErrorWrongParameters';
926 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
927 return -1;
928 }
929 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
930 $sql .= " AND entity = ".((int) $conf->entity);
931 }
932
933 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
934 $resql = $db->query($sql);
935 if ($resql) {
936 $num = $db->num_rows($resql);
937 if ($num > 0) {
938 return 1;
939 } else {
940 return 0;
941 }
942 }
943 return -1;
944 }
945
951 public function isEmpty()
952 {
953 return (empty($this->id));
954 }
955
963 {
964 if (!empty($object->error)) {
965 $this->error = $object->error;
966 }
967 if (!empty($object->errors)) {
968 $this->errors = array_merge($this->errors, $object->errors);
969 }
970 }
971
979 public function getTooltipContentArray($params)
980 {
981 return [];
982 }
983
991 public function getTooltipContent($params)
992 {
993 global $action, $extrafields, $langs, $hookmanager;
994
995 // If there is too much extrafields, we do not include them into tooltip
996 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
997
998 $data = $this->getTooltipContentArray($params);
999 $count = 0;
1000
1001 // Add extrafields
1002 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
1003 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
1004 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
1005 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1006 continue;
1007 }
1008 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1009 $data['more_extrafields'] = '<br>...';
1010 break;
1011 }
1012 $enabled = 1;
1013 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1014 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1015 }
1016 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1017 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1018 }
1019 $perms = 1;
1020 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1021 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1022 }
1023 if (empty($enabled)) {
1024 continue; // 0 = Never visible field
1025 }
1026 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1027 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1028 }
1029 if (empty($perms)) {
1030 continue; // 0 = Not visible
1031 }
1032 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1033 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1034 }
1035 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1036 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1037 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1038 } else {
1039 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1040 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1041 $count++;
1042 }
1043 }
1044 $data['closedivextra'] = '</div>';
1045 }
1046
1047 $hookmanager->initHooks(array($this->element . 'dao'));
1048 $parameters = array(
1049 'tooltipcontentarray' => &$data,
1050 'params' => $params,
1051 );
1052 // Note that $action and $object may have been modified by some hooks
1053 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1054
1055 //var_dump($data);
1056 $label = implode($data);
1057
1058 return $label;
1059 }
1060
1061
1067 public function errorsToString()
1068 {
1069 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1070 }
1071
1072
1079 public function getFormatedCustomerRef($objref)
1080 {
1081 global $hookmanager;
1082
1083 $parameters = array('objref' => $objref);
1084 $action = '';
1085 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1086 if ($reshook > 0) {
1087 return $hookmanager->resArray['objref'];
1088 }
1089 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1090 }
1091
1098 public function getFormatedSupplierRef($objref)
1099 {
1100 global $hookmanager;
1101
1102 $parameters = array('objref' => $objref);
1103 $action = '';
1104 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1105 if ($reshook > 0) {
1106 return $hookmanager->resArray['objref'];
1107 }
1108 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1109 }
1110
1120 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1121 {
1122 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1123 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1124 $tmparray = getCountry($this->country_id, 'all');
1125 $this->country_code = $tmparray['code'];
1126 $this->country = $tmparray['label'];
1127 }
1128
1129 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1130 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1131 $tmparray = getState($this->state_id, 'all', null, 1);
1132 $this->state_code = $tmparray['code'];
1133 $this->state = $tmparray['label'];
1134 $this->region_code = $tmparray['region_code'];
1135 $this->region = $tmparray['region'];
1136 }
1137
1138 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1139 }
1140
1141
1150 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1151 {
1152 global $user, $dolibarr_main_url_root;
1153
1154 if (empty($this->last_main_doc)) {
1155 return ''; // No way to known which document name to use
1156 }
1157
1158 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1159 $ecmfile = new EcmFiles($this->db);
1160 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1161 if ($result < 0) {
1162 $this->error = $ecmfile->error;
1163 $this->errors = $ecmfile->errors;
1164 return -1;
1165 }
1166
1167 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1168 // Add entry into index
1169 if ($initsharekey) {
1170 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1171
1172 // 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
1173 /*
1174 $ecmfile->filepath = $rel_dir;
1175 $ecmfile->filename = $filename;
1176 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1177 $ecmfile->fullpath_orig = '';
1178 $ecmfile->gen_or_uploaded = 'generated';
1179 $ecmfile->description = ''; // indexed content
1180 $ecmfile->keywords = ''; // keyword content
1181 $ecmfile->share = getRandomPassword(true);
1182 $result = $ecmfile->create($user);
1183 if ($result < 0)
1184 {
1185 $this->error = $ecmfile->error;
1186 $this->errors = $ecmfile->errors;
1187 }
1188 */
1189 } else {
1190 return '';
1191 }
1192 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1193 // Add entry into index
1194 if ($initsharekey) {
1195 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1196 $ecmfile->share = getRandomPassword(true);
1197 $ecmfile->update($user);
1198 } else {
1199 return '';
1200 }
1201 }
1202 // Define $urlwithroot
1203 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1204 // This is to use external domain name found into config file
1205 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1206 //else
1207 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1208 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1209
1210 $forcedownload = 0;
1211
1212 $paramlink = '';
1213 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1214 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1215 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1216 if (!empty($ecmfile->share)) {
1217 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1218 }
1219 if ($forcedownload) {
1220 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1221 }
1222
1223 if ($relativelink) {
1224 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1225 } else {
1226 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1227 }
1228
1229 // Here $ecmfile->share is defined
1230 return $linktoreturn;
1231 }
1232
1233
1234 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1244 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1245 {
1246 // phpcs:enable
1247 global $user, $langs;
1248
1249 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1250
1251 // Check parameters
1252 if ($fk_socpeople <= 0) {
1253 $langs->load("errors");
1254 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1255 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1256 return -1;
1257 }
1258 if (!$type_contact) {
1259 $langs->load("errors");
1260 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1261 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1262 return -2;
1263 }
1264
1265 if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && !$user->hasRight('societe', 'client', 'voir')) {
1266 $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc';
1267 $sql_allowed_contacts.= ' WHERE sc.fk_soc = '.(int) $this->socid;
1268 $sql_allowed_contacts.= ' AND sc.fk_user = '.(int) $user->id;
1269
1270 $resql_allowed_contacts = $this->db->query($sql_allowed_contacts);
1271
1272 if (!$resql_allowed_contacts) {
1273 $this->errors[] = $this->db->lasterror();
1274 return -3;
1275 } elseif ($obj = $this->db->fetch_object($resql_allowed_contacts)) {
1276 if ($obj->cnt == 0) {
1277 $langs->load("companies");
1278 $this->error = $langs->trans("ErrorCommercialNotAllowedForThirdparty", $user->id);
1279 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1280 return -3;
1281 }
1282 }
1283 }
1284
1285 $id_type_contact = 0;
1286 if (is_numeric($type_contact)) {
1287 $id_type_contact = $type_contact;
1288 } else {
1289 // We look for id type_contact
1290 $sql = "SELECT tc.rowid";
1291 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1292 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1293 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1294 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1295 //print $sql;
1296 $resql = $this->db->query($sql);
1297 if ($resql) {
1298 $obj = $this->db->fetch_object($resql);
1299 if ($obj) {
1300 $id_type_contact = $obj->rowid;
1301 }
1302 }
1303 }
1304 if ($id_type_contact == 0) {
1305 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");
1306 return 0;
1307 }
1308
1309 $datecreate = dol_now();
1310
1311 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1312 $TListeContacts = $this->liste_contact(-1, $source);
1313 $already_added = false;
1314 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1315 foreach ($TListeContacts as $array_contact) {
1316 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1317 $already_added = true;
1318 break;
1319 }
1320 }
1321 }
1322
1323 if (!$already_added) {
1324 $this->db->begin();
1325
1326 // Insert into database
1327 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1328 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1329 $sql .= " VALUES (".$this->id.", ".((int) $fk_socpeople)." , ";
1330 $sql .= "'".$this->db->idate($datecreate)."'";
1331 $sql .= ", 4, ".((int) $id_type_contact);
1332 $sql .= ")";
1333
1334 $resql = $this->db->query($sql);
1335 if ($resql) {
1336 if (!$notrigger) {
1337 $result = $this->call_trigger(strtoupper($this->element).'_ADD_CONTACT', $user);
1338 if ($result < 0) {
1339 $this->db->rollback();
1340 return -1;
1341 }
1342 }
1343
1344 $this->db->commit();
1345 return 1;
1346 } else {
1347 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1348 $this->error = $this->db->errno();
1349 $this->db->rollback();
1350 return -2;
1351 } else {
1352 $this->error = $this->db->lasterror();
1353 $this->db->rollback();
1354 return -1;
1355 }
1356 }
1357 } else {
1358 return 0;
1359 }
1360 }
1361
1362 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1370 public function copy_linked_contact($objFrom, $source = 'internal')
1371 {
1372 // phpcs:enable
1373 $contacts = $objFrom->liste_contact(-1, $source);
1374 foreach ($contacts as $contact) {
1375 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1376 return -1;
1377 }
1378 }
1379 return 1;
1380 }
1381
1382 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1392 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1393 {
1394 // phpcs:enable
1395 // Insert into database
1396 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1397 $sql .= " statut = ".((int) $statut);
1398 if ($type_contact_id) {
1399 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1400 }
1401 if ($fk_socpeople) {
1402 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1403 }
1404 $sql .= " where rowid = ".((int) $rowid);
1405
1406 $resql = $this->db->query($sql);
1407 if ($resql) {
1408 return 0;
1409 } else {
1410 $this->error = $this->db->lasterror();
1411 return -1;
1412 }
1413 }
1414
1415 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1423 public function delete_contact($rowid, $notrigger = 0)
1424 {
1425 // phpcs:enable
1426 global $user;
1427
1428 $error = 0;
1429
1430 // When called on a loaded object, the link must be one of its own contacts (same filter as liste_contact())
1431 if ($this->id > 0) {
1432 $sql = "SELECT ec.rowid FROM ".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc";
1433 $sql .= " WHERE ec.rowid = ".((int) $rowid)." AND ec.element_id = ".((int) $this->id);
1434 $sql .= " AND ec.fk_c_type_contact = tc.rowid AND tc.element = '".$this->db->escape($this->element)."'";
1435 $resql = $this->db->query($sql);
1436 if (!$resql || !$this->db->num_rows($resql)) {
1437 $this->error = 'ErrorRecordNotFound';
1438 return -1;
1439 }
1440 }
1441
1442 $this->db->begin();
1443
1444 if (!$error && empty($notrigger)) {
1445 // Call trigger
1446 $this->context['contact_id'] = ((int) $rowid);
1447 $result = $this->call_trigger(strtoupper($this->element).'_DELETE_CONTACT', $user);
1448 if ($result < 0) {
1449 $error++;
1450 }
1451 // End call triggers
1452 }
1453
1454 if (!$error) {
1455 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1456
1457 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1458 $sql .= " WHERE rowid = ".((int) $rowid);
1459
1460 $result = $this->db->query($sql);
1461 if (!$result) {
1462 $error++;
1463 $this->errors[] = $this->db->lasterror();
1464 }
1465 }
1466
1467 if (!$error) {
1468 $this->db->commit();
1469 return 1;
1470 } else {
1471 $this->error = $this->db->lasterror();
1472 $this->db->rollback();
1473 return -1;
1474 }
1475 }
1476
1477 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1485 public function delete_linked_contact($source = '', $code = '')
1486 {
1487 // phpcs:enable
1488 $listId = '';
1489 $temp = array();
1490 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1491
1492 if (!empty($typeContact)) {
1493 foreach ($typeContact as $key => $value) {
1494 array_push($temp, $key);
1495 }
1496 $listId = implode(",", $temp);
1497 }
1498
1499 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1500 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1501 if (empty($listId)) {
1502 return 0;
1503 }
1504
1505 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1506 $sql .= " WHERE element_id = ".((int) $this->id);
1507 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1508
1509 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1510 if ($this->db->query($sql)) {
1511 return 1;
1512 } else {
1513 $this->error = $this->db->lasterror();
1514 return -1;
1515 }
1516 }
1517
1518 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1530 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1531 {
1532 // phpcs:enable
1533 global $langs;
1534
1535 $tab = array();
1536
1537 $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
1538 if ($source == 'internal') {
1539 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1540 }
1541 if ($source == 'external' || $source == 'thirdparty') {
1542 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1543 }
1544 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1545 if (empty($arrayoftcids)) {
1546 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1547 }
1548 $sql .= " FROM";
1549 if (empty($arrayoftcids)) {
1550 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1551 }
1552 $sql .= " ".$this->db->prefix()."element_contact as ec";
1553 if ($source == 'internal') { // internal contact (user)
1554 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1555 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1556 }
1557 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1558 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1559 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1560 }
1561 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1562 if (empty($arrayoftcids)) {
1563 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1564 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1565 if ($code) {
1566 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1567 }
1568 if ($source == 'internal') {
1569 $sql .= " AND tc.source = 'internal'";
1570 }
1571 if ($source == 'external' || $source == 'thirdparty') {
1572 $sql .= " AND tc.source = 'external'";
1573 }
1574 $sql .= " AND tc.active = 1";
1575 } else {
1576 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1577 }
1578 if ($status >= 0) {
1579 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1580 }
1581 if ($statusoflink >= 0) {
1582 $sql .= " AND ec.statut = ".((int) $statusoflink);
1583 }
1584 $sql .= " ORDER BY t.lastname ASC";
1585
1586 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1587 $resql = $this->db->query($sql);
1588 if ($resql) {
1589 $num = $this->db->num_rows($resql);
1590 $i = 0;
1591 while ($i < $num) {
1592 $obj = $this->db->fetch_object($resql);
1593
1594 if (!$list) {
1595 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1596 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1597 $tab[$i] = array(
1598 'parentId' => $this->id,
1599 'source' => $obj->source,
1600 'socid' => $obj->socid,
1601 'id' => $obj->id,
1602 'nom' => $obj->lastname, // For backward compatibility
1603 'civility' => $obj->civility,
1604 'lastname' => $obj->lastname,
1605 'firstname' => $obj->firstname,
1606 'email' => $obj->email,
1607 'address' => $obj->address,
1608 'zip' => $obj->zip,
1609 'town' => $obj->town,
1610 'country_id' => $obj->country_id,
1611 'country' => $obj->country,
1612 'login' => (empty($obj->login) ? '' : $obj->login),
1613 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1614 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1615 'statuscontact' => $obj->statuscontact,
1616 'rowid' => $obj->rowid,
1617 'code' => $obj->code,
1618 'libelle' => $libelle_type,
1619 'status' => (int) $obj->statuslink,
1620 'fk_c_type_contact' => $obj->fk_c_type_contact
1621 );
1622 } else {
1623 $tab[$i] = $obj->id;
1624 }
1625
1626 $i++;
1627 }
1628
1629 return $tab;
1630 } else {
1631 $this->error = $this->db->lasterror();
1632 dol_print_error($this->db);
1633 return -1;
1634 }
1635 }
1636
1637
1644 public function swapContactStatus($rowid)
1645 {
1646 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1647 $sql .= " tc.code, tc.libelle as type_label";
1648 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1649 $sql .= " WHERE ec.rowid =".((int) $rowid);
1650 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1651 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1652
1653 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1654 $resql = $this->db->query($sql);
1655 if ($resql) {
1656 $obj = $this->db->fetch_object($resql);
1657 $newstatut = ($obj->statut == 4) ? 5 : 4;
1658 $result = $this->update_contact($rowid, $newstatut);
1659 $this->db->free($resql);
1660 return $result;
1661 } else {
1662 $this->error = $this->db->error();
1663 dol_print_error($this->db);
1664 return -1;
1665 }
1666 }
1667
1668 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1679 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1680 {
1681 // phpcs:enable
1682 global $langs;
1683
1684 if (empty($order)) {
1685 $order = 'position';
1686 }
1687 if ($order == 'position') {
1688 $order .= ',code';
1689 }
1690
1691 $tab = array();
1692
1693 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1694 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1695 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1696 if ($activeonly == 1) {
1697 $sql .= " AND tc.active = 1"; // only the active types
1698 }
1699 if (!empty($source) && $source != 'all') {
1700 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1701 }
1702 if (!empty($code)) {
1703 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1704 }
1705 $sql .= $this->db->order($order, 'ASC');
1706
1707 //print "sql=".$sql;
1708 $resql = $this->db->query($sql);
1709 if (!$resql) {
1710 $this->error = $this->db->lasterror();
1711 //dol_print_error($this->db);
1712 return null;
1713 }
1714
1715 $num = $this->db->num_rows($resql);
1716 $i = 0;
1717 while ($i < $num) {
1718 $obj = $this->db->fetch_object($resql);
1719
1720 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1721 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1722 if (empty($option)) {
1723 $tab[$obj->rowid] = $libelle_type;
1724 } elseif ($option == 1) {
1725 $tab[$obj->code] = $libelle_type;
1726 } else {
1727 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1728 }
1729 $i++;
1730 }
1731
1732 return $tab;
1733 }
1734
1746 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1747 {
1748 global $langs;
1749
1750 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1751
1752 $tab = array();
1753
1754 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1755 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1756
1757 $sqlWhere = array();
1758 if (!empty($element)) {
1759 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1760 }
1761 if (!empty($excludeelement)) {
1762 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1763 }
1764
1765 if ($activeonly == 1) {
1766 $sqlWhere[] = " tc.active=1"; // only the active types
1767 }
1768
1769 if (!empty($source) && $source != 'all') {
1770 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1771 }
1772
1773 if (!empty($code)) {
1774 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1775 }
1776
1777 if (count($sqlWhere) > 0) {
1778 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1779 }
1780
1781 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1782
1783 dol_syslog(__METHOD__, LOG_DEBUG);
1784 $resql = $this->db->query($sql);
1785 if ($resql) {
1786 $num = $this->db->num_rows($resql);
1787 if ($num > 0) {
1788 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1789
1790 while ($obj = $this->db->fetch_object($resql)) {
1791 $modulename = $obj->module ?? $obj->element;
1792 if (strpos($obj->element, 'project') !== false) {
1793 $modulename = 'projet';
1794 } elseif ($obj->element == 'contrat') {
1795 $element = 'contract';
1796 } elseif ($obj->element == 'action') {
1797 $modulename = 'agenda';
1798 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1799 $modulename = 'fournisseur';
1800 }
1801 if (isModEnabled($modulename)) {
1802 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1803 $tmpelement = $obj->element;
1804 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1805 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1806 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1807 }
1808 }
1809 }
1810 return $tab;
1811 } else {
1812 $this->error = $this->db->lasterror();
1813 return null;
1814 }
1815 }
1816
1828 public function getIdContact($source, $code, $status = 0)
1829 {
1830 $result = array();
1831 $i = 0;
1832 // Particular case for shipping
1833 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1834 $id = $this->origin_id;
1835 $element = 'commande';
1836 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1837 $id = $this->origin_id;
1838 $element = 'order_supplier';
1839 } else {
1840 $id = $this->id;
1841 $element = $this->element;
1842 }
1843
1844 $sql = "SELECT ec.fk_socpeople";
1845 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1846 if ($source == 'internal') {
1847 $sql .= " ".$this->db->prefix()."user as c,";
1848 }
1849 if ($source == 'external') {
1850 $sql .= " ".$this->db->prefix()."socpeople as c,";
1851 }
1852 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1853 $sql .= " WHERE ec.element_id = ".((int) $id);
1854 $sql .= " AND ec.fk_socpeople = c.rowid";
1855 if ($source == 'internal') {
1856 $sql .= " AND c.entity IN (".getEntity('user').")";
1857 }
1858 if ($source == 'external') {
1859 $sql .= " AND c.entity IN (".getEntity('societe').")";
1860 }
1861 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1862 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1863 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1864 if ($code) {
1865 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1866 }
1867 $sql .= " AND tc.active = 1";
1868 if ($status) {
1869 $sql .= " AND ec.statut = ".((int) $status);
1870 }
1871
1872 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1873 $resql = $this->db->query($sql);
1874 if ($resql) {
1875 while ($obj = $this->db->fetch_object($resql)) {
1876 $result[$i] = $obj->fk_socpeople;
1877 $i++;
1878 }
1879 } else {
1880 $this->error = $this->db->error();
1881 return null;
1882 }
1883
1884 return $result;
1885 }
1886
1887 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1894 public function fetch_contact($contactid = null)
1895 {
1896 // phpcs:enable
1897 if (empty($contactid)) {
1898 $contactid = $this->contact_id;
1899 }
1900
1901 if (empty($contactid)) {
1902 return 0;
1903 }
1904
1905 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1906 $contact = new Contact($this->db);
1907 $result = $contact->fetch($contactid);
1908 $this->contact = $contact;
1909 return $result;
1910 }
1911
1912 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1920 public function fetch_thirdparty($force_thirdparty_id = 0)
1921 {
1922 // phpcs:enable
1923 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1924 return 0;
1925 }
1926
1927 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1928
1929 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1930 if ($force_thirdparty_id) {
1931 $idtofetch = $force_thirdparty_id;
1932 }
1933
1934 if ($idtofetch) {
1935 $thirdparty = new Societe($this->db);
1936 $result = $thirdparty->fetch($idtofetch);
1937 if ($result < 0) {
1938 $this->errors = array_merge($this->errors, $thirdparty->errors);
1939 }
1940 $this->thirdparty = $thirdparty;
1941
1942 // Use first price level if level not defined for third party
1943 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
1944 $this->thirdparty->price_level = 1;
1945 }
1946
1947 return $result;
1948 } else {
1949 return -1;
1950 }
1951 }
1952
1953
1961 public function fetchOneLike($ref)
1962 {
1963 if (!$this->table_ref_field) {
1964 return 0;
1965 }
1966
1967 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
1968 $sql .= " WHERE ".$this->table_ref_field." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
1969 $sql .= " LIMIT 1";
1970
1971 $query = $this->db->query($sql);
1972
1973 if (!$this->db->num_rows($query)) {
1974 return 0;
1975 }
1976
1977 $result = $this->db->fetch_object($query);
1978
1979 if (method_exists($this, 'fetch')) {
1980 return $this->fetch($result->rowid);
1981 } else {
1982 $this->error = 'Fetch method not implemented on '.get_class($this);
1983 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
1984 array_push($this->errors, $this->error);
1985 return -1;
1986 }
1987 }
1988
1989 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1998 public function fetch_barcode()
1999 {
2000 // phpcs:enable
2001 return $this->fetchBarCode();
2002 }
2003
2011 public function fetchBarCode()
2012 {
2013 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2014
2015 $idtype = $this->barcode_type;
2016 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
2017 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2018 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2019 } elseif ($this->element == 'societe') {
2020 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2021 } else {
2022 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2023 }
2024 }
2025
2026 if ($idtype > 0) {
2027 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
2028 $sql = "SELECT rowid, code, libelle as label, coder";
2029 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2030 $sql .= " WHERE rowid = ".((int) $idtype);
2031
2032 $resql = $this->db->query($sql);
2033 if ($resql) {
2034 $obj = $this->db->fetch_object($resql);
2035
2036 $this->barcode_type = $obj->rowid;
2037 $this->barcode_type_code = $obj->code;
2038 $this->barcode_type_label = $obj->label;
2039 $this->barcode_type_coder = $obj->coder;
2040 return 1;
2041 } else {
2042 dol_print_error($this->db);
2043 return -1;
2044 }
2045 }
2046 }
2047 return 0;
2048 }
2049
2055 public function fetchProject()
2056 {
2057 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2058
2059 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2060 $this->fk_project = $this->fk_projet; // For backward compatibility
2061 }
2062 if (empty($this->fk_project)) {
2063 return 0;
2064 }
2065
2066 $project = new Project($this->db);
2067 $result = $project->fetch($this->fk_project);
2068
2069 $this->projet = $project; // deprecated
2070 $this->project = $project;
2071
2072 return $result;
2073 }
2074
2075 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2083 public function fetch_project()
2084 {
2085 // phpcs:enable
2086 return $this->fetchProject();
2087 }
2088
2089 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2097 public function fetch_projet()
2098 {
2099 // phpcs:enable
2100 return $this->fetchProject();
2101 }
2102
2103 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2109 public function fetch_product()
2110 {
2111 // phpcs:enable
2112 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2113
2114 // @phan-suppress-next-line PhanUndeclaredProperty
2115 if (empty($this->fk_product)) {
2116 return 0;
2117 }
2118
2119 $product = new Product($this->db);
2120 // @phan-suppress-next-line PhanUndeclaredProperty
2121 $result = $product->fetch($this->fk_product);
2122
2123 $this->product = $product;
2124 return $result;
2125 }
2126
2127 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2134 public function fetch_user($userid)
2135 {
2136 // phpcs:enable
2137 $user = new User($this->db);
2138 $result = $user->fetch($userid);
2139 $this->user = $user;
2140 return $result;
2141 }
2142
2143 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2150 public function fetch_origin()
2151 {
2152 // phpcs:enable
2153 $origin = $this->origin ? $this->origin : $this->origin_type;
2154
2155 // Manage classes with non standard name
2156 if ($origin == 'shipping') {
2157 $origin = 'expedition';
2158 }
2159 if ($origin == 'delivery') {
2160 $origin = 'livraison';
2161 }
2162 if ($origin == 'order_supplier' || $origin == 'supplier_order') {
2163 $origin = 'commandeFournisseur';
2164 }
2165
2166 $classname = ucfirst($origin);
2167 $this->origin_object = new $classname($this->db);
2168 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2169 $this->origin_object->fetch($this->origin_id);
2170 }
2171
2181 public function fetchObjectFrom($table, $field, $key, $element = null)
2182 {
2183 global $conf;
2184
2185 $result = false;
2186
2187 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2188 $sql .= " WHERE ".$field." = '".$this->db->escape($key)."'";
2189 if (!empty($element)) {
2190 $sql .= " AND entity IN (".getEntity($element).")";
2191 } else {
2192 $sql .= " AND entity = ".((int) $conf->entity);
2193 }
2194
2195 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2196 $resql = $this->db->query($sql);
2197 if ($resql) {
2198 $obj = $this->db->fetch_object($resql);
2199 // Test for avoid error -1
2200 if ($obj) {
2201 if (method_exists($this, 'fetch')) {
2202 $result = $this->fetch($obj->rowid);
2203 } else {
2204 $this->error = 'fetch() method not implemented on '.get_class($this);
2205 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2206 array_push($this->errors, $this->error);
2207 $result = -1;
2208 }
2209 }
2210 }
2211
2212 return $result;
2213 }
2214
2223 public function getValueFrom($table, $id, $field)
2224 {
2225 $result = false;
2226 if (!empty($id) && !empty($field) && !empty($table)) {
2227 $sql = "SELECT ".$field." FROM ".$this->db->prefix().$table;
2228 $sql .= " WHERE rowid = ".((int) $id);
2229
2230 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2231 $resql = $this->db->query($sql);
2232 if ($resql) {
2233 $row = $this->db->fetch_row($resql);
2234 $result = $row[0];
2235 }
2236 }
2237 return $result;
2238 }
2239
2256 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2257 {
2258 global $user;
2259
2260 if (empty($table)) {
2261 $table = $this->table_element;
2262 }
2263 if (empty($id)) {
2264 $id = $this->id;
2265 }
2266 if (empty($format)) {
2267 $format = 'text';
2268 }
2269 if (empty($id_field)) {
2270 $id_field = 'rowid';
2271 }
2272
2273 // Special case
2274 if ($table == 'product' && $field == 'note_private') {
2275 $field = 'note';
2276 }
2277
2278 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2279 $fk_user_field = 'fk_user_mod';
2280 }
2281 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2282 $fk_user_field = '';
2283 }
2284
2285 $oldvalue = null;
2286 if ($trigkey) {
2287 $sql = "SELECT " . $field;
2288 $sql .= " FROM " . MAIN_DB_PREFIX . $table;
2289 $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2290
2291 $resql = $this->db->query($sql);
2292 if ($resql) {
2293 if ($obj = $this->db->fetch_object($resql)) {
2294 if ($format == 'date') {
2295 $oldvalue = $this->db->jdate($obj->$field);
2296 } else {
2297 $oldvalue = $obj->$field;
2298 }
2299 }
2300 } else {
2301 $this->error = $this->db->lasterror();
2302 return -1;
2303 }
2304 }
2305
2306 $error = 0;
2307
2308 dol_syslog(__METHOD__, LOG_DEBUG);
2309
2310 $this->db->begin();
2311
2312 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2313
2314 if ($format == 'text') {
2315 $sql .= $field." = '".$this->db->escape($value)."'";
2316 } elseif ($format == 'int') {
2317 $sql .= $field." = ".((int) $value);
2318 } elseif ($format == 'date') {
2319 $sql .= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2320 } elseif ($format == 'dategmt') {
2321 $sql .= $field." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2322 }
2323
2324 if ($fk_user_field) {
2325 if (!empty($fuser) && is_object($fuser)) {
2326 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2327 } elseif (empty($fuser) || $fuser != 'none') {
2328 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2329 }
2330 }
2331
2332 $sql .= " WHERE ".$id_field." = ".((int) $id);
2333
2334 $resql = $this->db->query($sql);
2335 if ($resql) {
2336 if ($trigkey) {
2337 // call trigger with updated object values
2338 if (method_exists($this, 'fetch')) {
2339 $result = $this->fetch($id);
2340 } else {
2341 $result = $this->fetchCommon($id);
2342 }
2343 $this->oldcopy = clone $this;
2344 if (property_exists($this->oldcopy, $field)) {
2345 $this->oldcopy->$field = $oldvalue;
2346 }
2347
2348 if ($result >= 0) {
2349 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2350 }
2351 if ($result < 0) {
2352 $error++;
2353 }
2354 }
2355
2356 if (!$error) {
2357 if (property_exists($this, $field)) {
2358 $this->$field = $value;
2359 }
2360 $this->db->commit();
2361 return 1;
2362 } else {
2363 $this->db->rollback();
2364 return -2;
2365 }
2366 } else {
2367 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2368 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2369 } else {
2370 $this->error = $this->db->lasterror();
2371 }
2372 $this->db->rollback();
2373 return -1;
2374 }
2375 }
2376
2377 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2388 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2389 {
2390 // phpcs:enable
2391 global $conf, $user;
2392
2393 if (!$this->table_element) {
2394 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2395 return -1;
2396 }
2397 if ($fieldid == 'none') {
2398 return 1;
2399 }
2400
2401 // For backward compatibility
2402 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2403 $fieldid = 'titre';
2404 }
2405
2406 // Security on socid
2407 $socid = 0;
2408 if ($user->socid > 0) {
2409 $socid = $user->socid;
2410 }
2411
2412 // this->ismultientitymanaged contains
2413 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2414 $aliastablesociete = 's';
2415 if ($this->element == 'societe') {
2416 $aliastablesociete = 'te'; // te as table_element
2417 }
2418 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2419 $sql = "SELECT MAX(te.".$fieldid.")";
2420 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2421 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2422 $tmparray = explode('@', $this->ismultientitymanaged);
2423 $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
2424 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2425 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2426 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2427 $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
2428 }
2429 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2430 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2431 }
2432 if ($fieldid == 'rowid') {
2433 $sql .= " WHERE te.".$fieldid." < ".((int) $this->id);
2434 } else {
2435 $sql .= " WHERE te.".$fieldid." < '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2436 }
2437 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2438 $sql .= " AND sc.fk_user = ".((int) $user->id);
2439 }
2440 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2441 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2442 }
2443
2444 $filtermax = $filter;
2445
2446 // Manage filter
2447 $errormessage = '';
2448 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2449 if ($errormessage) {
2450 if (!preg_match('/^\s*AND/i', $filtermax)) {
2451 $sql .= " AND ";
2452 }
2453 $sql .= $filtermax;
2454 } else {
2455 $sql .= $tmpsql;
2456 }
2457
2458 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2459 $tmparray = explode('@', $this->ismultientitymanaged);
2460 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2461 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2462 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2463 }
2464 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2465 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2466 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2467 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2468 } else {
2469 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2470 }
2471 } else {
2472 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2473 }
2474 }
2475 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2476 $tmparray = explode('@', $this->ismultientitymanaged);
2477 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2478 }
2479 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2480 $sql .= ' AND te.fk_soc = '.((int) $socid);
2481 }
2482 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2483 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2484 }
2485 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2486 $sql .= ' AND te.rowid = '.((int) $socid);
2487 }
2488 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2489
2490 $result = $this->db->query($sql);
2491 if (!$result) {
2492 $this->error = $this->db->lasterror();
2493 return -1;
2494 }
2495 $row = $this->db->fetch_row($result);
2496 $this->ref_previous = $row[0];
2497
2498 $sql = "SELECT MIN(te.".$fieldid.")";
2499 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2500 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2501 $tmparray = explode('@', $this->ismultientitymanaged);
2502 $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
2503 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2504 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2505 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2506 $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
2507 }
2508 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2509 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2510 }
2511 if ($fieldid == 'rowid') {
2512 $sql .= " WHERE te.".$fieldid." > ".((int) $this->id);
2513 } else {
2514 $sql .= " WHERE te.".$fieldid." > '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2515 }
2516 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2517 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2518 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2519 $userschilds = $user->getAllChildIds();
2520 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2521 }
2522 $sql .= ')';
2523 }
2524 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2525 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2526 }
2527
2528 $filtermin = $filter;
2529
2530 // Manage filter
2531 $errormessage = '';
2532 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2533 if ($errormessage) {
2534 if (!preg_match('/^\s*AND/i', $filtermin)) {
2535 $sql .= " AND ";
2536 }
2537 $sql .= $filtermin;
2538
2539 $filtermin = '';
2540 } else {
2541 $sql .= $tmpsql;
2542 }
2543
2544 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2545 $tmparray = explode('@', $this->ismultientitymanaged);
2546 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2547 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2548 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2549 }
2550 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2551 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2552 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2553 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2554 } else {
2555 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2556 }
2557 } else {
2558 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2559 }
2560 }
2561 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2562 $tmparray = explode('@', $this->ismultientitymanaged);
2563 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2564 }
2565 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2566 $sql .= ' AND te.fk_soc = '.((int) $socid);
2567 }
2568 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2569 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2570 }
2571 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2572 $sql .= ' AND te.rowid = '.((int) $socid);
2573 }
2574 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2575 // 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
2576
2577 $result = $this->db->query($sql);
2578 if (!$result) {
2579 $this->error = $this->db->lasterror();
2580 return -2;
2581 }
2582 $row = $this->db->fetch_row($result);
2583 $this->ref_next = $row[0];
2584
2585 return 1;
2586 }
2587
2588
2596 public function getListContactId($source = 'external')
2597 {
2598 $contactAlreadySelected = array();
2599 $tab = $this->liste_contact(-1, $source);
2600 $num = count($tab);
2601 $i = 0;
2602 while ($i < $num) {
2603 if ($source == 'thirdparty') {
2604 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2605 } else {
2606 $contactAlreadySelected[$i] = $tab[$i]['id'];
2607 }
2608 $i++;
2609 }
2610 return $contactAlreadySelected;
2611 }
2612
2613
2621 public function setProject($projectid, $notrigger = 0)
2622 {
2623 global $user;
2624 $error = 0;
2625
2626 if (!$this->table_element) {
2627 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2628 return -1;
2629 }
2630
2631 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2632 // @phan-suppress-next-line PhanTypeMismatchProperty
2633 if (!empty($this->fields['fk_project'])) { // Common case
2634 if ($projectid) {
2635 $sql .= " SET fk_project = ".((int) $projectid);
2636 } else {
2637 $sql .= " SET fk_project = NULL";
2638 }
2639 $sql .= ' WHERE rowid = '.((int) $this->id);
2640 } elseif ($this->table_element == 'actioncomm') { // Special case for actioncomm
2641 if ($projectid) {
2642 $sql .= " SET fk_project = ".((int) $projectid);
2643 } else {
2644 $sql .= " SET fk_project = NULL";
2645 }
2646 $sql .= ' WHERE id = '.((int) $this->id);
2647 } else { // Special case for old architecture objects
2648 if ($projectid) {
2649 $sql .= ' SET fk_projet = '.((int) $projectid);
2650 } else {
2651 $sql .= ' SET fk_projet = NULL';
2652 }
2653 $sql .= " WHERE rowid = ".((int) $this->id);
2654 }
2655
2656 $this->db->begin();
2657
2658 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2659 if ($this->db->query($sql)) {
2660 $this->fk_project = ((int) $projectid);
2661 } else {
2662 dol_print_error($this->db);
2663 $error++;
2664 }
2665
2666 // Triggers
2667 if (!$error && !$notrigger) {
2668 // Call triggers
2669 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2670 if ($result < 0) {
2671 $error++;
2672 } //Do also here what you must do to rollback action if trigger fail
2673 // End call triggers
2674 }
2675
2676 // Commit or rollback
2677 if ($error) {
2678 $this->db->rollback();
2679 return -1;
2680 } else {
2681 $this->db->commit();
2682 return 1;
2683 }
2684 }
2685
2692 public function setPaymentMethods($id)
2693 {
2694 global $user;
2695
2696 $error = 0;
2697 $notrigger = 0;
2698
2699 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2700
2701 if ($this->status >= 0 || $this->element == 'societe') {
2702 // TODO uniformize field name
2703 $fieldname = 'fk_mode_reglement';
2704 if ($this->element == 'societe') {
2705 $fieldname = 'mode_reglement';
2706 }
2707 if (get_class($this) == 'Fournisseur') {
2708 $fieldname = 'mode_reglement_supplier';
2709 }
2710 if (get_class($this) == 'Tva') {
2711 $fieldname = 'fk_typepayment';
2712 }
2713 if (get_class($this) == 'Salary') {
2714 $fieldname = 'fk_typepayment';
2715 }
2716
2717 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2718 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2719 $sql .= ' WHERE rowid='.((int) $this->id);
2720
2721 if ($this->db->query($sql)) {
2722 $this->mode_reglement_id = $id;
2723 // for supplier
2724 if (get_class($this) == 'Fournisseur') {
2725 $this->mode_reglement_supplier_id = $id;
2726 }
2727 // Triggers
2728 if (!$error && !$notrigger) {
2729 // Call triggers
2730 if (get_class($this) == 'Commande') {
2731 $result = $this->call_trigger('ORDER_MODIFY', $user);
2732 } else {
2733 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
2734 }
2735 if ($result < 0) {
2736 $error++;
2737 }
2738 // End call triggers
2739 }
2740 return 1;
2741 } else {
2742 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2743 $this->error = $this->db->error();
2744 return -1;
2745 }
2746 } else {
2747 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2748 $this->error = 'Status of the object is incompatible '.$this->status;
2749 return -2;
2750 }
2751 }
2752
2759 public function setMulticurrencyCode($code)
2760 {
2761 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2762 if ($this->status >= 0 || $this->element == 'societe') {
2763 $fieldname = 'multicurrency_code';
2764
2765 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2766 $sql .= " SET ".$fieldname." = '".$this->db->escape($code)."'";
2767 $sql .= ' WHERE rowid='.((int) $this->id);
2768
2769 if ($this->db->query($sql)) {
2770 $this->multicurrency_code = $code;
2771
2772 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2773 if ($rate) {
2774 $this->setMulticurrencyRate($rate, 2);
2775 }
2776
2777 return 1;
2778 } else {
2779 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2780 $this->error = $this->db->error();
2781 return -1;
2782 }
2783 } else {
2784 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2785 $this->error = 'Status of the object is incompatible '.$this->status;
2786 return -2;
2787 }
2788 }
2789
2797 public function setMulticurrencyRate($rate, $mode = 1)
2798 {
2799 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2800 if ($this->status >= 0 || $this->element == 'societe') {
2801 $fieldname = 'multicurrency_tx';
2802
2803 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2804 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".((float) $rate);
2805 $sql .= ' WHERE rowid='.((int) $this->id);
2806
2807 if ($this->db->query($sql)) {
2808 $this->multicurrency_tx = $rate;
2809
2810 // Update line price
2811 if (!empty($this->lines)) {
2812 foreach ($this->lines as &$line) {
2813 // Amounts in company currency will be recalculated
2814 if ($mode == 1) {
2815 $line->subprice = 0;
2816 }
2817
2818 // Amounts in foreign currency will be recalculated
2819 if ($mode == 2) {
2820 $line->multicurrency_subprice = 0;
2821 }
2822
2823 switch ($this->element) {
2824 case 'propal':
2827 '@phan-var-force Propal $this';
2828 '@phan-var-force PropaleLigne $line';
2829 $this->updateline(
2830 $line->id,
2831 $line->subprice,
2832 $line->qty,
2833 $line->remise_percent,
2834 $line->tva_tx,
2835 $line->localtax1_tx,
2836 $line->localtax2_tx,
2837 ($line->description ? $line->description : $line->desc),
2838 'HT',
2839 $line->info_bits,
2840 $line->special_code,
2841 $line->fk_parent_line,
2842 $line->skip_update_total,
2843 $line->fk_fournprice,
2844 $line->pa_ht,
2845 $line->label,
2846 $line->product_type,
2847 $line->date_start,
2848 $line->date_end,
2849 $line->array_options,
2850 $line->fk_unit,
2851 $line->multicurrency_subprice
2852 );
2853 break;
2854 case 'commande':
2857 '@phan-var-force Commande $this';
2858 '@phan-var-force OrderLine $line';
2859 $this->updateline(
2860 $line->id,
2861 ($line->description ? $line->description : $line->desc),
2862 $line->subprice,
2863 $line->qty,
2864 $line->remise_percent,
2865 $line->tva_tx,
2866 $line->localtax1_tx,
2867 $line->localtax2_tx,
2868 'HT',
2869 $line->info_bits,
2870 $line->date_start,
2871 $line->date_end,
2872 $line->product_type,
2873 $line->fk_parent_line,
2874 $line->skip_update_total,
2875 $line->fk_fournprice,
2876 $line->pa_ht,
2877 $line->label,
2878 $line->special_code,
2879 $line->array_options,
2880 $line->fk_unit,
2881 $line->multicurrency_subprice
2882 );
2883 break;
2884 case 'facture':
2887 '@phan-var-force Facture $this';
2888 '@phan-var-force FactureLigne $line';
2889 $this->updateline(
2890 $line->id,
2891 ($line->description ? $line->description : $line->desc),
2892 $line->subprice,
2893 $line->qty,
2894 $line->remise_percent,
2895 $line->date_start,
2896 $line->date_end,
2897 $line->tva_tx,
2898 $line->localtax1_tx,
2899 $line->localtax2_tx,
2900 'HT',
2901 $line->info_bits,
2902 $line->product_type,
2903 $line->fk_parent_line,
2904 $line->skip_update_total,
2905 $line->fk_fournprice,
2906 $line->pa_ht,
2907 $line->label,
2908 $line->special_code,
2909 $line->array_options,
2910 $line->situation_percent,
2911 $line->fk_unit,
2912 $line->multicurrency_subprice
2913 );
2914 break;
2915 case 'facturerec':
2918 '@phan-var-force FactureRec $this';
2919 '@phan-var-force FactureLigneRec $line';
2920 $this->updateline(
2921 $line->id,
2922 ($line->description ? $line->description : $line->desc),
2923 $line->subprice,
2924 $line->qty,
2925 $line->tva_tx,
2926 $line->localtax1_tx,
2927 $line->localtax2_tx,
2928 $line->fk_product,
2929 $line->remise_percent,
2930 'HT',
2931 $line->info_bits,
2932 0,
2933 0,
2934 $line->product_type,
2935 $line->rang,
2936 $line->special_code,
2937 $line->label,
2938 $line->fk_unit,
2939 $line->multicurrency_subprice,
2940 0,
2941 $line->date_start,
2942 $line->date_end,
2943 $line->fk_fournprice,
2944 $line->pa_ht,
2945 $line->fk_parent_line
2946 );
2947 break;
2948 case 'supplier_proposal':
2951 '@phan-var-force SupplierProposal $this';
2952 '@phan-var-force SupplierProposalLine $line';
2953 $this->updateline(
2954 $line->id,
2955 $line->subprice,
2956 $line->qty,
2957 $line->remise_percent,
2958 $line->tva_tx,
2959 $line->localtax1_tx,
2960 $line->localtax2_tx,
2961 ($line->description ? $line->description : $line->desc),
2962 'HT',
2963 $line->info_bits,
2964 $line->special_code,
2965 $line->fk_parent_line,
2966 $line->skip_update_total,
2967 $line->fk_fournprice,
2968 $line->pa_ht,
2969 $line->label,
2970 $line->product_type,
2971 $line->array_options,
2972 $line->ref_fourn,
2973 (int) $line->fk_unit,
2974 $line->multicurrency_subprice
2975 );
2976 break;
2977 case 'order_supplier':
2980 '@phan-var-force CommandeFournisseur $this';
2981 '@phan-var-force CommandeFournisseurLigne $line';
2982 $this->updateline(
2983 $line->id,
2984 ($line->description ? $line->description : $line->desc),
2985 $line->subprice,
2986 $line->qty,
2987 $line->remise_percent,
2988 $line->tva_tx,
2989 $line->localtax1_tx,
2990 $line->localtax2_tx,
2991 'HT',
2992 $line->info_bits,
2993 $line->product_type,
2994 0,
2995 $line->date_start,
2996 $line->date_end,
2997 $line->array_options,
2998 $line->fk_unit,
2999 $line->multicurrency_subprice,
3000 $line->ref_supplier
3001 );
3002 break;
3003 case 'invoice_supplier':
3006 '@phan-var-force FactureFournisseur $this';
3007 '@phan-var-force SupplierInvoiceLIne $line';
3008 $this->updateline(
3009 $line->id,
3010 ($line->description ? $line->description : $line->desc),
3011 $line->subprice,
3012 $line->tva_tx,
3013 $line->localtax1_tx,
3014 $line->localtax2_tx,
3015 $line->qty,
3016 0,
3017 'HT',
3018 $line->info_bits,
3019 $line->product_type,
3020 $line->remise_percent,
3021 0,
3022 $line->date_start,
3023 $line->date_end,
3024 $line->array_options,
3025 $line->fk_unit,
3026 $line->multicurrency_subprice,
3027 $line->ref_supplier
3028 );
3029 break;
3030 default:
3031 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3032 break;
3033 }
3034 }
3035 }
3036
3037 return 1;
3038 } else {
3039 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3040 $this->error = $this->db->error();
3041 return -1;
3042 }
3043 } else {
3044 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3045 $this->error = 'Status of the object is incompatible '.$this->status;
3046 return -2;
3047 }
3048 }
3049
3057 public function setPaymentTerms($id, $deposit_percent = null)
3058 {
3059 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.var_export($deposit_percent, true).')');
3060 if ($this->status >= 0 || $this->element == 'societe') {
3061 // TODO uniformize field name
3062 $fieldname = 'fk_cond_reglement';
3063 if ($this->element == 'societe') {
3064 $fieldname = 'cond_reglement';
3065 }
3066 if (get_class($this) == 'Fournisseur') {
3067 $fieldname = 'cond_reglement_supplier';
3068 }
3069
3070 if (empty($deposit_percent) || $deposit_percent < 0) {
3071 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3072 }
3073
3074 if ($deposit_percent > 100) {
3075 $deposit_percent = 100;
3076 }
3077
3078 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3079 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3080 if (in_array($this->table_element, array('propal', 'commande', 'societe'))) {
3081 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3082 }
3083 $sql .= ' WHERE rowid='.((int) $this->id);
3084
3085 if ($this->db->query($sql)) {
3086 $this->cond_reglement_id = $id;
3087 // for supplier
3088 if (get_class($this) == 'Fournisseur') {
3089 $this->cond_reglement_supplier_id = $id;
3090 }
3091 $this->deposit_percent = $deposit_percent;
3092 return 1;
3093 } else {
3094 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3095 $this->error = $this->db->error();
3096 return -1;
3097 }
3098 } else {
3099 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3100 $this->error = 'Status of the object is incompatible '.$this->status;
3101 return -2;
3102 }
3103 }
3104
3111 public function setTransportMode($id)
3112 {
3113 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3114 if ($this->status >= 0 || $this->element == 'societe') {
3115 $fieldname = 'fk_transport_mode';
3116 if ($this->element == 'societe') {
3117 $fieldname = 'transport_mode';
3118 }
3119 if (get_class($this) == 'Fournisseur') {
3120 $fieldname = 'transport_mode_supplier';
3121 }
3122
3123 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3124 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3125 $sql .= ' WHERE rowid='.((int) $this->id);
3126
3127 if ($this->db->query($sql)) {
3128 $this->transport_mode_id = $id;
3129 // for supplier
3130 if (get_class($this) == 'Fournisseur') {
3131 $this->transport_mode_supplier_id = $id;
3132 }
3133 return 1;
3134 } else {
3135 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3136 $this->error = $this->db->error();
3137 return -1;
3138 }
3139 } else {
3140 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3141 $this->error = 'Status of the object is incompatible '.$this->status;
3142 return -2;
3143 }
3144 }
3145
3153 {
3154 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
3155 if ($this->status >= 0 || $this->element == 'societe') {
3156 $fieldname = 'retained_warranty_fk_cond_reglement';
3157
3158 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3159 $sql .= " SET ".$fieldname." = ".((int) $id);
3160 $sql .= ' WHERE rowid='.((int) $this->id);
3161
3162 if ($this->db->query($sql)) {
3163 $this->retained_warranty_fk_cond_reglement = $id;
3164 return 1;
3165 } else {
3166 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
3167 $this->error = $this->db->error();
3168 return -1;
3169 }
3170 } else {
3171 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3172 $this->error = 'Status of the object is incompatible '.$this->status;
3173 return -2;
3174 }
3175 }
3176
3184 public function setDeliveryAddress($id)
3185 {
3186 $fieldname = 'fk_delivery_address';
3187 if ($this->element == 'delivery' || $this->element == 'shipping') {
3188 $fieldname = 'fk_address';
3189 }
3190
3191 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$fieldname." = ".((int) $id);
3192 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3193
3194 if ($this->db->query($sql)) {
3195 $this->fk_delivery_address = $id;
3196 return 1;
3197 } else {
3198 $this->error = $this->db->error();
3199 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3200 return -1;
3201 }
3202 }
3203
3204
3213 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3214 {
3215 global $user;
3216
3217 if (empty($userused)) {
3218 $userused = $user;
3219 }
3220
3221 $error = 0;
3222
3223 if (!$this->table_element) {
3224 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3225 return -1;
3226 }
3227
3228 $this->db->begin();
3229
3230 if ($shipping_method_id < 0) {
3231 $shipping_method_id = 'NULL';
3232 }
3233 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3234
3235 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3236 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3237 $sql .= " WHERE rowid=".((int) $this->id);
3238 $resql = $this->db->query($sql);
3239 if (!$resql) {
3240 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3241 $this->error = $this->db->lasterror();
3242 $error++;
3243 } else {
3244 if (!$notrigger) {
3245 // Call trigger
3246 $this->context = array('shippingmethodupdate' => 1);
3247 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
3248 if ($result < 0) {
3249 $error++;
3250 }
3251 // End call trigger
3252 }
3253 }
3254 if ($error) {
3255 $this->db->rollback();
3256 return -1;
3257 } else {
3258 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3259 $this->db->commit();
3260 return 1;
3261 }
3262 }
3263
3264
3271 public function setWarehouse($warehouse_id)
3272 {
3273 if (!$this->table_element) {
3274 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3275 return -1;
3276 }
3277 if ($warehouse_id < 0) {
3278 $warehouse_id = 'NULL';
3279 }
3280 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3281
3282 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3283 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3284 $sql .= " WHERE rowid=".((int) $this->id);
3285
3286 if ($this->db->query($sql)) {
3287 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3288 return 1;
3289 } else {
3290 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3291 $this->error = $this->db->error();
3292 return 0;
3293 }
3294 }
3295
3296
3304 public function setDocModel($user, $modelpdf)
3305 {
3306 if (!$this->table_element) {
3307 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3308 return -1;
3309 }
3310
3311 $newmodelpdf = dol_trunc($modelpdf, 255);
3312
3313 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3314 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3315 $sql .= " WHERE rowid = ".((int) $this->id);
3316
3317 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3318 $resql = $this->db->query($sql);
3319 if ($resql) {
3320 $this->model_pdf = $modelpdf;
3321 return 1;
3322 } else {
3323 dol_print_error($this->db);
3324 return 0;
3325 }
3326 }
3327
3328
3337 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3338 {
3339 global $user;
3340
3341 if (empty($userused)) {
3342 $userused = $user;
3343 }
3344
3345 $error = 0;
3346
3347 if (!$this->table_element) {
3348 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3349 return -1;
3350 }
3351 $this->db->begin();
3352
3353 if ($fk_account < 0) {
3354 $fk_account = 'NULL';
3355 }
3356 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3357
3358 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3359 $sql .= " SET fk_account = ".((int) $fk_account);
3360 $sql .= " WHERE rowid=".((int) $this->id);
3361
3362 $resql = $this->db->query($sql);
3363 if (!$resql) {
3364 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3365 $this->error = $this->db->lasterror();
3366 $error++;
3367 } else {
3368 if (!$notrigger) {
3369 // Call trigger
3370 $this->context['bankaccountupdate'] = 1;
3371 $triggerName = strtoupper(get_class($this)).'_MODIFY';
3372 // Special cases
3373 if ($triggerName == 'FACTUREREC_MODIFY') {
3374 $triggerName = 'BILLREC_MODIFY';
3375 }
3376 $result = $this->call_trigger($triggerName, $userused);
3377 if ($result < 0) {
3378 $error++;
3379 }
3380 // End call trigger
3381 }
3382 }
3383 if ($error) {
3384 $this->db->rollback();
3385 return -1;
3386 } else {
3387 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3388 $this->db->commit();
3389 return 1;
3390 }
3391 }
3392
3393
3394 // TODO: Move line related operations to CommonObjectLine?
3395
3396 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3406 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3407 {
3408 // phpcs:enable
3409 if (!$this->table_element_line) {
3410 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3411 return -1;
3412 }
3413 if (!$this->fk_element) {
3414 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3415 return -1;
3416 }
3417
3418 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3419 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3420 $fieldposition = 'position';
3421 }
3422
3423 // Count number of lines to reorder (according to choice $renum)
3424 $nl = 0;
3425 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->table_element_line;
3426 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3427 if (!$renum) {
3428 $sql .= " AND " . $fieldposition . " = 0";
3429 }
3430 if ($renum) {
3431 $sql .= " AND " . $fieldposition . " <> 0";
3432 }
3433
3434 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3435 $resql = $this->db->query($sql);
3436 if ($resql) {
3437 $row = $this->db->fetch_row($resql);
3438 $nl = $row[0];
3439 } else {
3440 dol_print_error($this->db);
3441 }
3442 if ($nl > 0) {
3443 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3444 $rows = array();
3445
3446 // We first search all lines that are parent lines (for multilevel details lines)
3447 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3448 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3449 if ($fk_parent_line) {
3450 $sql .= ' AND fk_parent_line IS NULL';
3451 }
3452 $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3453
3454 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3455 $resql = $this->db->query($sql);
3456 if ($resql) {
3457 $i = 0;
3458 $num = $this->db->num_rows($resql);
3459 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3460 while ($i < $num) {
3461 $row = $this->db->fetch_row($resql);
3462 $rows[] = $row[0]; // Add parent line into array rows
3463 if ($fk_parent_line) {
3464 $children = $this->getChildrenOfLine($row[0], $grandchild);
3465 }
3466 if (!empty($children)) {
3467 foreach ($children as $child) {
3468 array_push($rows, $child);
3469 }
3470 }
3471 $i++;
3472 }
3473
3474 // Now we set a new number for each lines (parent and children with children included into parent tree)
3475 if (!empty($rows)) {
3476 foreach ($rows as $key => $row) {
3477 $this->updateRangOfLine($row, ($key + 1));
3478 }
3479 }
3480 } else {
3481 dol_print_error($this->db);
3482 }
3483 }
3484 return 1;
3485 }
3486
3494 public function getChildrenOfLine($id, $includealltree = 0)
3495 {
3496 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3497 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3498 $fieldposition = 'position';
3499 }
3500
3501 $rows = array();
3502
3503 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3504 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3505 $sql .= ' AND fk_parent_line = '.((int) $id);
3506 $sql .= " ORDER BY " . $fieldposition . " ASC";
3507
3508 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3509
3510 $resql = $this->db->query($sql);
3511 if (!$resql || $this->db->num_rows($resql) <= 0) {
3512 return array();
3513 }
3514
3515 while ($row = $this->db->fetch_row($resql)) {
3516 $rows[] = $row[0];
3517 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3518 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3519 }
3520 }
3521 return $rows;
3522 }
3523
3524 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3532 public function line_up($rowid, $fk_parent_line = true)
3533 {
3534 // phpcs:enable
3535 $this->line_order(false, 'ASC', $fk_parent_line);
3536
3537 // Get rang of line
3538 $rang = $this->getRangOfLine($rowid);
3539
3540 // Update position of line
3541 $this->updateLineUp($rowid, $rang);
3542 }
3543
3544 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3552 public function line_down($rowid, $fk_parent_line = true)
3553 {
3554 // phpcs:enable
3555 $this->line_order(false, 'ASC', $fk_parent_line);
3556
3557 // Get rang of line
3558 $rang = $this->getRangOfLine($rowid);
3559
3560 // Get max value for rang
3561 $max = $this->line_max();
3562
3563 // Update position of line
3564 $this->updateLineDown($rowid, $rang, $max);
3565 }
3566
3574 public function updateRangOfLine($rowid, $rang)
3575 {
3576 global $hookmanager;
3577 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3578 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3579 $fieldposition = 'position';
3580 }
3581
3582 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3583 $sql .= ' WHERE rowid = '.((int) $rowid);
3584 if ($this->id > 0 && !empty($this->fk_element)) {
3585 // The line must belong to the object we reorder lines of
3586 $sql .= " AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
3587 }
3588
3589 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3590 if (!$this->db->query($sql)) {
3591 dol_print_error($this->db);
3592 return -1;
3593 }
3594
3595 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3596 $action = '';
3597 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3598 return 1;
3599 }
3600
3601 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3608 public function line_ajaxorder($rows)
3609 {
3610 // phpcs:enable
3611 $num = count($rows);
3612 for ($i = 0; $i < $num; $i++) {
3613 $this->updateRangOfLine($rows[$i], ($i + 1));
3614 }
3615 }
3616
3624 public function updateLineUp($rowid, $rang)
3625 {
3626 if ($rang <= 1) {
3627 return -1;
3628 }
3629
3630 $fieldposition = 'rang';
3631 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3632 $fieldposition = 'position';
3633 }
3634
3635 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3636 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3637 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3638 if (!$this->db->query($sql)) {
3639 $this->error = $this->db->lasterror();
3640 return -1;
3641 }
3642
3643 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3644 $sql .= ' WHERE rowid = '.((int) $rowid);
3645 if (!$this->db->query($sql)) {
3646 $this->error = $this->db->lasterror();
3647 return -1;
3648 }
3649
3650 return 1;
3651 }
3652
3661 public function updateLineDown($rowid, $rang, $max)
3662 {
3663 if ($rang >= $max) {
3664 return -1;
3665 }
3666
3667 $fieldposition = 'rang';
3668 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3669 $fieldposition = 'position';
3670 }
3671
3672 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3673 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3674 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3675 if (!$this->db->query($sql)) {
3676 $this->error = $this->db->lasterror();
3677 return -1;
3678 }
3679
3680 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3681 $sql .= ' WHERE rowid = '.((int) $rowid);
3682 if (!$this->db->query($sql)) {
3683 $this->error = $this->db->lasterror();
3684 return -1;
3685 }
3686
3687 return 1;
3688 }
3689
3696 public function getRangOfLine($rowid)
3697 {
3698 $fieldposition = 'rang';
3699 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3700 $fieldposition = 'position';
3701 }
3702
3703 $sql = "SELECT " . $fieldposition . " FROM ".$this->db->prefix().$this->table_element_line;
3704 $sql .= " WHERE rowid = ".((int) $rowid);
3705
3706 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3707 $resql = $this->db->query($sql);
3708 if (!$resql) {
3709 return 0;
3710 }
3711
3712 $row = $this->db->fetch_row($resql);
3713 return $row[0];
3714 }
3715
3722 public function getIdOfLine($rang)
3723 {
3724 $fieldposition = 'rang';
3725 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3726 $fieldposition = 'position';
3727 }
3728
3729 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3730 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3731 $sql .= " AND " . $fieldposition . " = ".((int) $rang);
3732 $resql = $this->db->query($sql);
3733 if (!$resql) {
3734 return 0;
3735 }
3736
3737 $row = $this->db->fetch_row($resql);
3738 return $row[0];
3739 }
3740
3741 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3748 public function line_max($fk_parent_line = 0)
3749 {
3750 // phpcs:enable
3751 $positionfield = 'rang';
3752 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3753 $positionfield = 'position';
3754 }
3755
3756 $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3757 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3758
3759 // Search the last rang with fk_parent_line
3760 if ($fk_parent_line) {
3761 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3762 }
3763
3764 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3765 $resql = $this->db->query($sql);
3766 if ($resql) {
3767 $row = $this->db->fetch_row($resql);
3768
3769 if ($fk_parent_line) {
3770 if (!empty($row[0])) {
3771 return $row[0];
3772 } else {
3773 return $this->getRangOfLine($fk_parent_line);
3774 }
3775 } else {
3776 return $row[0];
3777 }
3778 }
3779
3780 return 0;
3781 }
3782
3783 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3790 public function update_ref_ext($ref_ext)
3791 {
3792 // phpcs:enable
3793 if (!$this->table_element) {
3794 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3795 return -1;
3796 }
3797
3798 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3799 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3800 // @phan-suppress-next-line PhanUndeclaredProperty
3801 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3802
3803 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3804 if ($this->db->query($sql)) {
3805 $this->ref_ext = $ref_ext;
3806 return 1;
3807 } else {
3808 $this->error = $this->db->error();
3809 return -1;
3810 }
3811 }
3812
3813 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3822 public function update_note($note, $suffix = '', $notrigger = 0)
3823 {
3824 // phpcs:enable
3825 global $user;
3826
3827 if (!$this->table_element) {
3828 $this->error = 'update_note was called on object with property table_element not defined';
3829 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3830 return -1;
3831 }
3832 if (!in_array($suffix, array('', '_public', '_private'))) {
3833 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3834 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3835 return -2;
3836 }
3837
3838 $newsuffix = $suffix;
3839
3840 // Special case
3841 if ($this->table_element == 'product' && $newsuffix == '_private') {
3842 $newsuffix = '';
3843 }
3844 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3845 $fieldusermod = "fk_user_mod";
3846 } elseif ($this->table_element == 'ecm_files') {
3847 $fieldusermod = "fk_user_m";
3848 } else {
3849 $fieldusermod = "fk_user_modif";
3850 }
3851 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3852 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3853 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3854 $sql .= " WHERE rowid = ".((int) $this->id);
3855
3856 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3857 if ($this->db->query($sql)) {
3858 if ($suffix == '_public') {
3859 $this->note_public = $note;
3860 } elseif ($suffix == '_private') {
3861 $this->note_private = $note;
3862 } else {
3863 $this->note = $note; // deprecated
3864 $this->note_private = $note;
3865 }
3866 if (empty($notrigger)) {
3867 switch ($this->element) {
3868 case 'societe':
3869 $trigger_name = 'COMPANY_MODIFY';
3870 break;
3871 case 'commande':
3872 $trigger_name = 'ORDER_MODIFY';
3873 break;
3874 case 'facture':
3875 $trigger_name = 'BILL_MODIFY';
3876 break;
3877 case 'invoice_supplier':
3878 $trigger_name = 'BILL_SUPPLIER_MODIFY';
3879 break;
3880 case 'facturerec':
3881 $trigger_name = 'BILLREC_MODIFIY';
3882 break;
3883 case 'expensereport':
3884 $trigger_name = 'EXPENSE_REPORT_MODIFY';
3885 break;
3886 default:
3887 $trigger_name = strtoupper($this->element) . '_MODIFY';
3888 }
3889 $ret = $this->call_trigger($trigger_name, $user);
3890 if ($ret < 0) {
3891 return -1;
3892 }
3893 }
3894 return 1;
3895 } else {
3896 $this->error = $this->db->lasterror();
3897 return -1;
3898 }
3899 }
3900
3901 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3910 public function update_note_public($note)
3911 {
3912 // phpcs:enable
3913 return $this->update_note($note, '_public');
3914 }
3915
3916 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3927 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
3928 {
3929 // phpcs:enable
3930 global $conf, $hookmanager, $action;
3931
3932 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3933 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3934 if ($reshook > 0) {
3935 return 1; // replacement code
3936 } elseif ($reshook < 0) {
3937 return -1; // failure
3938 } // reshook = 0 => execute normal code
3939
3940 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3941 $isElementForSupplier = false;
3942 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
3943 $MODULE = "";
3944 if ($this->element == 'propal') {
3945 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3946 } elseif ($this->element == 'commande' || $this->element == 'order') {
3947 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3948 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
3949 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3950 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
3951 $isElementForSupplier = true;
3952 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3953 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3954 $isElementForSupplier = true;
3955 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3956 } elseif ($this->element == 'supplier_proposal') {
3957 $isElementForSupplier = true;
3958 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3959 }
3960 if ($isElementForSupplier) {
3961 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
3962 }
3963
3964 if (!empty($MODULE)) {
3965 if (getDolGlobalString($MODULE)) {
3966 $modsactivated = explode(',', getDolGlobalString($MODULE));
3967 foreach ($modsactivated as $mod) {
3968 if (isModEnabled($mod)) {
3969 return 1; // update was disabled by specific setup
3970 }
3971 }
3972 }
3973 }
3974
3975 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3976
3977 $forcedroundingmode = $roundingadjust;
3978 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
3979 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
3980 } elseif ($forcedroundingmode == 'auto') {
3981 $forcedroundingmode = '0';
3982 }
3983
3984 $error = 0;
3985
3986 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3987
3988 // Define constants to find lines to sum (field name int the table_element_line not into table_element)
3989 $fieldtva = 'total_tva';
3990 $fieldlocaltax1 = 'total_localtax1';
3991 $fieldlocaltax2 = 'total_localtax2';
3992 $fieldup = 'subprice';
3993 $base_price_type = 'HT';
3994 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3995 $fieldtva = 'tva';
3996 $fieldup = 'pu_ht';
3997 }
3998 if ($this->element == 'invoice_supplier_rec') {
3999 $fieldup = 'pu_ht';
4000 }
4001 if ($this->element == 'expensereport') {
4002 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
4003 // 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
4004 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
4005 $forcedroundingmode = '0';
4006 $fieldup = 'value_unit';
4007 $base_price_type = 'TTC';
4008 }
4009
4010 $sql = "SELECT rowid, qty, ".$this->db->sanitize($fieldup)." as up, remise_percent, 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,";
4011 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
4012 if ($this->table_element_line == 'facturedet') {
4013 $sql .= ', situation_percent';
4014 }
4015 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4016 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
4017 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4018 if ($exclspec) {
4019 $product_field = 'product_type';
4020 if ($this->table_element_line == 'contratdet') {
4021 $product_field = ''; // contratdet table has no product_type field
4022 }
4023 if ($product_field) {
4024 $sql .= " AND ".$product_field." <> 9";
4025 }
4026 }
4027 $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
4028
4029 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4030
4031 $resql = $this->db->query($sql);
4032 if ($resql) {
4033 $this->total_ht = 0;
4034 $this->total_tva = 0;
4035 $this->total_localtax1 = 0;
4036 $this->total_localtax2 = 0;
4037 $this->total_ttc = 0;
4038 $total_ht_by_vats = array();
4039 $total_tva_by_vats = array();
4040 $total_ttc_by_vats = array();
4041 $this->multicurrency_total_ht = 0;
4042 $this->multicurrency_total_tva = 0;
4043 $this->multicurrency_total_ttc = 0;
4044
4045 $this->db->begin();
4046
4047 $num = $this->db->num_rows($resql);
4048 $i = 0;
4049 while ($i < $num) {
4050 $obj = $this->db->fetch_object($resql);
4051
4052 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4053 $parameters = array('fk_element' => $obj->rowid);
4054 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4055
4056 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'
4057 // This part of code is to fix data. We should not call it too often.
4058 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4059 $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);
4060
4061 $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.
4062 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4063 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4064 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4065
4066 if ($diff_on_current_total) {
4067 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4068 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4069 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4070 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4071 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4072 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);
4073 $resqlfix = $this->db->query($sqlfix);
4074 if (!$resqlfix) {
4075 dol_print_error($this->db, 'Failed to update line');
4076 }
4077 $obj->total_tva = $tmpcal[1];
4078 $obj->total_ttc = $tmpcal[2];
4079 $obj->multicurrency_total_tva = $tmpcal[17];
4080 $obj->multicurrency_total_ttc = $tmpcal[18];
4081 } elseif ($diff_when_using_price_ht) {
4082 // 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
4083 // 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).
4084 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4085 // After calculation from HT, total is consistent and total_ht is same, but we have found a difference between VAT part calculated from unit price and the VAT part into database,
4086 // and we ask to force the use of rounding on line (like done on calculation) so this should not happen, so we force the update of line to fix.
4087
4088 // This part of code must be called only to fix corrupted data due to the use of the feature to round total instead of rounding lines.
4089 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4090 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4091 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4092 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4093 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);
4094 $resqlfix = $this->db->query($sqlfix);
4095 if (!$resqlfix) {
4096 dol_print_error($this->db, 'Failed to update line');
4097 }
4098 $obj->total_tva = $tmpcal[1];
4099 $obj->total_ttc = $tmpcal[2];
4100 $obj->multicurrency_total_tva = $tmpcal[17];
4101 $obj->multicurrency_total_ttc = $tmpcal[18];
4102 }
4103 }
4104 }
4105
4106 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4107 $this->total_tva += $obj->total_tva;
4108 $this->total_localtax1 += $obj->total_localtax1;
4109 $this->total_localtax2 += $obj->total_localtax2;
4110 $this->total_ttc += $obj->total_ttc;
4111 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4112 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4113 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4114
4115 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4116 $total_ht_by_vats[$obj->vatrate] = 0;
4117 }
4118 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4119 $total_tva_by_vats[$obj->vatrate] = 0;
4120 }
4121 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4122 $total_ttc_by_vats[$obj->vatrate] = 0;
4123 }
4124 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4125 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4126 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4127
4128 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4129 if ($base_price_type == 'TTC') {
4130 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4131 } else {
4132 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4133 }
4134 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4135 //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";
4136 if ($diff) {
4137 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4138 if (abs((float) $diff) > $maxdiff) {
4139 // If error is more than 10 times the accuracy of rounding. This should not happen.
4140 $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.';
4141 dol_syslog($errmsg, LOG_WARNING);
4142 $this->error = $errmsg;
4143 $error++;
4144 break;
4145 }
4146
4147 if ($base_price_type == 'TTC') {
4148 $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);
4149 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);
4150 } else {
4151 $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);
4152 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);
4153 }
4154
4155 $resqlfix = $this->db->query($sqlfix);
4156
4157 if (!$resqlfix) {
4158 dol_print_error($this->db, 'Failed to update line');
4159 }
4160
4161 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4162 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4163 if ($base_price_type == 'TTC') {
4164 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4165 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4166 } else {
4167 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4168 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4169 }
4170 }
4171 }
4172
4173 $i++;
4174 }
4175
4176 // Add revenue stamp to total
4177 // @phan-suppress-next-line PhanUndeclaredProperty
4178 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4179 // @phan-suppress-next-line PhanUndeclaredProperty
4180 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4181
4182 // Situations totals
4183 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
4184 '@phan-var-force Facture $this';
4185 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
4186 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4187 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4188 $prev_sits = $this->get_prev_sits();
4189
4190 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4191 $this->total_ht -= $sit->total_ht;
4192 $this->total_tva -= $sit->total_tva;
4193 $this->total_localtax1 -= $sit->total_localtax1;
4194 $this->total_localtax2 -= $sit->total_localtax2;
4195 $this->total_ttc -= $sit->total_ttc;
4196 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4197 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4198 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4199 }
4200 }
4201 }
4202 }
4203
4204 // Clean total
4205 $this->total_ht = (float) price2num($this->total_ht);
4206 $this->total_tva = (float) price2num($this->total_tva);
4207 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4208 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4209 $this->total_ttc = (float) price2num($this->total_ttc);
4210
4211 $this->db->free($resql);
4212
4213 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4214 $fieldht = 'total_ht';
4215 $fieldtva = 'tva';
4216 $fieldlocaltax1 = 'localtax1';
4217 $fieldlocaltax2 = 'localtax2';
4218 $fieldttc = 'total_ttc';
4219 // Specific code for backward compatibility with old field names
4220 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4221 $fieldtva = 'total_tva';
4222 }
4223
4224 if (!$error && empty($nodatabaseupdate)) {
4225 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4226 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4227 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4228 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4229 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4230 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4231 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4232 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4233 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4234 $sql .= " WHERE rowid = ".((int) $this->id);
4235
4236 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4237 $resql = $this->db->query($sql);
4238
4239 if (!$resql) {
4240 $error++;
4241 $this->error = $this->db->lasterror();
4242 $this->errors[] = $this->db->lasterror();
4243 }
4244 }
4245
4246 if (!$error) {
4247 $this->db->commit();
4248 return 1;
4249 } else {
4250 $this->db->rollback();
4251 return -1;
4252 }
4253 } else {
4254 dol_print_error($this->db, 'Bad request in update_price');
4255 return -1;
4256 }
4257 }
4258
4259 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4270 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4271 {
4272 // phpcs:enable
4273 global $user, $hookmanager, $action;
4274
4275 if (empty($this->origin_type) && !empty($this->origin)) {
4276 $this->origin_type = $this->origin;
4277 }
4278 $origin = (!empty($origin) ? $origin : $this->origin_type);
4279 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4280 $f_user = isset($f_user) ? $f_user : $user;
4281
4282 // Special case
4283 if ($origin == 'order') {
4284 $origin = 'commande';
4285 }
4286 if ($origin == 'invoice') {
4287 $origin = 'facture';
4288 }
4289 if ($origin == 'invoice_template') {
4290 $origin = 'facturerec';
4291 }
4292 if ($origin == 'supplierorder') {
4293 $origin = 'order_supplier';
4294 }
4295
4296 // Add module part to target type
4297 $targettype = $this->getElementType();
4298
4299 $parameters = array('targettype' => $targettype);
4300 // Hook for explicitly set the targettype if it must be different than $this->element
4301 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4302 if ($reshook > 0) {
4303 if (!empty($hookmanager->resArray['targettype'])) {
4304 $targettype = $hookmanager->resArray['targettype'];
4305 }
4306 }
4307
4308 $this->db->begin();
4309 $error = 0;
4310
4311 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4312 $sql .= "fk_source";
4313 $sql .= ", sourcetype";
4314 $sql .= ", fk_target";
4315 $sql .= ", targettype";
4316 $sql .= ") VALUES (";
4317 $sql .= ((int) $origin_id);
4318 $sql .= ", '" . $this->db->escape($origin) . "'";
4319 $sql .= ", " . ((int) $this->id);
4320 $sql .= ", '" . $this->db->escape($targettype) . "'";
4321 $sql .= ")";
4322
4323 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4324 if ($this->db->query($sql)) {
4325 if (!$notrigger) {
4326 // Call trigger
4327 $this->context['link_origin'] = $origin;
4328 $this->context['link_origin_id'] = $origin_id;
4329 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
4330 if ($result < 0) {
4331 $error++;
4332 }
4333 // End call triggers
4334 }
4335 } else {
4336 $this->error = $this->db->lasterror();
4337 $error++;
4338 }
4339
4340 if (!$error) {
4341 $this->db->commit();
4342 return 1;
4343 } else {
4344 $this->db->rollback();
4345 return 0;
4346 }
4347 }
4348
4354 public function getElementType()
4355 {
4356 // 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.
4357 // 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).
4358 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4359 // Add module part to target type if object has $module property and isn't in core modules.
4360 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4361 }
4362
4363
4386 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4387 {
4388 global $hookmanager, $action;
4389
4390 // Important for pdf generation time reduction
4391 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4392 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4393 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4394 return 1;
4395 }
4396
4397 $this->linkedObjectsIds = array();
4398 $this->linkedObjects = array();
4399
4400 $justsource = false;
4401 $justtarget = false;
4402 $withtargettype = false;
4403 $withsourcetype = false;
4404
4405 // Hook for explicitly set the targettype if it must be differtent than $this->element
4406 if (is_object($hookmanager)) {
4407 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4408 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4409 if ($reshook > 0) {
4410 if (!empty($hookmanager->resArray['sourcetype'])) {
4411 $sourcetype = $hookmanager->resArray['sourcetype'];
4412 }
4413 if (!empty($hookmanager->resArray['sourceid'])) {
4414 $sourceid = $hookmanager->resArray['sourceid'];
4415 }
4416 if (!empty($hookmanager->resArray['targettype'])) {
4417 $targettype = $hookmanager->resArray['targettype'];
4418 }
4419 if (!empty($hookmanager->resArray['targetid'])) {
4420 $targetid = $hookmanager->resArray['targetid'];
4421 }
4422 }
4423 }
4424
4425 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4426 $justsource = true; // the source (id and type) is a search criteria
4427 if (!empty($targettype)) {
4428 $withtargettype = true;
4429 }
4430 }
4431 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4432 $justtarget = true; // the target (id and type) is a search criteria
4433 if (!empty($sourcetype)) {
4434 $withsourcetype = true;
4435 }
4436 }
4437
4438 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4439 $targetid = (!empty($targetid) ? $targetid : $this->id);
4440 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4441 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4442
4443 /*if (empty($sourceid) && empty($targetid))
4444 {
4445 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4446 return -1;
4447 }*/
4448
4449 // Links between objects are stored in table element_element
4450 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4451 $sql .= " FROM ".$this->db->prefix()."element_element";
4452 $sql .= " WHERE ";
4453 if ($justsource || $justtarget) {
4454 if ($justsource) {
4455 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4456 if ($withtargettype) {
4457 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4458 }
4459 } elseif ($justtarget) {
4460 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4461 if ($withsourcetype) {
4462 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4463 }
4464 }
4465 } else {
4466 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4467 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4468 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4469 $this->linkedObjectsFullLoaded[$this->id] = true;
4470 }
4471 }
4472 $sql .= " ORDER BY ".$orderby;
4473
4474 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4475 $resql = $this->db->query($sql);
4476 if ($resql) {
4477 $num = $this->db->num_rows($resql);
4478 $i = 0;
4479 while ($i < $num) {
4480 $obj = $this->db->fetch_object($resql);
4481 if ($justsource || $justtarget) {
4482 if ($justsource) {
4483 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4484 } elseif ($justtarget) {
4485 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4486 }
4487 } else {
4488 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4489 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4490 }
4491 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4492 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4493 }
4494 }
4495 $i++;
4496 }
4497
4498 if (!empty($this->linkedObjectsIds)) {
4499 $tmparray = $this->linkedObjectsIds;
4500 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4501 $element_properties = getElementProperties($objecttype);
4502 $element = $element_properties['element'];
4503 $classPath = $element_properties['classpath'];
4504 $classFile = $element_properties['classfile'];
4505 $className = $element_properties['classname'];
4506 $module = $element_properties['module'];
4507
4508 // Here $module, $classFile and $className are set, we can use them.
4509 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4510 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4511 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4512 if (class_exists($className)) {
4513 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4514 $object = new $className($this->db);
4515 '@phan-var-force CommonObject $object';
4516 $ret = $object->fetch($objectid);
4517 if ($ret >= 0) {
4518 $this->linkedObjects[$objecttype][$i] = $object;
4519 }
4520 }
4521 }
4522 }
4523 } else {
4524 unset($this->linkedObjectsIds[$objecttype]);
4525 }
4526 }
4527 }
4528 return 1;
4529 } else {
4530 dol_print_error($this->db);
4531 return -1;
4532 }
4533 }
4534
4541 public function clearObjectLinkedCache()
4542 {
4543 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4544 unset($this->linkedObjectsFullLoaded[$this->id]);
4545 }
4546
4547 return 1;
4548 }
4549
4562 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4563 {
4564 global $user;
4565 $updatesource = false;
4566 $updatetarget = false;
4567 $f_user = isset($f_user) ? $f_user : $user;
4568
4569 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4570 $updatesource = true;
4571 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4572 $updatetarget = true;
4573 }
4574
4575 $this->db->begin();
4576 $error = 0;
4577
4578 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4579 if ($updatesource) {
4580 $sql .= "fk_source = " . ((int) $sourceid);
4581 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4582 $sql .= " WHERE fk_target = " . ((int) $this->id);
4583 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4584 } elseif ($updatetarget) {
4585 $sql .= "fk_target = " . ((int) $targetid);
4586 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4587 $sql .= " WHERE fk_source = " . ((int) $this->id);
4588 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4589 }
4590
4591 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4592 if ($this->db->query($sql)) {
4593 if (!$notrigger) {
4594 // Call trigger
4595 $this->context['link_source_id'] = $sourceid;
4596 $this->context['link_source_type'] = $sourcetype;
4597 $this->context['link_target_id'] = $targetid;
4598 $this->context['link_target_type'] = $targettype;
4599 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user);
4600 if ($result < 0) {
4601 $error++;
4602 }
4603 // End call triggers
4604 }
4605 } else {
4606 $this->error = $this->db->lasterror();
4607 $error++;
4608 }
4609
4610 if (!$error) {
4611 $this->db->commit();
4612 return 1;
4613 } else {
4614 $this->db->rollback();
4615 return -1;
4616 }
4617 }
4618
4632 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4633 {
4634 global $user;
4635 $deletesource = false;
4636 $deletetarget = false;
4637 $f_user = isset($f_user) ? $f_user : $user;
4638
4639 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4640 $deletesource = true;
4641 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4642 $deletetarget = true;
4643 }
4644
4645 $element = $this->getElementType();
4646 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4647 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4648 $targetid = (!empty($targetid) ? $targetid : $this->id);
4649 $targettype = (!empty($targettype) ? $targettype : $element);
4650 $this->db->begin();
4651 $error = 0;
4652
4653 if (!$notrigger) {
4654 // Call trigger
4655 $this->context['link_id'] = $rowid;
4656 $this->context['link_source_id'] = $sourceid;
4657 $this->context['link_source_type'] = $sourcetype;
4658 $this->context['link_target_id'] = $targetid;
4659 $this->context['link_target_type'] = $targettype;
4660 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
4661 if ($result < 0) {
4662 $error++;
4663 }
4664 // End call triggers
4665 }
4666
4667 if (!$error) {
4668 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4669 $sql .= " WHERE";
4670 if ($rowid > 0) {
4671 $sql .= " rowid = " . ((int) $rowid);
4672 } else {
4673 if ($deletesource) {
4674 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4675 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4676 } elseif ($deletetarget) {
4677 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4678 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4679 } else {
4680 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4681 $sql .= " OR";
4682 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4683 }
4684 }
4685
4686 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4687 if (!$this->db->query($sql)) {
4688 $this->error = $this->db->lasterror();
4689 $this->errors[] = $this->error;
4690 $error++;
4691 }
4692 }
4693
4694 if (!$error) {
4695 $this->db->commit();
4696 return 1;
4697 } else {
4698 $this->db->rollback();
4699 return 0;
4700 }
4701 }
4702
4712 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4713 {
4714 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4715 return -1;
4716 }
4717 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4718 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4719 }
4720
4721 global $db;
4722
4723 $sql = "SELECT ".$field_select." FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4724 $resql = $db->query($sql);
4725
4726 $TRes = array();
4727 if (!empty($resql)) {
4728 while ($res = $db->fetch_object($resql)) {
4729 $TRes[] = $res->{$field_select};
4730 }
4731 }
4732
4733 return $TRes;
4734 }
4735
4744 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4745 {
4746 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4747 return -1;
4748 }
4749
4750 global $db;
4751
4752 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4753 $resql = $db->query($sql);
4754 $n = 0;
4755 if ($resql) {
4756 $res = $db->fetch_object($resql);
4757 if ($res) {
4758 $n = $res->nb;
4759 }
4760 }
4761
4762 return $n;
4763 }
4764
4773 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4774 {
4775 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4776 return -1;
4777 }
4778
4779 global $db;
4780
4781 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4782 $resql = $db->query($sql);
4783
4784 if (empty($resql)) {
4785 return 0;
4786 }
4787
4788 return 1;
4789 }
4790
4801 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = 'fk_statut')
4802 {
4803 global $user;
4804
4805 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4806
4807 $elementId = (!empty($elementId) ? $elementId : $this->id);
4808 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4809
4810 $this->db->begin();
4811
4812 if ($elementTable == 'facture_rec') {
4813 $fieldstatus = "suspended";
4814 }
4815 if ($elementTable == 'mailing') {
4816 $fieldstatus = "statut";
4817 }
4818 if ($elementTable == 'cronjob') {
4819 $fieldstatus = "status";
4820 }
4821 if ($elementTable == 'user') {
4822 $fieldstatus = "statut";
4823 }
4824 if ($elementTable == 'expensereport') {
4825 $fieldstatus = "fk_statut";
4826 }
4827 if ($elementTable == 'receptiondet_batch') {
4828 $fieldstatus = "status";
4829 }
4830 if ($elementTable == 'prelevement_bons') {
4831 $fieldstatus = "statut";
4832 }
4833 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4834 $fieldstatus = 'status';
4835 }
4836
4837 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
4838 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
4839 // If status = 1 = validated, update also fk_user_valid
4840 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields
4841 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4842 $sql .= ", fk_user_valid = ".((int) $user->id);
4843 }
4844 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4845 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
4846 }
4847 if ($status == 1 && in_array($elementTable, array('inventory'))) {
4848 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
4849 }
4850 $sql .= " WHERE rowid = ".((int) $elementId);
4851 $sql .= " AND ".$fieldstatus." <> ".((int) $status); // We avoid update if status already correct
4852
4853 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4854 $resql = $this->db->query($sql);
4855 if ($resql) {
4856 $error = 0;
4857
4858 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
4859
4860 if ($nb_rows_affected > 0) {
4861 if (empty($trigkey)) {
4862 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
4863 if ($this->element == 'supplier_proposal' && $status == 2) {
4864 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4865 }
4866 if ($this->element == 'supplier_proposal' && $status == 3) {
4867 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4868 }
4869 if ($this->element == 'supplier_proposal' && $status == 4) {
4870 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4871 }
4872 if ($this->element == 'fichinter' && $status == 3) {
4873 $trigkey = 'FICHINTER_CLASSIFY_DONE';
4874 }
4875 if ($this->element == 'fichinter' && $status == 2) {
4876 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
4877 }
4878 if ($this->element == 'fichinter' && $status == 1) {
4879 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4880 }
4881 }
4882
4883 $this->context = array_merge($this->context, array('newstatus' => $status));
4884
4885 if ($trigkey) {
4886 $this->oldcopy = dol_clone($this);
4887
4888 // Call trigger
4889 $result = $this->call_trigger($trigkey, $user);
4890 if ($result < 0) {
4891 $error++;
4892 }
4893 // End call triggers
4894 }
4895 } else {
4896 // The status was probably already good. We do nothing more, no triggers.
4897 }
4898
4899 if (!$error) {
4900 $this->db->commit();
4901
4902 if (empty($savElementId)) {
4903 // If the element we update is $this (so $elementId was provided as null)
4904 if ($fieldstatus == 'tosell') {
4905 $this->status = $status;
4906 } elseif ($fieldstatus == 'tobuy') {
4907 $this->status_buy = $status; // @phpstan-ignore-line
4908 } elseif ($fieldstatus == 'tobatch') {
4909 $this->status_batch = $status; // @phpstan-ignore-line
4910 } else {
4911 $this->status = $status;
4912 }
4913 }
4914
4915 return 1;
4916 } else {
4917 $this->db->rollback();
4918 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
4919 return -1;
4920 }
4921 } else {
4922 $this->error = $this->db->lasterror();
4923 $this->db->rollback();
4924 return -1;
4925 }
4926 }
4927
4928
4936 public function getCanvas($id = 0, $ref = '')
4937 {
4938 if (empty($id) && empty($ref)) {
4939 return 0;
4940 }
4941 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
4942 return 0; // To increase speed. Not enabled by default.
4943 }
4944
4945 // Clean parameters
4946 $ref = trim($ref);
4947
4948 $sql = "SELECT rowid, canvas";
4949 $sql .= " FROM ".$this->db->prefix().$this->table_element;
4950 $sql .= " WHERE entity IN (".getEntity($this->element).")";
4951 if (!empty($id)) {
4952 $sql .= " AND rowid = ".((int) $id);
4953 }
4954 if (!empty($ref)) {
4955 $sql .= " AND ref = '".$this->db->escape($ref)."'";
4956 }
4957
4958 $resql = $this->db->query($sql);
4959 if ($resql) {
4960 $obj = $this->db->fetch_object($resql);
4961 if ($obj) {
4962 $this->canvas = $obj->canvas;
4963 return 1;
4964 } else {
4965 return 0;
4966 }
4967 } else {
4968 dol_print_error($this->db);
4969 return -1;
4970 }
4971 }
4972
4973
4980 public function getSpecialCode($lineid)
4981 {
4982 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->table_element_line;
4983 $sql .= " WHERE rowid = ".((int) $lineid);
4984 $resql = $this->db->query($sql);
4985 if ($resql) {
4986 $row = $this->db->fetch_row($resql);
4987 return (!empty($row[0]) ? $row[0] : 0);
4988 }
4989
4990 return 0;
4991 }
4992
5001 public function isObjectUsed($id = 0, $entity = 0)
5002 {
5003 global $langs;
5004
5005 if (empty($id)) {
5006 $id = $this->id;
5007 }
5008
5009 // Check parameters
5010 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
5011 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
5012 return -1;
5013 }
5014
5015 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5016 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5017 $tmparray = array_keys($this->childtables);
5018 if (is_numeric($tmparray[0])) {
5019 $arraytoscan = array_flip($this->childtables);
5020 }
5021
5022 // Test if child exists
5023 $haschild = 0;
5024 foreach ($arraytoscan as $table => $element) {
5025 //print $id.'-'.$table.'-'.$elementname.'<br>';
5026
5027 // Check if module is enabled (to avoid error if tables of module not created)
5028 if (isset($element['enabled']) && !empty($element['enabled'])) {
5029 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5030 if (empty($enabled)) {
5031 continue;
5032 }
5033 }
5034
5035 // Check if element can be deleted
5036 $sql = "SELECT COUNT(*) as nb";
5037 $sql .= " FROM ".$this->db->prefix().$table." as c";
5038 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5039 $sql .= ", ".$this->db->prefix().$element['parent']." as p";
5040 }
5041 if (!empty($element['fk_element'])) {
5042 $sql .= " WHERE c.".$element['fk_element']." = ".((int) $id);
5043 } else {
5044 $sql .= " WHERE c.".$this->fk_element." = ".((int) $id);
5045 }
5046 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5047 $sql .= " AND c.".$element['parentkey']." = p.rowid";
5048 }
5049 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5050 $sql .= " AND c.".$element['parenttypefield']." = '".$this->db->escape($element['parenttypevalue'])."'";
5051 }
5052 if (!empty($entity)) {
5053 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5054 $sql .= " AND p.entity = ".((int) $entity);
5055 } else {
5056 $sql .= " AND c.entity = ".((int) $entity);
5057 }
5058 }
5059
5060 $resql = $this->db->query($sql);
5061 if ($resql) {
5062 $obj = $this->db->fetch_object($resql);
5063 if ($obj->nb > 0) {
5064 $langs->load("errors");
5065 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5066 $haschild += $obj->nb;
5067 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5068 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5069 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5070 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5071 } else { // new usage: $element['name']=Translation key
5072 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5073 }
5074 break; // We found at least one, we stop here
5075 }
5076 } else {
5077 $this->errors[] = $this->db->lasterror();
5078 return -1;
5079 }
5080 }
5081 if ($haschild > 0) {
5082 $this->errors[] = "ErrorRecordHasChildren";
5083 return $haschild;
5084 } else {
5085 return 0;
5086 }
5087 }
5088
5095 public function hasProductsOrServices($predefined = -1)
5096 {
5097 $nb = 0;
5098
5099 foreach ($this->lines as $key => $val) {
5100 $qualified = 0;
5101 if ($predefined == -1) {
5102 $qualified = 1;
5103 }
5104 if ($predefined == 1 && $val->fk_product > 0) {
5105 $qualified = 1;
5106 }
5107 if ($predefined == 0 && $val->fk_product <= 0) {
5108 $qualified = 1;
5109 }
5110 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5111 $qualified = 1;
5112 }
5113 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5114 $qualified = 1;
5115 }
5116 if ($qualified) {
5117 $nb++;
5118 }
5119 }
5120 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5121 return $nb;
5122 }
5123
5129 public function getTotalDiscount()
5130 {
5131 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5132 $total_discount = 0.00;
5133
5134 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5135 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5136 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
5137
5138 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5139 $resql = $this->db->query($sql);
5140 if ($resql) {
5141 $num = $this->db->num_rows($resql);
5142 $i = 0;
5143 while ($i < $num) {
5144 $obj = $this->db->fetch_object($resql);
5145
5146 $pu_ht = $obj->pu_ht;
5147 $qty = $obj->qty;
5148 $total_ht = $obj->total_ht;
5149
5150 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5151 $total_discount += $total_discount_line;
5152
5153 $i++;
5154 }
5155 }
5156
5157 //print $total_discount; exit;
5158 return (float) price2num($total_discount);
5159 }
5160
5161 return null;
5162 }
5163
5164
5171 public function getTotalWeightVolume()
5172 {
5173 $totalWeight = 0;
5174 $totalVolume = 0;
5175 // defined for shipment only
5176 $totalOrdered = 0;
5177 // defined for shipment only
5178 $totalToShip = 0;
5179
5180 if (empty($this->lines)) {
5181 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5182 }
5183
5184 foreach ($this->lines as $line) {
5185 if (isset($line->qty_asked)) {
5186 $totalOrdered += $line->qty_asked; // defined for shipment only
5187 }
5188 if (isset($line->qty_shipped)) {
5189 $totalToShip += $line->qty_shipped; // defined for shipment only
5190 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5191 if (empty($totalToShip)) {
5192 $totalToShip = 0;
5193 }
5194 $totalToShip += $line->qty; // defined for reception only
5195 }
5196
5197 // Define qty, weight, volume, weight_units, volume_units
5198 if ($this->element == 'shipping') {
5199 // for shipments
5200 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5201 } else {
5202 $qty = $line->qty ? $line->qty : 0;
5203 }
5204
5205 $weight = !empty($line->weight) ? $line->weight : 0;
5206 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5207 $volume = !empty($line->volume) ? $line->volume : 0;
5208 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5209
5210 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5211 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5212 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5213 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5214
5215 $weightUnit = 0;
5216 $volumeUnit = 0;
5217 if (!empty($weight_units)) {
5218 $weightUnit = $weight_units;
5219 }
5220 if (!empty($volume_units)) {
5221 $volumeUnit = $volume_units;
5222 }
5223
5224 if (empty($totalWeight)) {
5225 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5226 }
5227 if (empty($totalVolume)) {
5228 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5229 }
5230
5231 //var_dump($line->volume_units);
5232 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5233 $trueWeightUnit = pow(10, $weightUnit);
5234 $totalWeight += $weight * $qty * $trueWeightUnit;
5235 } else {
5236 if ($weight_units == 99) {
5237 // conversion 1 Pound = 0.45359237 KG
5238 $trueWeightUnit = 0.45359237;
5239 $totalWeight += $weight * $qty * $trueWeightUnit;
5240 } elseif ($weight_units == 98) {
5241 // conversion 1 Ounce = 0.0283495 KG
5242 $trueWeightUnit = 0.0283495;
5243 $totalWeight += $weight * $qty * $trueWeightUnit;
5244 } else {
5245 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5246 }
5247 }
5248 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5249 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5250 $trueVolumeUnit = pow(10, $volumeUnit);
5251 //print $line->volume;
5252 $totalVolume += $volume * $qty * $trueVolumeUnit;
5253 } else {
5254 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5255 }
5256 }
5257
5258 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5259 }
5260
5261
5267 public function setExtraParameters()
5268 {
5269 $this->db->begin();
5270
5271 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5272 $extraparams = dol_trunc($extraparams, 250);
5273
5274 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5275 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5276 $sql .= " WHERE rowid = ".((int) $this->id);
5277
5278 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5279 $resql = $this->db->query($sql);
5280 if (!$resql) {
5281 $this->error = $this->db->lasterror();
5282 $this->db->rollback();
5283 return -1;
5284 } else {
5285 $this->db->commit();
5286 return 1;
5287 }
5288 }
5289
5290
5291 // --------------------
5292 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5293 // --------------------
5294
5295 /* This is to show add lines */
5296
5306 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5307 {
5308 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5309
5310 // Line extrafield
5311 if (!is_object($extrafields)) {
5312 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5313 $extrafields = new ExtraFields($this->db);
5314 }
5315 $extrafields->fetch_name_optionals_label($this->table_element_line);
5316
5317 // Output template part (modules that overwrite templates must declare this into descriptor)
5318 // Use global variables + $dateSelector + $seller and $buyer
5319 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5320 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5321 foreach ($dirtpls as $module => $reldir) {
5322 if (!empty($module)) {
5323 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5324 } else {
5325 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5326 }
5327
5328 if (empty($conf->file->strict_mode)) {
5329 $res = @include $tpl;
5330 } else {
5331 $res = include $tpl; // for debug
5332 }
5333 if ($res) {
5334 break;
5335 }
5336 }
5337 }
5338
5339
5340
5341 /* This is to show array of line of details */
5342
5343
5358 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5359 {
5360 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5361 // TODO We should not use global var for this
5362 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5363
5364 // Define $usemargins (used by objectline_xxx.tpl.php files)
5365 $usemargins = 0;
5366 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5367 $usemargins = 1;
5368 }
5369
5370 $num = count($this->lines);
5371
5372 // Line extrafield
5373 if (!is_object($extrafields)) {
5374 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5375 $extrafields = new ExtraFields($this->db);
5376 }
5377 $extrafields->fetch_name_optionals_label($this->table_element_line);
5378
5379 if (method_exists($this, 'loadExpeditions')) {
5380 // 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.
5381 $this->loadExpeditions();
5382 }
5383
5384 $parameters = array();
5385 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5386 if (empty($reshook)) {
5387 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5388 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5389 if (empty($reshook)) {
5390 // Output template part (modules that overwrite templates must declare this into descriptor)
5391 // Use global variables + $dateSelector + $seller and $buyer
5392 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5393 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5394 foreach ($dirtpls as $module => $reldir) {
5395 $res = 0;
5396 if (!empty($module)) {
5397 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5398 } else {
5399 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5400 }
5401 if (file_exists($tpl)) {
5402 if (empty($conf->file->strict_mode)) {
5403 $res = @include $tpl;
5404 } else {
5405 $res = include $tpl; // for debug
5406 }
5407 }
5408 if ($res) {
5409 break;
5410 }
5411 }
5412 }
5413
5414 $i = 0;
5415
5416 print "<!-- begin printObjectLines() -->\n";
5417 foreach ($this->lines as $line) {
5418 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5419 //$line->fetch_optionals();
5420
5421 if (is_object($hookmanager)) {
5422 if (empty($line->fk_parent_line)) {
5423 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5424 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5425 } else {
5426 $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);
5427 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5428 }
5429 }
5430 if (empty($reshook)) {
5431 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5432 }
5433
5434 $i++;
5435 }
5436 print "<!-- end printObjectLines() -->\n";
5437 }
5438 }
5439
5457 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5458 {
5459 global $conf, $langs, $user, $object, $hookmanager;
5460 global $form;
5461 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5462
5463 // var used into tpl
5464 $text = '';
5465 $description = '';
5466
5467 // Line in view mode
5468 if ($action != 'editline' || $selected != $line->id) {
5469 // Product
5470 if (!empty($line->fk_product) && $line->fk_product > 0) {
5471 $product_static = new Product($this->db);
5472 $product_static->fetch($line->fk_product);
5473
5474 $product_static->ref = $line->ref; //can change ref in hook
5475 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5476
5477 $text = $product_static->getNomUrl(1);
5478
5479 // Define output language and label
5480 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5481 // @phan-suppress-next-line PhanUndeclaredProperty
5482 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5483 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5484 return;
5485 }
5486
5487 $prod = new Product($this->db);
5488 $prod->fetch($line->fk_product);
5489
5490 $outputlangs = $langs;
5491 $newlang = '';
5492 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5493 $newlang = GETPOST('lang_id', 'aZ09');
5494 }
5495 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5496 $newlang = $this->thirdparty->default_lang; // To use language of customer
5497 }
5498 if (!empty($newlang)) {
5499 $outputlangs = new Translate("", $conf);
5500 $outputlangs->setDefaultLang($newlang);
5501 }
5502
5503 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5504 } else {
5505 $label = $line->product_label;
5506 }
5507
5508 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5509 $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.
5510 }
5511
5512 $line->pu_ttc = price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5513
5514 // Output template part (modules that overwrite templates must declare this into descriptor)
5515 // Use global variables + $dateSelector + $seller and $buyer
5516 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5517
5518 $qty_shipped = 0;
5519 if (isset($this->expeditions[$line->id])) {
5520 $qty_shipped = $this->expeditions[$line->id];
5521 }
5522 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5523
5524 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5525 foreach ($dirtpls as $module => $reldir) {
5526 $res = 0;
5527 if (!empty($module)) {
5528 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5529 } else {
5530 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5531 }
5532 //var_dump($tpl);
5533 if (file_exists($tpl)) {
5534 if (empty($conf->file->strict_mode)) {
5535 $res = @include $tpl;
5536 } else {
5537 $res = include $tpl; // for debug
5538 }
5539 }
5540 if ($res) {
5541 break;
5542 }
5543 }
5544 }
5545
5546 // Line in update mode
5547 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5548 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5549
5550 $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5551
5552 // Output template part (modules that overwrite templates must declare this into descriptor)
5553 // Use global variables + $dateSelector + $seller and $buyer
5554 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5555 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5556 foreach ($dirtpls as $module => $reldir) {
5557 if (!empty($module)) {
5558 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5559 } else {
5560 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5561 }
5562
5563 if (empty($conf->file->strict_mode)) {
5564 $res = @include $tpl;
5565 } else {
5566 $res = include $tpl; // for debug
5567 }
5568 if ($res) {
5569 break;
5570 }
5571 }
5572 }
5573 }
5574
5575
5576 /* This is to show array of line of details of source object */
5577
5578
5589 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5590 {
5591 global $langs, $hookmanager, $form, $action;
5592
5593 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5594 print '<tr class="liste_titre">';
5595 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5596 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5597 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5598 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5599 if (isModEnabled("multicurrency")) {
5600 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5601 }
5602 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5603 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5604 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5605 }
5606 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5607 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5608 print '<td class="center">';
5609 print $form->showCheckAddButtons('checkforselect', 1);
5610 print '</td>';
5611 print '</tr>';
5612 $i = 0;
5613
5614 if (!empty($this->lines)) {
5615 foreach ($this->lines as $line) {
5616 $reshook = 0;
5617 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5618 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5619 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5620 if (!empty($line->fk_parent_line)) {
5621 $parameters['fk_parent_line'] = $line->fk_parent_line;
5622 }
5623 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5624 }
5625 if (empty($reshook)) {
5626 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5627 }
5628
5629 $i++;
5630 }
5631 }
5632 }
5633
5647 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5648 {
5649 global $langs, $conf;
5650
5651 //var_dump($line);
5652 if (!empty($line->date_start)) {
5653 // @phan-suppress-next-line PhanUndeclaredProperty
5654 $date_start = $line->date_start;
5655 } else {
5656 $date_start = $line->date_debut_prevue;
5657 if ($line->date_debut_reel) {
5658 $date_start = $line->date_debut_reel;
5659 }
5660 }
5661 if (!empty($line->date_end)) {
5662 // @phan-suppress-next-line PhanUndeclaredProperty
5663 $date_end = $line->date_end;
5664 } else {
5665 $date_end = $line->date_fin_prevue;
5666 if ($line->date_fin_reel) {
5667 $date_end = $line->date_fin_reel;
5668 }
5669 }
5670
5671 // Set thevalue into ->tpl[] array.
5672 $this->tpl['id'] = $line->id;
5673
5674 $this->tpl['label'] = '';
5675 if (!empty($line->fk_parent_line)) {
5676 $this->tpl['label'] .= img_picto('', 'rightarrow');
5677 }
5678
5679 if (($line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5680 $discount = new DiscountAbsolute($this->db);
5681 if (property_exists($this, 'socid')) {
5682 $discount->fk_soc = $this->socid;
5683 $discount->socid = $this->socid;
5684 }
5685 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5686 } elseif (!empty($line->fk_product)) {
5687 $productstatic = new Product($this->db);
5688 $productstatic->id = $line->fk_product;
5689 $productstatic->ref = $line->ref;
5690 $productstatic->type = $line->fk_product_type;
5691 if (empty($productstatic->ref)) {
5692 $line->fetch_product();
5693 $productstatic = $line->product;
5694 }
5695
5696 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5697 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5698 // Dates
5699 if ($line->product_type == 1 && ($date_start || $date_end)) {
5700 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5701 }
5702 } else {
5703 $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"')));
5704 if (!empty($line->desc)) {
5705 $this->tpl['label'] .= $line->desc;
5706 } else {
5707 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5708 }
5709
5710 // Dates
5711 if ($line->product_type == 1 && ($date_start || $date_end)) {
5712 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5713 }
5714 }
5715
5716 if (!empty($line->desc)) {
5717 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5718 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5719 $discount = new DiscountAbsolute($this->db);
5720 $discount->fetch($line->fk_remise_except);
5721 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5722 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5723 $discount = new DiscountAbsolute($this->db);
5724 $discount->fetch($line->fk_remise_except);
5725 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5726 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5727 $discount = new DiscountAbsolute($this->db);
5728 $discount->fetch($line->fk_remise_except);
5729 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5730 } elseif ($line->desc == '(EXCESS PAID)') {
5731 $discount = new DiscountAbsolute($this->db);
5732 $discount->fetch($line->fk_remise_except);
5733 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5734 } else {
5735 $this->tpl['description'] = dol_trunc($line->desc, 60);
5736 }
5737 } else {
5738 $this->tpl['description'] = '&nbsp;';
5739 }
5740
5741 // VAT Rate
5742 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5743 $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
5744 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5745 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5746 }
5747
5748 $this->tpl['price'] = price($line->subprice);
5749 $this->tpl['total_ht'] = price($line->total_ht);
5750 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5751 $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5752 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5753 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
5754 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
5755 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
5756 }
5757 $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
5758
5759 // Is the line strike or not
5760 $this->tpl['strike'] = 0;
5761 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5762 $this->tpl['strike'] = 1;
5763 } elseif ($line->special_code == SUBTOTALS_SPECIAL_CODE) {
5764 $this->tpl['strike'] = 1;
5765 }
5766
5767 // Output template part (modules that overwrite templates must declare this into descriptor)
5768 // Use global variables + $dateSelector + $seller and $buyer
5769 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5770 foreach ($dirtpls as $module => $reldir) {
5771 if (!empty($module)) {
5772 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5773 } else {
5774 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5775 }
5776
5777 if (empty($conf->file->strict_mode)) {
5778 $res = @include $tpl;
5779 } else {
5780 $res = include $tpl; // for debug
5781 }
5782 if ($res) {
5783 break;
5784 }
5785 }
5786 }
5787
5788
5789 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5800 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
5801 {
5802 // phpcs:enable
5803 $this->db->begin();
5804
5805 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5806 $sql .= "resource_id";
5807 $sql .= ", resource_type";
5808 $sql .= ", element_id";
5809 $sql .= ", element_type";
5810 $sql .= ", busy";
5811 $sql .= ", mandatory";
5812 $sql .= ") VALUES (";
5813 $sql .= ((int) $resource_id);
5814 $sql .= ", '".$this->db->escape($resource_type)."'";
5815 $sql .= ", '".$this->db->escape((string) $this->id)."'";
5816 $sql .= ", '".$this->db->escape($this->element)."'";
5817 $sql .= ", '".$this->db->escape((string) $busy)."'";
5818 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
5819 $sql .= ")";
5820
5821 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5822 if ($this->db->query($sql)) {
5823 $this->db->commit();
5824 return 1;
5825 } else {
5826 $this->error = $this->db->lasterror();
5827 $this->db->rollback();
5828 return 0;
5829 }
5830 }
5831
5832 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5841 public function delete_resource($rowid, $element, $notrigger = 0)
5842 {
5843 // phpcs:enable
5844 global $user;
5845
5846 $this->db->begin();
5847
5848 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
5849 $sql .= " WHERE rowid = ".((int) $rowid);
5850
5851 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5852
5853 $resql = $this->db->query($sql);
5854 if (!$resql) {
5855 $this->error = $this->db->lasterror();
5856 $this->db->rollback();
5857 return -1;
5858 } else {
5859 if (!$notrigger) {
5860 $result = $this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
5861 if ($result < 0) {
5862 $this->db->rollback();
5863 return -1;
5864 }
5865 }
5866 $this->db->commit();
5867 return 1;
5868 }
5869 }
5870
5871
5877 public function __clone()
5878 {
5879 // Force a copy of this->lines, otherwise it will point to same object.
5880 if (isset($this->lines) && is_array($this->lines)) {
5881 $nboflines = count($this->lines);
5882 for ($i = 0; $i < $nboflines; $i++) {
5883 if (is_object($this->lines[$i])) {
5884 $this->lines[$i] = clone $this->lines[$i];
5885 }
5886 }
5887 }
5888 }
5889
5903 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5904 {
5905 global $conf, $langs, $hookmanager, $action;
5906
5907 $srctemplatepath = '';
5908
5909 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
5910 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5911
5912 if (!empty($reshook)) {
5913 return $reshook;
5914 }
5915
5916 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5917
5918 if (empty($modele)) {
5919 $this->error = 'BadValueForParameterModele';
5920 return -1;
5921 }
5922
5923 // Increase limit for PDF build
5924 $err = error_reporting();
5925 error_reporting(0);
5926 @set_time_limit(120);
5927 error_reporting($err);
5928
5929 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5930 $tmp = explode(':', $modele, 2);
5931 $saved_model = $modele;
5932 if (!empty($tmp[1])) {
5933 $modele = $tmp[0];
5934 $srctemplatepath = $tmp[1];
5935 }
5936
5937 // Search template files
5938 $file = '';
5939 $classname = '';
5940 $filefound = '';
5941 $dirmodels = array('/');
5942 if (is_array($conf->modules_parts['models'])) {
5943 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5944 }
5945 foreach ($dirmodels as $reldir) {
5946 foreach (array('doc', 'pdf') as $prefix) {
5947 if (in_array(get_class($this), array('Adherent'))) {
5948 // Member module use prefix_modele.class.php
5949 $file = $prefix."_".$modele.".class.php";
5950 } else {
5951 // Other module use prefix_modele.modules.php
5952 $file = $prefix."_".$modele.".modules.php";
5953 }
5954
5955 $file = dol_sanitizeFileName($file);
5956
5957 // We check if the file exists
5958 $file = dol_buildpath($reldir.$modelspath.$file, 0);
5959 if (file_exists($file)) {
5960 $filefound = $file;
5961 $classname = $prefix.'_'.$modele;
5962 break;
5963 }
5964 }
5965 if ($filefound) {
5966 break;
5967 }
5968 }
5969
5970 if ($filefound === '' || $classname === '') {
5971 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
5972 $this->errors[] = $this->error;
5973 dol_syslog($this->error, LOG_ERR);
5974 return -1;
5975 }
5976
5977 // Sanitize $filefound
5978 $filefound = dol_sanitizePathName($filefound);
5979
5980 // If generator was found
5981 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
5982
5983 require_once $filefound;
5984
5985 $obj = new $classname($this->db);
5986
5987 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
5988 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
5989 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
5990 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
5991 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
5992 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
5993 // ModelePDFStock|ModelePDFStockTransfer|
5994 // ModeleDon|ModelePDFTask|
5995 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
5996 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
5997 '@phan-var-force ModelePDFMember $obj';
5998 // '@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';
5999
6000 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6001 if ($obj->type == 'odt' && empty($srctemplatepath)) {
6002 $varfortemplatedir = $obj->scandir;
6003 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
6004 $dirtoscan = getDolGlobalString($varfortemplatedir);
6005
6006 $listoffiles = array();
6007
6008 // Now we add first model found in directories scanned
6009 $listofdir = explode(',', $dirtoscan);
6010 foreach ($listofdir as $key => $tmpdir) {
6011 $tmpdir = trim($tmpdir);
6012 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6013 if (!$tmpdir) {
6014 unset($listofdir[$key]);
6015 continue;
6016 }
6017 if (is_dir($tmpdir)) {
6018 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6019 if (count($tmpfiles)) {
6020 $listoffiles = array_merge($listoffiles, $tmpfiles);
6021 }
6022 }
6023 }
6024
6025 if (count($listoffiles)) {
6026 foreach ($listoffiles as $record) {
6027 $srctemplatepath = $record['fullname'];
6028 break;
6029 }
6030 }
6031 }
6032
6033 if (empty($srctemplatepath)) {
6034 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6035 return -1;
6036 }
6037 }
6038
6039 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6040 if (!dol_is_file($srctemplatepath)) {
6041 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6042 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6043 return -1;
6044 }
6045 }
6046
6047 // We save charset_output to restore it because write_file can change it if needed for
6048 // output format that does not support UTF8.
6049 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6050
6051 // update model_pdf in object
6052 $this->model_pdf = $saved_model;
6053
6054 if ($obj instanceof ModelePDFMember) {
6055 if ($this instanceof Adherent) {
6056 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6057 } else {
6058 $resultwritefile = -1;
6059 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6060 }
6061 } elseif ($obj instanceof ModeleDon) {
6062 // Only 3 arguments
6063 if ($this instanceof Don) {
6064 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6065 } else {
6066 $resultwritefile = -1;
6067 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6068 }
6069 } else {
6070 // TODO: Try to set type above again
6071 '@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';
6072 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams); // @phan-suppress-line-PhanTypeMismatchArgument
6073 }
6074 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6075
6076 if ($resultwritefile > 0) {
6077 $outputlangs->charset_output = $sav_charset_output;
6078
6079 // We delete old preview
6080 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6081 dol_delete_preview($this);
6082
6083 // Index file in database
6084 if (!empty($obj->result['fullpath'])) {
6085 $destfull = $obj->result['fullpath'];
6086
6087 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6088 $update_main_doc_field = 0;
6089 if (!empty($obj->update_main_doc_field)) {
6090 $update_main_doc_field = 1;
6091 }
6092
6093 // Check that the file exists, before indexing it.
6094 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6095 if (dol_is_file($destfull)) {
6096 $this->indexFile($destfull, $update_main_doc_field);
6097 }
6098 } else {
6099 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);
6100 }
6101
6102 // Success in building document. We build meta file.
6103 dol_meta_create($this);
6104
6105 return 1;
6106 } else {
6107 $outputlangs->charset_output = $sav_charset_output;
6108 $this->error = $obj->error;
6109 $this->errors = $obj->errors;
6110 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
6111 return -1;
6112 }
6113 }
6114
6124 public function indexFile($destfull, $update_main_doc_field)
6125 {
6126 global $conf, $user;
6127
6128 $upload_dir = dirname($destfull);
6129 $destfile = basename($destfull);
6130 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6131
6132 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6133 $filename = basename($destfile);
6134 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6135 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6136
6137 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6138 $ecmfile = new EcmFiles($this->db);
6139 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6140
6141 // Set the public "share" key
6142 $setsharekey = false;
6143 if ($this->element == 'propal' || $this->element == 'proposal') {
6144 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6145 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6146 }
6147 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6148 $setsharekey = true;
6149 }
6150 }
6151 if ($this->element == 'commande' && getDolGlobalInt("ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6152 $setsharekey = true;
6153 }
6154 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6155 $setsharekey = true;
6156 }
6157 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6158 $setsharekey = true;
6159 }
6160 if ($this->element == 'product' && getDolGlobalInt("PRODUCT_ALLOW_EXTERNAL_DOWNLOAD")) {
6161 $setsharekey = true;
6162 }
6163 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6164 $setsharekey = true;
6165 }
6166 if ($this->element == 'fichinter' && getDolGlobalInt("FICHINTER_ALLOW_EXTERNAL_DOWNLOAD")) {
6167 $setsharekey = true;
6168 }
6169 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6170 $setsharekey = true;
6171 }
6172 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6173 $setsharekey = true;
6174 }
6175
6176 if ($setsharekey) {
6177 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6178 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6179 $ecmfile->share = getRandomPassword(true);
6180 }
6181 }
6182
6183 if ($result > 0) {
6184 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6185 $ecmfile->fullpath_orig = '';
6186 $ecmfile->gen_or_uploaded = 'generated';
6187 $ecmfile->description = ''; // indexed content
6188 $ecmfile->keywords = ''; // keyword content
6189 $ecmfile->src_object_type = $this->table_element;
6190 $ecmfile->src_object_id = $this->id;
6191 $result = $ecmfile->update($user);
6192 if ($result < 0) {
6193 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6194 return -1;
6195 }
6196 } else {
6197 $ecmfile->entity = $conf->entity;
6198 $ecmfile->filepath = $rel_dir;
6199 $ecmfile->filename = $filename;
6200 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6201 $ecmfile->fullpath_orig = '';
6202 $ecmfile->gen_or_uploaded = 'generated';
6203 $ecmfile->description = ''; // indexed content
6204 $ecmfile->keywords = ''; // keyword content
6205 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6206 $ecmfile->src_object_id = $this->id;
6207
6208 $result = $ecmfile->create($user);
6209 if ($result < 0) {
6210 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6211 return -1;
6212 }
6213 }
6214
6215 /*$this->result['fullname']=$destfull;
6216 $this->result['filepath']=$ecmfile->filepath;
6217 $this->result['filename']=$ecmfile->filename;*/
6218 //var_dump($obj->update_main_doc_field);exit;
6219
6220 if ($update_main_doc_field && !empty($this->table_element)) {
6221 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6222 $sql .= " WHERE rowid = ".((int) $this->id);
6223
6224 $resql = $this->db->query($sql);
6225 if (!$resql) {
6226 dol_print_error($this->db);
6227 return -1;
6228 } else {
6229 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6230 }
6231 }
6232 }
6233
6234 return 1;
6235 }
6236
6245 public function addThumbs($file, $quality = 50)
6246 {
6247 $file_osencoded = dol_osencode($file);
6248
6249 if (file_exists($file_osencoded)) {
6250 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6251
6252 $tmparraysize = getDefaultImageSizes();
6253 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6254 $maxheightsmall = $tmparraysize['maxheightsmall'];
6255 $maxwidthmini = $tmparraysize['maxwidthmini'];
6256 $maxheightmini = $tmparraysize['maxheightmini'];
6257 //$quality = $tmparraysize['quality'];
6258
6259 // Create small thumbs for company (Ratio is near 16/9)
6260 // Used on logon for example
6261 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6262
6263 // Create mini thumbs for company (Ratio is near 16/9)
6264 // Used on menu or for setup page for example
6265 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6266 }
6267 }
6268
6276 public function delThumbs($file)
6277 {
6278 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6279 dol_delete_file($imgThumbName);
6280 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6281 dol_delete_file($imgThumbName);
6282 }
6283
6284
6285 /* Functions common to commonobject and commonobjectline */
6286
6287 /* For default values */
6288
6302 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6303 {
6304 // If param here has been posted, we use this value first.
6305 if (GETPOSTISSET($fieldname)) {
6306 return GETPOST($fieldname, $type, 3);
6307 }
6308
6309 if (isset($alternatevalue)) {
6310 return $alternatevalue;
6311 }
6312
6313 $newelement = $this->element;
6314 if ($newelement == 'facture') {
6315 $newelement = 'invoice';
6316 }
6317 if ($newelement == 'commande') {
6318 $newelement = 'order';
6319 }
6320 if (empty($newelement)) {
6321 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6322 return '';
6323 }
6324
6325 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6326 //var_dump($keyforfieldname);
6327 if (getDolGlobalString($keyforfieldname)) {
6328 return getDolGlobalString($keyforfieldname);
6329 }
6330
6331 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6332 // store content into $conf->cache['overwrite_default']
6333
6334 return '';
6335 }
6336
6337
6338 /* For triggers */
6339
6340
6341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6352 public function call_trigger($triggerName, $user)
6353 {
6354 // phpcs:enable
6355 global $langs, $conf;
6356
6357 if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
6358 dol_print_error(null, 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
6359 exit;
6360 }
6361 if (!is_object($langs)) { // If lang was not defined, we set it. It is required by run_triggers().
6362 dol_syslog("call_trigger was called with no langs variable defined".getCallerInfoString(), LOG_WARNING);
6363 include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
6364 $langs = new Translate('', $conf);
6365 $langs->load("main");
6366 }
6367
6368 include_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
6369 $interface = new Interfaces($this->db);
6370 $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
6371
6372 if ($result < 0) {
6373 if (!empty($this->errors)) {
6374 $this->errors = array_unique(array_merge($this->errors, $interface->errors)); // We use array_unique because when a trigger call another trigger on same object, this->errors is added twice.
6375 } else {
6376 $this->errors = $interface->errors;
6377 }
6378 }
6379 return $result;
6380 }
6381
6382
6383 /* Functions for data in other language */
6384
6385
6395 {
6396 // To avoid SQL errors. Probably not the better solution though
6397 if (!$this->element) {
6398 return 0;
6399 }
6400 if (!($this->id > 0)) {
6401 return 0;
6402 }
6403 if (is_array($this->array_languages)) {
6404 return 1;
6405 }
6406
6407 $this->array_languages = array();
6408
6409 $element = $this->element;
6410 if ($element == 'categorie') {
6411 $element = 'categories'; // For compatibility
6412 }
6413
6414 // Request to get translation values for object
6415 $sql = "SELECT rowid, property, lang , value";
6416 $sql .= " FROM ".$this->db->prefix()."object_lang";
6417 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6418 $sql .= " AND fk_object = ".((int) $this->id);
6419
6420 $resql = $this->db->query($sql);
6421 if ($resql) {
6422 $numrows = $this->db->num_rows($resql);
6423 if ($numrows) {
6424 $i = 0;
6425 while ($i < $numrows) {
6426 $obj = $this->db->fetch_object($resql);
6427 $key = $obj->property;
6428 $value = $obj->value;
6429 $codelang = $obj->lang;
6430 $type = $this->fields[$key]['type'];
6431
6432 // we can add this attribute to object
6433 if (preg_match('/date/', $type)) {
6434 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6435 } else {
6436 $this->array_languages[$key][$codelang] = $value;
6437 }
6438
6439 $i++;
6440 }
6441 }
6442
6443 $this->db->free($resql);
6444
6445 if ($numrows) {
6446 return $numrows;
6447 } else {
6448 return 0;
6449 }
6450 } else {
6451 dol_print_error($this->db);
6452 return -1;
6453 }
6454 }
6455
6463 public function setValuesForExtraLanguages($onlykey = '')
6464 {
6465 // Get extra fields
6466 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6467 $tmparray = explode('-', $postfieldkey);
6468 if ($tmparray[0] != 'field') {
6469 continue;
6470 }
6471
6472 $element = $tmparray[1];
6473 $key = $tmparray[2];
6474 $codelang = $tmparray[3];
6475 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6476
6477 if (!empty($onlykey) && $key != $onlykey) {
6478 continue;
6479 }
6480 if ($element != $this->element) {
6481 continue;
6482 }
6483
6484 $key_type = $this->fields[$key]['type'];
6485
6486 $enabled = 1;
6487 if (isset($this->fields[$key]['enabled'])) {
6488 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6489 }
6490 /*$perms = 1;
6491 if (isset($this->fields[$key]['perms']))
6492 {
6493 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6494 }*/
6495 if (empty($enabled)) {
6496 continue;
6497 }
6498 //if (empty($perms)) continue;
6499
6500 if (in_array($key_type, array('date'))) {
6501 // Clean parameters
6502 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6503 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6504 } elseif (in_array($key_type, array('datetime'))) {
6505 // Clean parameters
6506 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6507 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6508 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6509 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6510 if (!empty($value_arr)) {
6511 $value_key = implode(',', $value_arr);
6512 } else {
6513 $value_key = '';
6514 }
6515 } elseif (in_array($key_type, array('price', 'double'))) {
6516 $value_arr = GETPOST($postfieldkey, 'alpha');
6517 $value_key = price2num($value_arr);
6518 } else {
6519 $value_key = GETPOST($postfieldkey);
6520 if (in_array($key_type, array('link')) && $value_key == '-1') {
6521 $value_key = '';
6522 }
6523 }
6524
6525 $this->array_languages[$key][$codelang] = $value_key;
6526
6527 /*if ($nofillrequired) {
6528 $langs->load('errors');
6529 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6530 return -1;
6531 }*/
6532 }
6533
6534 return 1;
6535 }
6536
6537
6538 /* Functions for extrafields */
6539
6546 public function fetchNoCompute($id)
6547 {
6548 global $conf;
6549
6550 $savDisableCompute = $conf->disable_compute;
6551 $conf->disable_compute = 1;
6552
6553 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6554
6555 $conf->disable_compute = $savDisableCompute;
6556
6557 return $ret;
6558 }
6559
6560 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6570 public function fetch_optionals($rowid = null, $optionsArray = null)
6571 {
6572 // phpcs:enable
6573 global $conf, $extrafields;
6574
6575 if (empty($rowid)) {
6576 $rowid = $this->id;
6577 }
6578 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6579 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6580 }
6581
6582 // To avoid SQL errors. Probably not the better solution though
6583 if (!$this->table_element) {
6584 return 0;
6585 }
6586
6587 $this->array_options = array();
6588
6589 if (!is_array($optionsArray)) {
6590 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6591 if (!isset($extrafields) || !is_object($extrafields)) {
6592 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6593 $extrafields = new ExtraFields($this->db);
6594 }
6595
6596 // Load array of extrafields for elementype = $this->table_element
6597 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6598 $extrafields->fetch_name_optionals_label($this->table_element);
6599 }
6600 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6601 } else {
6602 global $extrafields;
6603 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6604 }
6605
6606 $table_element = $this->table_element;
6607 if ($table_element == 'categorie') {
6608 $table_element = 'categories'; // For compatibility
6609 }
6610
6611 // Request to get complementary values
6612 if (is_array($optionsArray) && count($optionsArray) > 0) {
6613 $sql = "SELECT rowid";
6614 foreach ($optionsArray as $name => $label) {
6615 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6616 $sql .= ", ".$this->db->sanitize($name);
6617 }
6618 // use geo sql fonction to read as text
6619 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6620 // TODO Add an abstraction method in the database driver
6621 $sql .= ", ST_AsWKT(".$name.") as ".$this->db->sanitize($name);
6622 }
6623 }
6624 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6625 $sql .= " WHERE fk_object = ".((int) $rowid);
6626
6627 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6628 $resql = $this->db->query($sql);
6629 if ($resql) {
6630 $numrows = $this->db->num_rows($resql);
6631 if ($numrows) {
6632 $tab = $this->db->fetch_array($resql);
6633
6634 foreach ($tab as $key => $value) {
6635 // 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)
6636 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6637 // we can add this attribute to object
6638 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6639 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6640 $this->array_options["options_".$key] = $this->db->jdate($value);
6641 } else {
6642 $this->array_options["options_".$key] = $value;
6643 }
6644
6645 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6646 }
6647 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6648 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6649 $this->array_options["options_".$key] = dolDecrypt($value);
6650 }
6651 }
6652 }
6653 } else {
6658 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6659 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6660 $this->array_options['options_' . $key] = null;
6661 }
6662 }
6663 }
6664
6665 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6666 // in the element's extrafields table)
6667 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6668 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6669 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6670 //var_dump($conf->disable_compute);
6671 if (empty($conf->disable_compute)) {
6672 global $objectoffield; // We set a global variable to $objectoffield so
6673 $objectoffield = $this; // we can use it inside computed formula
6674 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6675 }
6676 }
6677 }
6678 }
6679
6680 $this->db->free($resql);
6681
6682 if ($numrows) {
6683 return $numrows;
6684 } else {
6685 return 0;
6686 }
6687 } else {
6688 $this->errors[] = $this->db->lasterror;
6689 return -1;
6690 }
6691 }
6692 return 0;
6693 }
6694
6701 public function deleteExtraFields()
6702 {
6703 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6704 return 0;
6705 }
6706
6707 $this->db->begin();
6708
6709 $table_element = $this->table_element;
6710 if ($table_element == 'categorie') {
6711 $table_element = 'categories'; // For compatibility
6712 }
6713
6714 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6715
6716 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6717
6718 $resql = $this->db->query($sql_del);
6719 if (!$resql) {
6720 $this->error = $this->db->lasterror();
6721 $this->db->rollback();
6722 return -1;
6723 } else {
6724 $this->db->commit();
6725 return 1;
6726 }
6727 }
6728
6739 public function insertExtraFields($trigger = '', $userused = null)
6740 {
6741 global $langs, $user;
6742
6743 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6744 return 0;
6745 }
6746
6747 if (empty($userused)) {
6748 $userused = $user;
6749 }
6750
6751 $error = 0;
6752
6753 // Check parameters
6754 $langs->load('admin');
6755 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6756 $extrafields = new ExtraFields($this->db);
6757 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6758
6759 // Eliminate copied source object extra fields that do not exist in target object
6760 $new_array_options = array();
6761 foreach ($this->array_options as $key => $value) {
6762 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6763 $new_array_options[$key] = $value;
6764 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6765 $new_array_options['options_'.$key] = $value;
6766 }
6767 }
6768
6769 foreach ($new_array_options as $key => $value) {
6770 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6771 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6772 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6773 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6774 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6775 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6776 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6777
6778 // If we clone, we have to clean unique extrafields to prevent duplicates.
6779 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6780 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && !empty($attributeUnique)) {
6781 $new_array_options[$key] = null;
6782 }
6783
6784 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6785 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6786 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6787 $new_array_options[$key] = null;
6788 }
6789
6790 // Similar code than into insertExtraFields
6791 if ($attributeRequired) {
6792 $v = $this->array_options[$key];
6793 if (ExtraFields::isEmptyValue($v, $attributeType)) {
6794 $langs->load("errors");
6795 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6796 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6797 return -1;
6798 }
6799 }
6800
6801 if (!empty($attrfieldcomputed)) {
6802 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6803 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
6804 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
6805 $new_array_options[$key] = $value;
6806 } else {
6807 $new_array_options[$key] = null;
6808 }
6809 }
6810
6811 switch ($attributeType) {
6812 case 'int':
6813 case 'duration':
6814 case 'stars':
6815 if (!is_numeric($value) && $value != '') {
6816 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6817 return -1;
6818 } elseif ($value === '') {
6819 $new_array_options[$key] = null;
6820 }
6821 break;
6822 case 'boolean':
6823 if ($value === '' || $value === false || $value === null) {
6824 $new_array_options[$key] = null;
6825 }
6826 break;
6827 case 'price':
6828 case 'double':
6829 $value = price2num($value);
6830 if (!is_numeric($value) && $value != '') {
6831 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6832 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6833 return -1;
6834 } elseif ($value == '') {
6835 $value = null;
6836 }
6837 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6838 $new_array_options[$key] = $value;
6839 break;
6840 /*case 'select': // Not required, we chose value='0' for undefined values
6841 if ($value=='-1')
6842 {
6843 $this->array_options[$key] = null;
6844 }
6845 break;*/
6846 case 'password':
6847 $algo = '';
6848 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6849 // If there is an encryption choice, we use it to encrypt data before insert
6850 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6851 $algo = reset($tmparrays);
6852 if ($algo != '') {
6853 //global $action; // $action may be 'create', 'update', 'update_extras'...
6854 //var_dump($action);
6855 //var_dump($this->oldcopy);exit;
6856 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
6857 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6858 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6859 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6860 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6861 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6862 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6863 } else {
6864 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6865 }
6866 } else {
6867 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6868 }
6869 } else {
6870 // If value has changed
6871 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6872 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6873 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6874 } else {
6875 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6876 }
6877 } else {
6878 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
6879 }
6880 }
6881 } else {
6882 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6883 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6884 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
6885 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6886 } else {
6887 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6888 }
6889 }
6890 } else {
6891 // No encryption
6892 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6893 }
6894 } else { // Common usage
6895 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6896 }
6897 break;
6898 case 'date':
6899 case 'datetime':
6900 // If data is a string instead of a timestamp, we convert it
6901 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6902 $this->array_options[$key] = strtotime($this->array_options[$key]);
6903 }
6904 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6905 break;
6906 case 'datetimegmt':
6907 // If data is a string instead of a timestamp, we convert it
6908 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6909 $this->array_options[$key] = strtotime($this->array_options[$key]);
6910 }
6911 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
6912 break;
6913 case 'link':
6914 $param_list = array_keys($attributeParam['options']);
6915 // 0 : ObjectName
6916 // 1 : classPath
6917 $InfoFieldList = explode(":", $param_list[0]);
6918 dol_include_once($InfoFieldList[1]);
6919 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
6920 if ($value == '-1') { // -1 is key for no defined in combo list of objects
6921 $new_array_options[$key] = '';
6922 } elseif ($value) {
6923 $object = new $InfoFieldList[0]($this->db);
6924 '@phan-var-force CommonObject $object';
6925
6926 $objectId = 0;
6927
6928 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
6929 if (is_numeric($value)) {
6930 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
6931 } else {
6932 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
6933 }
6934
6935 $obj = $this->db->getRow($sqlFetchObject);
6936
6937 // getRow() returns an object on success, int 0 when the query succeeded but
6938 // returned no row, and false on SQL failure. Testing "!== false" let the 0
6939 // through as a success: $obj->rowid on an int is null, $res was set to 1 and
6940 // null was stored in the column while a success was reported.
6941 if (is_object($obj)) {
6942 $objectId = $obj->rowid;
6943 $res = 1;
6944 } else {
6945 $res = -1;
6946 }
6947
6948 if ($res > 0) {
6949 $new_array_options[$key] = $objectId;
6950 } else {
6951 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6952 return -1;
6953 }
6954 }
6955 } else {
6956 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6957 }
6958 break;
6959 case 'checkbox':
6960 case 'chkbxlst':
6961 if (is_array($this->array_options[$key])) {
6962 $new_array_options[$key] = implode(',', $this->array_options[$key]);
6963 } else {
6964 $new_array_options[$key] = $this->array_options[$key];
6965 }
6966 break;
6967 }
6968 }
6969
6970 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
6971
6972 foreach ($new_array_options as $key => $newValue) {
6973 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6974 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6975 if ($attributeType === 'separate') {
6976 // separator extrafields don't have data per object so they don't have a comlumn in the database
6977 continue;
6978 }
6979 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
6980 // Add field of attribute
6981 if (! $geoDataType) {
6982 // not a geodata type
6983 if ($newValue != '') {
6984 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
6985 } else {
6986 $sqlColumnValues[$attributeKey] = 'null';
6987 }
6988 continue;
6989 }
6990 if (empty($newValue)) {
6991 $sqlColumnValues[$attributeKey] = 'null';
6992 continue;
6993 }
6994 if (preg_match('/error/i', $newValue)) {
6995 dol_syslog(
6996 'Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request',
6997 LOG_WARNING
6998 );
6999 $sqlColumnValues[$attributeKey] = 'null';
7000 continue;
7001 }
7002
7003 // Geodata type: Text must be a WKT string. Examples:
7004 // - point => "POINT(15 20)"
7005 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
7006 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
7007 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
7008 $sqlColumnValues[$key] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')";
7009 }
7010
7011 $this->db->begin();
7012
7013 $table_element = $this->table_element;
7014 if ($table_element == 'categorie') {
7015 $table_element = 'categories'; // For compatibility
7016 }
7017 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
7018
7019 dol_syslog(get_class($this)."::insertExtraFields delete then insert", LOG_DEBUG);
7020
7021 // if the extrafields row already exists for the object, we update it
7022 if ($this->db->getRow("SELECT 1 FROM {$extrafieldsTable} WHERE fk_object = ".((int) $this->id))) {
7023 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7024 if (empty($sqlColumnValues)) {
7025 // No column of the target element to update. This happens when the object was created from
7026 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7027 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7028 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7029 // that would produce an invalid SQL statement.
7030 $this->db->commit();
7031 return 1;
7032 }
7033 $sqlColumnValueString = implode(
7034 ',',
7039 array_map(function ($key) use ($sqlColumnValues) {
7040 return "{$key} = {$sqlColumnValues[$key]}";
7041 }, array_keys($sqlColumnValues))
7042 );
7043 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7044 } else {
7045 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7046 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7047 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7048 if (isset($sqlColumnValues[$key])) {
7049 // extrafield value already provided: no need to add it
7050 continue;
7051 }
7052 // default value: empty string, except for 'int', 'double' and 'price'
7053 $sqlColumnValues[$key] = "''";
7054 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7055 $sqlColumnValues[$key] = 0;
7056 }
7057 }
7058 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7059 $sqlValues = implode(',', array_values($sqlColumnValues));
7060 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7061 }
7062
7063 $resql = $this->db->query($sql);
7064 if (!$resql) {
7065 $this->error = $this->db->lasterror();
7066 $error++;
7067 }
7068
7069 if (!$error && $trigger) {
7070 // Call trigger
7071 $this->context = array('extrafieldaddupdate' => 1);
7072 $result = $this->call_trigger($trigger, $userused);
7073 if ($result < 0) {
7074 $error++;
7075 }
7076 // End call trigger
7077 }
7078
7079 if ($error) {
7080 $this->db->rollback();
7081 return -1;
7082 } else {
7083 $this->db->commit();
7084 return 1;
7085 }
7086 }
7087
7099 public function insertExtraLanguages($trigger = '', $userused = null)
7100 {
7101 global $langs, $user;
7102
7103 if (empty($userused)) {
7104 $userused = $user;
7105 }
7106
7107 $error = 0;
7108
7109 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7110 return 0; // For avoid conflicts if trigger used
7111 }
7112
7113 if (is_array($this->array_languages)) {
7114 $new_array_languages = $this->array_languages;
7115
7116 foreach ($new_array_languages as $key => $value) {
7117 $attributeKey = $key;
7118 $attributeType = $this->fields[$attributeKey]['type'];
7119 $attributeLabel = $this->fields[$attributeKey]['label'];
7120
7121 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7122 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7123
7124 switch ($attributeType) {
7125 case 'int':
7126 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7127 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7128 return -1;
7129 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7130 $new_array_languages[$key] = null;
7131 }
7132 break;
7133 case 'double':
7134 $value = price2num((string) $value);
7135 if (!is_numeric($value) && $value != '') {
7136 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7137 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7138 return -1;
7139 } elseif ($value == '') {
7140 $new_array_languages[$key] = null;
7141 } else {
7142 $new_array_languages[$key] = $value;
7143 }
7144 break;
7145 /*case 'select': // Not required, we chose value='0' for undefined values
7146 if ($value=='-1')
7147 {
7148 $this->array_options[$key] = null;
7149 }
7150 break;*/
7151 }
7152 }
7153
7154 $this->db->begin();
7155
7156 $table_element = $this->table_element;
7157 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7158 $table_element = 'categories'; // For compatibility
7159 }
7160
7161 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7162
7163 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7164 foreach ($langcodearray as $langcode => $value) {
7165 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7166 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7167 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7168 $this->db->query($sql_del);
7169
7170 if ($value !== '') {
7171 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7172 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7173 $sql .= ")";
7174
7175 $resql = $this->db->query($sql);
7176 if (!$resql) {
7177 $this->error = $this->db->lasterror();
7178 $error++;
7179 break;
7180 }
7181 }
7182 }
7183 }
7184
7185 if (!$error && $trigger) {
7186 // Call trigger
7187 $this->context = array('extralanguagesaddupdate' => 1);
7188 $result = $this->call_trigger($trigger, $userused);
7189 if ($result < 0) {
7190 $error++;
7191 }
7192 // End call trigger
7193 }
7194
7195 if ($error) {
7196 $this->db->rollback();
7197 return -1;
7198 } else {
7199 $this->db->commit();
7200 return 1;
7201 }
7202 } else {
7203 return 0;
7204 }
7205 }
7206
7217 public function updateExtraField($key, $trigger = null, $userused = null)
7218 {
7219 global $langs, $user, $hookmanager;
7220
7221 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7222 return 0;
7223 }
7224
7225 if (empty($userused)) {
7226 $userused = $user;
7227 }
7228
7229 $error = 0;
7230
7231 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7232 // Check parameters
7233 $langs->load('admin');
7234 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7235 $extrafields = new ExtraFields($this->db);
7236 $extrafields->fetch_name_optionals_label($this->table_element);
7237 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7238 // Extrafield not defined for this object type: nothing to update
7239 return 0;
7240 }
7241 $value = $this->array_options["options_".$key];
7242
7243 $attributeKey = $key;
7244 $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
7245 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
7246 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
7247 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
7248 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7249 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
7250
7251 // Similar code than into insertExtraFields
7252 if ($attributeRequired) {
7253 $mandatorypb = false;
7254 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7255 $mandatorypb = true;
7256 }
7257 if ($this->array_options["options_".$key] === '') {
7258 $mandatorypb = true;
7259 }
7260 if ($mandatorypb) {
7261 $langs->load("errors");
7262 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7263 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7264 return -1;
7265 }
7266 }
7267
7268 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7269 $new_array_options = $this->array_options;
7270
7271 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7272 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7273 if (!empty($attrfieldcomputed)) {
7274 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7275 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7276 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7277
7278 $new_array_options["options_".$key] = $value;
7279
7280 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7281 } else {
7282 $new_array_options["options_".$key] = null;
7283
7284 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7285 }
7286 }
7287
7288 switch ($attributeType) {
7289 case 'int':
7290 if (!is_numeric($value) && $value != '') {
7291 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7292 return -1;
7293 } elseif ($value === '') {
7294 $new_array_options["options_".$key] = null;
7295
7296 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7297 }
7298 break;
7299 case 'price':
7300 case 'double':
7301 $value = price2num($value);
7302 if (!is_numeric($value) && $value != '') {
7303 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7304 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7305 return -1;
7306 } elseif ($value === '') {
7307 $value = null;
7308 }
7309 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7310 $new_array_options["options_".$key] = $value;
7311
7312 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7313 break;
7314 /*case 'select': // Not required, we chose value='0' for undefined values
7315 if ($value=='-1')
7316 {
7317 $new_array_options["options_".$key] = $value;
7318
7319 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7320 }
7321 break;*/
7322 case 'password':
7323 $algo = '';
7324 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7325 // If there is an encryption choice, we use it to encrypt data before insert
7326 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7327 $algo = reset($tmparrays);
7328 if ($algo != '') {
7329 //global $action; // $action may be 'create', 'update', 'update_extras'...
7330 //var_dump($action);
7331 //var_dump($this->oldcopy);exit;
7332 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7333 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
7334 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7335 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.
7336 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7337 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7338 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7339 } else {
7340 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7341 }
7342 } else {
7343 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7344 }
7345 } else {
7346 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7347 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7348 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7349 } else {
7350 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7351 }
7352 } else {
7353 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7354 }
7355 }
7356 } else {
7357 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7358 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7359 } else {
7360 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7361 }
7362 }
7363 } else {
7364 // No encryption
7365 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7366 }
7367 } else { // Common usage
7368 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7369 }
7370
7371 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7372 break;
7373 case 'date':
7374 case 'datetime':
7375 if (empty($this->array_options["options_".$key])) {
7376 $new_array_options["options_".$key] = null;
7377
7378 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7379 } else {
7380 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7381 }
7382 break;
7383 case 'datetimegmt':
7384 if (empty($this->array_options["options_".$key])) {
7385 $new_array_options["options_".$key] = null;
7386
7387 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7388 } else {
7389 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7390 }
7391 break;
7392 case 'boolean':
7393 if (empty($this->array_options["options_".$key])) {
7394 $new_array_options["options_".$key] = null;
7395
7396 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7397 }
7398 break;
7399 case 'link':
7400 if ($this->array_options["options_".$key] === '') {
7401 $new_array_options["options_".$key] = null;
7402
7403 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7404 }
7405 break;
7406 /*
7407 case 'link':
7408 $param_list = array_keys($attributeParam['options']);
7409 // 0 : ObjectName
7410 // 1 : classPath
7411 $InfoFieldList = explode(":", $param_list[0]);
7412 dol_include_once($InfoFieldList[1]);
7413 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7414 {
7415 if ($value == '-1') // -1 is key for no defined in combo list of objects
7416 {
7417 $new_array_options[$key] = '';
7418 } elseif ($value) {
7419 $object = new $InfoFieldList[0]($this->db);
7420 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7421 else $res = $object->fetch(0, $value); // For compatibility
7422
7423 if ($res > 0) $new_array_options[$key] = $object->id;
7424 else {
7425 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7426 $this->db->rollback();
7427 return -1;
7428 }
7429 }
7430 } else {
7431 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7432 }
7433 break;
7434 */
7435 case 'checkbox':
7436 case 'chkbxlst':
7437 $new_array_options = array();
7438 if (is_array($this->array_options["options_".$key])) {
7439 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7440 } else {
7441 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7442 }
7443
7444 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7445 break;
7446 }
7447
7448 $this->db->begin();
7449
7450 $linealreadyfound = 0;
7451
7452 // 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)
7453 $table_element = $this->table_element;
7454 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7455 $table_element = 'categories'; // For compatibility
7456 }
7457
7458 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7459 $resql = $this->db->query($sql);
7460 if ($resql) {
7461 $tmpobj = $this->db->fetch_object($resql);
7462 if ($tmpobj) {
7463 $linealreadyfound = $tmpobj->nb;
7464 }
7465 }
7466
7467 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7468 if ($linealreadyfound) {
7469 if ($this->array_options["options_".$key] === null) {
7470 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
7471 } else {
7472 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7473 }
7474 $sql .= " WHERE fk_object = ".((int) $this->id);
7475
7476 $resql = $this->db->query($sql);
7477 if (!$resql) {
7478 $error++;
7479 $this->error = $this->db->lasterror();
7480 }
7481 } else {
7482 $result = $this->insertExtraFields('', $user);
7483 if ($result < 0) {
7484 $error++;
7485 }
7486 }
7487
7488 if (!$error) {
7489 $parameters = array('key' => $key);
7490 global $action;
7491 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7492 if ($reshook < 0) {
7493 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7494 }
7495 }
7496
7497 if (!$error && $trigger) {
7498 // Call trigger
7499 $this->context = array('extrafieldupdate' => 1);
7500 $result = $this->call_trigger($trigger, $userused);
7501 if ($result < 0) {
7502 $error++;
7503 }
7504 // End call trigger
7505 }
7506
7507 if ($error) {
7508 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7509 $this->db->rollback();
7510 return -1;
7511 } else {
7512 $this->db->commit();
7513 return 1;
7514 }
7515 } else {
7516 return 0;
7517 }
7518 }
7519
7526 public function getExtraField($key)
7527 {
7528 return $this->array_options['options_'.$key] ?? null;
7529 }
7530
7538 public function setExtraField($key, $value)
7539 {
7540 $this->array_options['options_'.$key] = $value;
7541 }
7542
7553 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7554 {
7555 global $user;
7556
7557 if (empty($userused)) {
7558 $userused = $user;
7559 }
7560
7561 //$error = 0;
7562
7563 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7564 return 0; // For avoid conflicts if trigger used
7565 }
7566
7567 return 0;
7568 }
7569
7570
7585 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7586 {
7587 global $conf, $langs, $form;
7588
7589
7590 // 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...)
7591
7592 if (!is_object($form)) {
7593 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7594 $form = new Form($this->db);
7595 }
7596
7597 if (!empty($this->fields)) {
7598 $val = $this->fields[$key];
7599 }
7600
7601 // Validation tests and output
7602 $fieldValidationErrorMsg = '';
7603 $validationClass = '';
7604 $fieldValidationErrorMsg = $this->getFieldError($key);
7605 if (!empty($fieldValidationErrorMsg)) {
7606 $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
7607 } else {
7608 $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
7609 }
7610
7611 //$valuemultiselectinput = array();
7612 $out = '';
7613 $type = '';
7614 $isDependList = 0;
7615 $param = array();
7616 $param['options'] = array();
7617 $reg = array();
7618 // @phan-suppress-next-line PhanTypeMismatchProperty
7619 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7620 // Because we work on extrafields
7621 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7622 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7623 $type = 'link';
7624 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7625 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7626 $type = 'link';
7627 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7628 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7629 $type = 'link';
7630 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7631 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7632 $type = 'sellist';
7633 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7634 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7635 $type = 'sellist';
7636 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7637 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7638 $type = 'sellist';
7639 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7640 $param['options'] = array($reg[1] => 'N');
7641 $type = 'chkbxlst';
7642 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7643 $param['options'] = array();
7644 $type = 'varchar';
7645 $size = $reg[1];
7646 } elseif (preg_match('/varchar/', $val['type'])) {
7647 $param['options'] = array();
7648 $type = 'varchar';
7649 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7650 $param['options'] = array();
7651 $type = 'stars';
7652 $size = $reg[1];
7653 } else {
7654 $param['options'] = array();
7655 $type = $this->fields[$key]['type'];
7656 }
7657 //var_dump($type); var_dump($param['options']);
7658
7659 // Special case that force options and type ($type can be integer, varchar, ...)
7660 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7661 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7662 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7663 if (empty($this->fields[$key]['multiinput'])) {
7664 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7665 }
7666 }
7667
7668 $label = $this->fields[$key]['label'];
7669 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7670 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7671 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7672 // @phan-suppress-next-line PhanTypeMismatchProperty
7673 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7674 // @phan-suppress-next-line PhanTypeMismatchProperty
7675 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7676 // @phan-suppress-next-line PhanTypeMismatchProperty
7677 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7678 // @phan-suppress-next-line PhanTypeMismatchProperty
7679 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7680 // @phan-suppress-next-line PhanTypeMismatchProperty
7681 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7682
7683 // @phan-suppress-next-line PhanTypeMismatchProperty
7684 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7685 // @phan-suppress-next-line PhanTypeMismatchProperty
7686 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7687 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7688
7689 $objectid = $this->id;
7690
7691 if ($computed) {
7692 if (!preg_match('/^search_/', $keyprefix)) {
7693 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7694 } else {
7695 return '';
7696 }
7697 }
7698
7699 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7700 if (empty($morecss) && !empty($val['css'])) {
7701 $morecss = $val['css'];
7702 } elseif (empty($morecss)) {
7703 if ($type == 'date') {
7704 $morecss = 'minwidth100imp';
7705 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7706 $morecss = 'minwidth200imp';
7707 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7708 $morecss = 'maxwidth75';
7709 } elseif ($type == 'url') {
7710 $morecss = 'minwidth400';
7711 } elseif ($type == 'boolean') {
7712 $morecss = '';
7713 } else {
7714 if (is_numeric($size) && round((float) $size) < 12) {
7715 $morecss = 'minwidth100';
7716 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7717 $morecss = 'minwidth200';
7718 } else {
7719 $morecss = 'minwidth400';
7720 }
7721 }
7722 }
7723
7724 // Add validation state class
7725 if (!empty($validationClass)) {
7726 $morecss .= $validationClass;
7727 }
7728
7729 if (in_array($type, array('date'))) {
7730 $tmp = explode(',', $size);
7731 $newsize = $tmp[0];
7732 $showtime = 0;
7733
7734 // Do not show current date when field not required (see selectDate() method)
7735 if (!$required && $value == '') {
7736 $value = '-1';
7737 }
7738
7739 // TODO Must also support $moreparam
7740 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7741 } elseif (in_array($type, array('datetime'))) {
7742 $tmp = explode(',', $size);
7743 $newsize = $tmp[0];
7744 $showtime = 1;
7745
7746 // Do not show current date when field not required (see selectDate() method)
7747 if (!$required && $value == '') {
7748 $value = '-1';
7749 }
7750
7751 // TODO Must also support $moreparam
7752 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7753 } elseif (in_array($type, array('duration'))) {
7754 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7755 } elseif (in_array($type, array('int', 'integer'))) {
7756 $tmp = explode(',', $size);
7757 $newsize = $tmp[0];
7758 $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' : '').'>';
7759 } elseif (in_array($type, array('real'))) {
7760 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7761 } elseif (preg_match('/varchar/', (string) $type)) {
7762 $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' : '').'>';
7763 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7764 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7765 } elseif (preg_match('/^text/', (string) $type)) {
7766 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7767 if (!empty($param['options'])) {
7768 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7769 //var_dump($param['options']);
7770 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7771 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7772 $out .= "<script>";
7773 $out .= '
7774 function handlemultiinputdisabling(htmlname){
7775 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
7776 multiinput = $("#"+htmlname+"_multiinput");
7777 multiinput.find("option").each(function(){
7778 tmpval = $("#"+htmlname).val();
7779 tmpvalarray = tmpval.split("\n");
7780 valtotest = $(this).val();
7781 if(tmpvalarray.includes(valtotest)){
7782 $(this).prop("disabled",true);
7783 } else {
7784 if($(this).prop("disabled") == true){
7785 console.log(valtotest)
7786 $(this).prop("disabled", false);
7787 }
7788 }
7789 });
7790 }
7791
7792 $(document).ready(function () {
7793 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
7794 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
7795 tmpvalarray = tmpval.split(",");
7796 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
7797 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
7798 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
7799 if(tmpval == ""){
7800 tmpval = valtotest;
7801 } else {
7802 tmpval = tmpval + "\n" + valtotest;
7803 }
7804 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
7805 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7806 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
7807 } else {
7808 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
7809 }
7810 });
7811 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
7812 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7813 });
7814 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7815 })';
7816 $out .= "</script>";
7817 $value = str_replace(',', "\n", $value);
7818 }
7819 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7820 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
7821 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
7822 } else {
7823 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7824 }
7825 } elseif (preg_match('/^html/', (string) $type)) {
7826 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7827 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7828 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
7829 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
7830 } else {
7831 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7832 }
7833 } elseif ($type == 'boolean') {
7834 $checked = '';
7835 if (!empty($value)) {
7836 $checked = ' checked value="1" ';
7837 } else {
7838 $checked = ' value="1" ';
7839 }
7840 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
7841 } elseif ($type == 'price') {
7842 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7843 $value = price($value);
7844 }
7845 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
7846 } elseif ($type == 'stars') {
7847 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7848 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
7849 $i = 1;
7850 while ($i <= $size) {
7851 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
7852 $i++;
7853 }
7854 $out .= '</div>';
7855 $out .= '<script>
7856 jQuery(function($) { /* commonobject.class.php 1 */
7857 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
7858 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
7859 container.find(".star").each(function() {
7860 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7861 });
7862 container.find(".star").on("mouseover", function() {
7863 let selectedStar = $(this).data("value");
7864 container.find(".star").each(function() {
7865 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
7866 });
7867 });
7868 container.on("mouseout", function() {
7869 container.find(".star").each(function() {
7870 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7871 });
7872 });
7873 container.find(".star").off("click").on("click", function() {
7874 selectedStars = $(this).data("value");
7875 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
7876 selectedStars = 0;
7877 }
7878 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
7879 container.find(".star").each(function() {
7880 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7881 });
7882 });
7883 });
7884 </script>';
7885 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7886 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7887 $value = price($value);
7888 }
7889 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
7890 } elseif ($type == 'select') { // combo list
7891 $out = '';
7892 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7893 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7894 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7895 }
7896
7897 $tmpselect = '';
7898 $nbchoice = 0;
7899
7900 foreach ($param['options'] as $keyb => $valb) {
7901 if ((string) $keyb == '') {
7902 continue;
7903 }
7904 if (strpos($valb, "|") !== false) {
7905 list($valb, $parent) = explode('|', $valb);
7906 }
7907 $nbchoice++;
7908 $tmpselect .= '<option value="'.$keyb.'"';
7909 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
7910 if (!empty($parent)) {
7911 $isDependList = 1;
7912 }
7913 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
7914 $tmpselect .= '>'.$langs->trans($valb).'</option>';
7915 }
7916
7917 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
7918 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
7919 $out .= '<option value="0">&nbsp;</option>';
7920 }
7921 $out .= $tmpselect;
7922 $out .= '</select>';
7923 } elseif ($type == 'sellist') {
7924 $out = '';
7925 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7926 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7927 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7928 }
7929
7930 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
7931 if (is_array($param['options'])) {
7932 $tmpparamoptions = array_keys($param['options']);
7933 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
7934
7935 $InfoFieldList = explode(":", $paramoptions[0], 5);
7936 // 0 : tableName
7937 // 1 : label field name
7938 // 2 : key fields name (if different of rowid)
7939 // optional parameters...
7940 // 3 : key field parent (for dependent lists). How this is used ?
7941 // 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".
7942 // 5 : string category type. This replace the filter.
7943 // 6 : ids categories list separated by comma for category root. This replace the filter.
7944 // 7 : sort field
7945
7946 // If there is filter
7947 if (! empty($InfoFieldList[4])) {
7948 $pos = 0;
7949 $parenthesisopen = 0;
7950 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
7951 if (substr($InfoFieldList[4], $pos, 1) == '(') {
7952 $parenthesisopen++;
7953 }
7954 if (substr($InfoFieldList[4], $pos, 1) == ')') {
7955 $parenthesisopen--;
7956 }
7957 $pos++;
7958 }
7959 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
7960 $tmpafter = substr($InfoFieldList[4], $pos + 1);
7961 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
7962 $InfoFieldList[4] = $tmpbefore;
7963 if ($tmpafter !== '') {
7964 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
7965 }
7966 //var_dump($InfoFieldList);
7967
7968 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
7969 $reg = array();
7970 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
7971 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
7972 }
7973
7974 //var_dump($InfoFieldList);
7975 }
7976
7977 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
7978
7979 $parentName = '';
7980 $parentField = '';
7981
7982 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
7983
7984 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7985 if (strpos($InfoFieldList[4], 'extra.') !== false) {
7986 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
7987 } else {
7988 $keyList = $InfoFieldList[2].' as rowid';
7989 }
7990 }
7991 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
7992 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
7993 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
7994 $keyList .= ', main.'.$parentField;
7995 } else {
7996 $keyList .= ', '.$parentField;
7997 }
7998 }
7999
8000 $filter_categorie = false;
8001 if (count($InfoFieldList) > 5) {
8002 if ($InfoFieldList[0] == 'categorie') {
8003 $filter_categorie = true;
8004 }
8005 }
8006
8007 if (!$filter_categorie) {
8008 $fields_label = explode('|', $InfoFieldList[1]);
8009 if (is_array($fields_label)) {
8010 $keyList .= ', ';
8011 $keyList .= implode(', ', $fields_label);
8012 }
8013
8014 $sqlwhere = '';
8015 $sql = "SELECT " . $keyList;
8016 $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
8017
8018 if (!empty($InfoFieldList[4])) {
8019 // can use SELECT request
8020 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8021 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8022 }
8023
8024 // current object id can be use into filter
8025 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8026 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8027 } else {
8028 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8029 }
8030
8031 // We have to join on extrafield table
8032 $errstr = '';
8033 if (strpos($InfoFieldList[4], 'extra') !== false) {
8034 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8035 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8036 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8037 } else {
8038 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8039 }
8040 } else {
8041 $sqlwhere .= ' WHERE 1=1';
8042 }
8043
8044 // Add Usf filter on second line
8045 /*
8046 if ($Usf) {
8047 $errorstr = '';
8048 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8049 if (!$errorstr) {
8050 $sqlwhere .= $sqlusf;
8051 } else {
8052 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8053 }
8054 }
8055 */
8056
8057 // Some tables may have field, some other not. For the moment we disable it.
8058 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8059 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8060 }
8061 $sql .= $sqlwhere;
8062
8063 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8064 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8065 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8066 } else {
8067 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8068 }
8069 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8070 // print $sql;
8071
8072 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8073 $resql = $this->db->query($sql);
8074 if ($resql) {
8075 $out .= '<option value="0">&nbsp;</option>';
8076 $num = $this->db->num_rows($resql);
8077 $i = 0;
8078 while ($i < $num) {
8079 $labeltoshow = '';
8080 $obj = $this->db->fetch_object($resql);
8081
8082 // Several field into label (eq table:code|libelle:rowid)
8083 $notrans = false;
8084 $fields_label = explode('|', $InfoFieldList[1]);
8085 if (count($fields_label) > 1) {
8086 $notrans = true;
8087 foreach ($fields_label as $field_toshow) {
8088 $labeltoshow .= $obj->$field_toshow . ' ';
8089 }
8090 } else {
8091 $labeltoshow = $obj->{$InfoFieldList[1]};
8092 }
8093 $labeltoshow = dol_trunc($labeltoshow, 45);
8094
8095 if ($value == $obj->rowid) {
8096 foreach ($fields_label as $field_toshow) {
8097 $translabel = $langs->trans($obj->$field_toshow);
8098 if ($translabel != $obj->$field_toshow) {
8099 $labeltoshow = dol_trunc($translabel) . ' ';
8100 } else {
8101 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8102 }
8103 }
8104 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8105 } else {
8106 if (!$notrans) {
8107 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8108 if ($translabel != $obj->{$InfoFieldList[1]}) {
8109 $labeltoshow = dol_trunc($translabel, 18);
8110 } else {
8111 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8112 }
8113 }
8114 if (empty($labeltoshow)) {
8115 $labeltoshow = '(not defined)';
8116 }
8117 if ($value == $obj->rowid) {
8118 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8119 }
8120
8121 if (!empty($InfoFieldList[3]) && $parentField) {
8122 $parent = $parentName . ':' . $obj->{$parentField};
8123 $isDependList = 1;
8124 }
8125
8126 $out .= '<option value="' . $obj->rowid . '"';
8127 $out .= ($value == $obj->rowid ? ' selected' : '');
8128 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8129 $out .= '>' . $labeltoshow . '</option>';
8130 }
8131
8132 $i++;
8133 }
8134 $this->db->free($resql);
8135 } else {
8136 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8137 }
8138 } else {
8139 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8140 $categcode = $InfoFieldList[5];
8141 if (is_numeric($categcode)) {
8142 $categcode = Categorie::$MAP_ID_TO_CODE[(int) $InfoFieldList[5]];
8143 }
8144 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8145 $out .= '<option value="0">&nbsp;</option>';
8146 foreach ($data as $data_key => $data_value) {
8147 $out .= '<option value="' . $data_key . '"';
8148 $out .= ($value == $data_key ? ' selected' : '');
8149 $out .= '>' . $data_value . '</option>';
8150 }
8151 }
8152 }
8153 $out .= '</select>';
8154 } elseif ($type == 'checkbox') {
8155 $value_arr = explode(',', $value);
8156 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8157 } elseif ($type == 'radio') {
8158 $out = '';
8159 foreach ($param['options'] as $keyopt => $valopt) {
8160 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8161 $out .= ' value="'.$keyopt.'"';
8162 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8163 $out .= ($value == $keyopt ? 'checked' : '');
8164 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8165 }
8166 } elseif ($type == 'chkbxlst') {
8167 if (is_array($value)) {
8168 $value_arr = $value;
8169 } else {
8170 $value_arr = explode(',', $value);
8171 }
8172
8173 if (is_array($param['options'])) {
8174 $tmpparamoptions = array_keys($param['options']);
8175 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8176
8177 $InfoFieldList = explode(":", $paramoptions[0], 5);
8178 // 0 : tableName
8179 // 1 : label field name
8180 // 2 : key fields name (if different of rowid)
8181 // optional parameters...
8182 // 3 : key field parent (for dependent lists). How this is used ?
8183 // 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".
8184 // 5 : string category type. This replace the filter.
8185 // 6 : ids categories list separated by comma for category root. This replace the filter.
8186 // 7 : sort field
8187
8188 // If there is a filter
8189 if (! empty($InfoFieldList[4])) {
8190 $pos = 0;
8191 $parenthesisopen = 0;
8192 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8193 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8194 $parenthesisopen++;
8195 }
8196 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8197 $parenthesisopen--;
8198 }
8199 $pos++;
8200 }
8201 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8202 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8203 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8204 $InfoFieldList[4] = $tmpbefore;
8205 if ($tmpafter !== '') {
8206 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8207 }
8208
8209 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8210 $reg = array();
8211 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8212 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8213 }
8214
8215 //var_dump($InfoFieldList);
8216 }
8217
8218 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8219
8220 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8221
8222 $parentName = '';
8223 $parentField = '';
8224
8225 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8226
8227 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8228 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8229 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8230 } else {
8231 $keyList = $InfoFieldList[2].' as rowid';
8232 }
8233 }
8234 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8235 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8236 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8237 $keyList .= ', main.'.$parentField;
8238 } else {
8239 $keyList .= ', '.$parentField;
8240 }
8241 }
8242
8243 $filter_categorie = false;
8244 if (count($InfoFieldList) > 5) {
8245 if ($InfoFieldList[0] == 'categorie') {
8246 $filter_categorie = true;
8247 }
8248 }
8249
8250 // Common filter
8251 if (!$filter_categorie) {
8252 $fields_label = explode('|', $InfoFieldList[1]);
8253 if (is_array($fields_label)) {
8254 $keyList .= ', ';
8255 $keyList .= implode(', ', $fields_label);
8256 }
8257
8258 $sqlwhere = '';
8259 $sql = "SELECT " . $keyList;
8260 $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8261
8262 if (!empty($InfoFieldList[4])) {
8263 // can use SELECT request
8264 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8265 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8266 }
8267
8268 // current object id can be use into filter
8269 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8270 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8271 } else {
8272 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8273 }
8274
8275 // We have to join on extrafield table
8276 $errstr = '';
8277 if (strpos($InfoFieldList[4], 'extra') !== false) {
8278 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8279 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8280 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8281 } else {
8282 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8283 }
8284 } else {
8285 $sqlwhere .= ' WHERE 1=1';
8286 }
8287
8288 // Add Usf filter on second line
8289 /*
8290 if ($Usf) {
8291 $errorstr = '';
8292 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8293 if (!$errorstr) {
8294 $sqlwhere .= $sqlusf;
8295 } else {
8296 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8297 }
8298 }
8299 */
8300
8301 // Some tables may have field, some other not. For the moment we disable it.
8302 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8303 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8304 }
8305 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8306 // print $sql;
8307
8308 $sql .= $sqlwhere;
8309
8310 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8311
8312 $resql = $this->db->query($sql);
8313 if ($resql) {
8314 $num = $this->db->num_rows($resql);
8315 $i = 0;
8316
8317 $data = array();
8318
8319 while ($i < $num) {
8320 $labeltoshow = '';
8321 $obj = $this->db->fetch_object($resql);
8322
8323 $notrans = false;
8324 // Several field into label (eq table:code|libelle:rowid)
8325 $fields_label = explode('|', $InfoFieldList[1]);
8326 if (count($fields_label) > 1) {
8327 $notrans = true;
8328 foreach ($fields_label as $field_toshow) {
8329 $labeltoshow .= $obj->$field_toshow . ' ';
8330 }
8331 } else {
8332 $labeltoshow = $obj->{$InfoFieldList[1]};
8333 }
8334 $labeltoshow = dol_trunc($labeltoshow, 45);
8335
8336 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8337 foreach ($fields_label as $field_toshow) {
8338 $translabel = $langs->trans($obj->$field_toshow);
8339 if ($translabel != $obj->$field_toshow) {
8340 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8341 } else {
8342 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8343 }
8344 }
8345
8346 $data[$obj->rowid] = $labeltoshow;
8347 } else {
8348 if (!$notrans) {
8349 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8350 if ($translabel != $obj->{$InfoFieldList[1]}) {
8351 $labeltoshow = dol_trunc($translabel, 18);
8352 } else {
8353 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8354 }
8355 }
8356 if (empty($labeltoshow)) {
8357 $labeltoshow = '(not defined)';
8358 }
8359
8360 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8361 $data[$obj->rowid] = $labeltoshow;
8362 }
8363
8364 if (!empty($InfoFieldList[3]) && $parentField) {
8365 $parent = $parentName . ':' . $obj->{$parentField};
8366 $isDependList = 1;
8367 }
8368
8369 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8370 }
8371
8372 $i++;
8373 }
8374 $this->db->free($resql);
8375
8376 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8377 } else {
8378 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8379 }
8380 } else {
8381 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8382 $categcode = $InfoFieldList[5];
8383 if (is_numeric($categcode)) {
8384 $categcode = Categorie::$MAP_ID_TO_CODE[(int) $InfoFieldList[5]];
8385 }
8386 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8387 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8388 }
8389 }
8390 } elseif ($type == 'link') {
8391 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8392 // Filter can contains some ':' inside.
8393 $param_list = array_keys($param['options']);
8394 $param_list_array = explode(':', $param_list[0], 4);
8395
8396 $showempty = (($required && $default != '') ? 0 : 1);
8397
8398 if (!preg_match('/search_/', $keyprefix)) {
8399 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8400 // @phan-suppress-next-line PhanTypeMismatchProperty
8401 if (!empty($this->fields[$key]['picto'])) {
8402 $morecss .= ' widthcentpercentminusxx';
8403 } else {
8404 $morecss .= ' widthcentpercentminusx';
8405 }
8406 } else {
8407 // @phan-suppress-next-line PhanTypeMismatchProperty
8408 if (!empty($this->fields[$key]['picto'])) {
8409 $morecss .= ' widthcentpercentminusx';
8410 }
8411 }
8412 }
8413
8414 // $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.
8415
8416 // $valparent is a string 'dataobject@module:keyoffieldinfieldsarray' to find the record field to link to.
8417 // $valparent = $this->element.($this->module ? '@'.$this->module : '').':'.$key.$keysuffix;
8418
8419 // $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.
8420
8421 //$objectfield = $valparent;
8422 $objectfield = $val; // Is better than using old method $valparent
8423
8424 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8425 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8426
8427 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8428 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8429 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8430 list($class, $classfile) = explode(':', $param_list[0]);
8431 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8432 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8433 } else {
8434 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8435 }
8436 $paramforthenewlink = '';
8437 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8438 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8439 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8440 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8441 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8442 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8443 $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>';
8444 }
8445 }
8446 } elseif ($type == 'password') {
8447 // If prefix is 'search_', field is used as a filter, we use a common text field.
8448 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8449 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8450 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8451 } else {
8452 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8453 }
8454 } elseif ($type == 'array') {
8455 $newval = $val;
8456 $newval['type'] = 'varchar(256)';
8457
8458 $out = '';
8459 if (!empty($value)) {
8460 foreach ($value as $option) {
8461 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8462 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8463 }
8464 }
8465 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8466
8467 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8468 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8469
8470 if (!empty($conf->use_javascript_ajax)) {
8471 $out .= '
8472 <script nonce="'.getNonce().'">
8473 $(document).ready(function() {
8474 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8475 $("'.dol_escape_js($newInput).'").insertBefore(this);
8476 });
8477
8478 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8479 $(this).parent().remove();
8480 });
8481 });
8482 </script>';
8483 }
8484 }
8485 if (!empty($hidden)) {
8486 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8487 }
8488
8489 if ($isDependList == 1) {
8490 $out .= $this->getJSListDependancies('_common');
8491 }
8492 /* Add comments
8493 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8494 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8495 */
8496
8497 // Display error message for field
8498 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8499 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8500 }
8501
8502 return $out;
8503 }
8504
8518 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8519 {
8520 global $conf, $langs, $form;
8521
8522 // 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...)
8523
8524 if (!is_object($form)) {
8525 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8526 $form = new Form($this->db);
8527 }
8528
8529 //$label = empty($val['label']) ? '' : $val['label'];
8530 $type = empty($val['type']) ? '' : $val['type'];
8531 $size = empty($val['css']) ? '' : $val['css'];
8532 $reg = array();
8533
8534 // Convert var to be able to share same code than showOutputField of extrafields
8535 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8536 $type = 'varchar'; // convert varchar(xx) int varchar
8537 $size = $reg[1];
8538 } elseif (preg_match('/varchar/', $type)) {
8539 $type = 'varchar'; // convert varchar(xx) int varchar
8540 }
8541 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8542 // @phan-suppress-next-line PhanTypeMismatchProperty
8543 if (empty($this->fields[$key]['multiinput'])) {
8544 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8545 }
8546 }
8547 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8548 $type = 'link';
8549 }
8550
8551 $default = empty($val['default']) ? '' : $val['default'];
8552 $computed = empty($val['computed']) ? '' : $val['computed'];
8553 $unique = empty($val['unique']) ? '' : $val['unique'];
8554 $required = empty($val['required']) ? '' : $val['required'];
8555 $param = array();
8556 $param['options'] = array();
8557
8558 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8559 $param['options'] = $val['arrayofkeyval'];
8560 }
8561 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8562 $type = 'link';
8563 $stringforoptions = $reg[1].':'.$reg[2];
8564 // Special case: Force addition of getnomurlparam1 to -1 for users
8565 if ($reg[1] == 'User') {
8566 $stringforoptions .= ':#getnomurlparam1=-1';
8567 }
8568 $param['options'] = array($stringforoptions => $stringforoptions);
8569 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8570 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8571 $type = 'sellist';
8572 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8573 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8574 $type = 'sellist';
8575 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8576 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8577 $type = 'sellist';
8578 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8579 $param['options'] = array($reg[1] => 'N');
8580 $type = 'chkbxlst';
8581 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8582 $param['options'] = array();
8583 $type = 'stars';
8584 $size = $reg[1];
8585 }
8586
8587 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8588 $list = (empty($val['list']) ? '' : $val['list']);
8589 $help = (empty($val['help']) ? '' : $val['help']);
8590 $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)
8591
8592 if ($hidden) {
8593 return '';
8594 }
8595
8596 // If field is a computed field, value must become result of compute
8597 if ($computed) {
8598 // Make the eval of compute string
8599 //var_dump($computed);
8600 $value = dol_eval((string) $computed, 1, 0, '2');
8601 }
8602
8603 if (empty($morecss)) {
8604 if ($type == 'date') {
8605 $morecss = 'minwidth100imp';
8606 } elseif ($type == 'datetime' || $type == 'timestamp') {
8607 $morecss = 'minwidth200imp';
8608 } elseif (in_array($type, array('int', 'double', 'price'))) {
8609 $morecss = 'maxwidth75';
8610 } elseif ($type == 'url') {
8611 $morecss = 'minwidth400';
8612 } elseif ($type == 'boolean') {
8613 $morecss = '';
8614 } else {
8615 if (is_numeric($size) && round((float) $size) < 12) {
8616 $morecss = 'minwidth100';
8617 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8618 $morecss = 'minwidth200';
8619 } else {
8620 $morecss = 'minwidth400';
8621 }
8622 }
8623 }
8624
8625 // Format output value differently according to properties of field
8626 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8627 // @phan-suppress-next-line PhanTypeMismatchProperty
8628 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.
8629 $value = $this->getNomUrl(1, '', 0, '', 1);
8630 }
8631 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8632 $value = $this->getLibStatut(3);
8633 } elseif ($type == 'date') {
8634 if (!empty($value)) {
8635 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8636 } else {
8637 $value = '';
8638 }
8639 } elseif ($type == 'datetime' || $type == 'timestamp') {
8640 if (!empty($value)) {
8641 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8642 } else {
8643 $value = '';
8644 }
8645 } elseif ($type == 'duration') {
8646 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8647 if (!is_null($value) && $value !== '') {
8648 $value = convertSecondToTime((int) $value, 'allhourmin');
8649 }
8650 } elseif ($type == 'double' || $type == 'real') {
8651 if (!is_null($value) && $value !== '') {
8652 $value = price($value);
8653 }
8654 } elseif ($type == 'boolean') {
8655 $checked = '';
8656 if (!empty($value)) {
8657 $checked = ' checked ';
8658 }
8659 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8660 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8661 } else {
8662 $value = yn($value ? 1 : 0);
8663 }
8664 } elseif ($type == 'mail' || $type == 'email') {
8665 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8666 } elseif ($type == 'url') {
8667 $value = dol_print_url((string) $value, '_blank', 32, 1);
8668 } elseif ($type == 'phone') {
8669 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8670 } elseif ($type == 'ip') {
8671 $value = dol_print_ip((string) $value, 0);
8672 } elseif ($type == 'stars') {
8673 $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 : '').'>';
8674 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8675 $i = 1;
8676 while ($i <= $size) {
8677 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8678 $i++;
8679 }
8680 $value .= '</div>';
8681 $value .= '<script>
8682 $(document).ready(function() { /* commonobject.class.php 2 */
8683 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8684 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8685 container.find(".star").each(function() {
8686 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8687 });
8688 container.find(".star").on("mouseover", function() {
8689 let selectedStar = $(this).data("value");
8690 container.find(".star").each(function() {
8691 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8692 });
8693 });
8694 container.on("mouseout", function() {
8695 container.find(".star").each(function() {
8696 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8697 });
8698 });
8699 container.find(".star").off("click").on("click", function() {
8700 selectedStars = $(this).data("value");
8701 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8702 selectedStars = 0;
8703 }
8704 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8705 container.find(".star").each(function() {
8706 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8707 });
8708 $.ajax({
8709 url: "ajax/'.$this->element.'.php",
8710 method: "POST",
8711 data: {
8712 objectId: "'.$this->id.'",
8713 field: "'.$keyprefix.$key.$keysuffix.'",
8714 value: selectedStars,
8715 token: "'.newToken().'"
8716 },
8717 success: function(response) {
8718 var res = JSON.parse(response);
8719 console[res.status === "success" ? "log" : "error"](res.message);
8720 },
8721 error: function(xhr, status, error) {
8722 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8723 }
8724 });
8725 });
8726 });
8727 </script>';
8728 } elseif ($type == 'price') {
8729 if (!is_null($value) && $value !== '') {
8730 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8731 }
8732 } elseif ($type == 'select') {
8733 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8734 if (strpos($value, "|") !== false) {
8735 $value = $langs->trans(explode('|', $value)[0]);
8736 } elseif (! is_numeric($value)) {
8737 $value = $langs->trans($value);
8738 }
8739 } elseif ($type == 'sellist') {
8740 $param_list = array_keys($param['options']);
8741 $InfoFieldList = explode(":", $param_list[0]);
8742
8743 $selectkey = "rowid";
8744 $keyList = 'rowid';
8745
8746 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8747 $selectkey = $InfoFieldList[2];
8748 $keyList = $InfoFieldList[2].' as rowid';
8749 }
8750
8751 $fields_label = explode('|', $InfoFieldList[1]);
8752 if (is_array($fields_label)) {
8753 $keyList .= ', ';
8754 $keyList .= implode(', ', $fields_label);
8755 }
8756
8757 $filter_categorie = false;
8758 if (count($InfoFieldList) > 5) {
8759 if ($InfoFieldList[0] == 'categorie') {
8760 $filter_categorie = true;
8761 }
8762 }
8763
8764 $sql = "SELECT ".$keyList;
8765 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8766 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8767 $sql .= ' as main';
8768 }
8769 if ($selectkey == 'rowid' && empty($value)) {
8770 $sql .= " WHERE ".$selectkey." = 0";
8771 } elseif ($selectkey == 'rowid') {
8772 $sql .= " WHERE ".$selectkey." = ".((int) $value);
8773 } else {
8774 $sql .= " WHERE ".$selectkey." = '".$this->db->escape((string) $value)."'";
8775 }
8776
8777 //$sql.= ' AND entity = '.$conf->entity;
8778
8779 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
8780 $resql = $this->db->query($sql);
8781 if ($resql) {
8782 if (!$filter_categorie) {
8783 $value = ''; // value was used, so now we reset it to use it to build final output
8784 $numrows = $this->db->num_rows($resql);
8785 if ($numrows) {
8786 $obj = $this->db->fetch_object($resql);
8787
8788 // Several field into label (eq table:code|libelle:rowid)
8789 $fields_label = explode('|', $InfoFieldList[1]);
8790
8791 if (is_array($fields_label) && count($fields_label) > 1) {
8792 foreach ($fields_label as $field_toshow) {
8793 $translabel = '';
8794 if (!empty($obj->$field_toshow)) {
8795 $translabel = $langs->trans($obj->$field_toshow);
8796 }
8797 if ($translabel != $field_toshow) {
8798 $value .= dol_trunc($translabel, 18) . ' ';
8799 } else {
8800 $value .= $obj->$field_toshow . ' ';
8801 }
8802 }
8803 } else {
8804 $translabel = '';
8805 if (!empty($obj->{$InfoFieldList[1]})) {
8806 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8807 }
8808 if ($translabel != $obj->{$InfoFieldList[1]}) {
8809 $value = dol_trunc($translabel, 18);
8810 } else {
8811 $value = $obj->{$InfoFieldList[1]};
8812 }
8813 }
8814 }
8815 } else {
8816 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8817
8818 $toprint = array();
8819 $obj = $this->db->fetch_object($resql);
8820 $c = new Categorie($this->db);
8821 $c->fetch($obj->rowid);
8822 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8823 foreach ($ways as $way) {
8824 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8825 }
8826 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8827 }
8828 } else {
8829 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8830 }
8831 } elseif ($type == 'radio') {
8832 $value = $param['options'][(string) $value];
8833 } elseif ($type == 'checkbox') {
8834 $value_arr = explode(',', (string) $value);
8835 $value = '';
8836 if (is_array($value_arr) && count($value_arr) > 0) {
8837 $toprint = array();
8838 foreach ($value_arr as $keyval => $valueval) {
8839 if (!empty($valueval)) {
8840 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
8841 }
8842 }
8843 if (!empty($toprint)) {
8844 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8845 }
8846 }
8847 } elseif ($type == 'chkbxlst') {
8848 $value_arr = (isset($value) ? explode(',', $value) : array());
8849
8850 $param_list = array_keys($param['options']);
8851 $InfoFieldList = explode(":", $param_list[0]);
8852
8853 $selectkey = "rowid";
8854 $keyList = 'rowid';
8855
8856 if (count($InfoFieldList) >= 3) {
8857 $selectkey = $InfoFieldList[2];
8858 $keyList = $InfoFieldList[2].' as rowid';
8859 }
8860
8861 $fields_label = explode('|', $InfoFieldList[1]);
8862 if (is_array($fields_label)) {
8863 $keyList .= ', ';
8864 $keyList .= implode(', ', $fields_label);
8865 }
8866
8867 $filter_categorie = false;
8868 if (count($InfoFieldList) > 5) {
8869 if ($InfoFieldList[0] == 'categorie') {
8870 $filter_categorie = true;
8871 }
8872 }
8873
8874 $sql = "SELECT ".$keyList;
8875 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8876 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8877 $sql .= ' as main';
8878 }
8879 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
8880 // $sql.= ' AND entity = '.$conf->entity;
8881
8882 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
8883 $resql = $this->db->query($sql);
8884 if ($resql) {
8885 if (!$filter_categorie) {
8886 $value = ''; // value was used, so now we reset it to use it to build final output
8887 $toprint = array();
8888 while ($obj = $this->db->fetch_object($resql)) {
8889 // Several field into label (eq table:code|libelle:rowid)
8890 $fields_label = explode('|', $InfoFieldList[1]);
8891 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8892 if (is_array($fields_label) && count($fields_label) > 1) {
8893 foreach ($fields_label as $field_toshow) {
8894 $translabel = '';
8895 if (!empty($obj->$field_toshow)) {
8896 $translabel = $langs->trans($obj->$field_toshow);
8897 }
8898 if ($translabel != $field_toshow) {
8899 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8900 } else {
8901 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
8902 }
8903 }
8904 } else {
8905 $translabel = '';
8906 if (!empty($obj->{$InfoFieldList[1]})) {
8907 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8908 }
8909 if ($translabel != $obj->{$InfoFieldList[1]}) {
8910 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8911 } else {
8912 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
8913 }
8914 }
8915 }
8916 }
8917 } else {
8918 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8919
8920 $toprint = array();
8921 while ($obj = $this->db->fetch_object($resql)) {
8922 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8923 $c = new Categorie($this->db);
8924 $c->fetch($obj->rowid);
8925 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8926 foreach ($ways as $way) {
8927 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8928 }
8929 }
8930 }
8931 }
8932 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8933 } else {
8934 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8935 }
8936 } elseif ($type == 'link') {
8937 $out = '';
8938
8939 // only if something to display (perf)
8940 if ($value) {
8941 $param_list = array_keys($param['options']);
8942 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8943 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
8944
8945 $InfoFieldList = explode(":", $param_list[0]);
8946
8947 $classname = $InfoFieldList[0];
8948 $classpath = $InfoFieldList[1];
8949
8950 // Set $getnomurlparam1 et getnomurlparam2
8951 $getnomurlparam = 3;
8952 $getnomurlparam2 = '';
8953 $regtmp = array();
8954 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
8955 $getnomurlparam = $regtmp[1];
8956 }
8957 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
8958 $getnomurlparam2 = $regtmp[1];
8959 }
8960
8961 if (!empty($classpath)) {
8962 dol_include_once($InfoFieldList[1]);
8963
8964 if ($classname && !class_exists($classname)) {
8965 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
8966 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
8967 $element_prop = getElementProperties($classname);
8968 if ($element_prop) {
8969 $classname = $element_prop['classname'];
8970 }
8971 }
8972
8973
8974 if ($classname && class_exists($classname)) {
8975 $object = new $classname($this->db);
8976 '@phan-var-force CommonObject $object';
8977 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
8978 '@phan-var-force Product $object';
8979 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
8980 } else {
8981 $result = $object->fetch($value);
8982 }
8983 if ($result > 0) {
8984 if ($object->element === 'product') {
8985 '@phan-var-force Product $object';
8986 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
8987 if (isset($val['get_name_url_params'])) {
8988 $get_name_url_params = explode(':', $val['get_name_url_params']);
8989 if (!empty($get_name_url_params)) {
8990 $param_num_max = count($get_name_url_param_arr) - 1;
8991 foreach ($get_name_url_params as $param_num => $param_value) {
8992 if ($param_num > $param_num_max) {
8993 break;
8994 }
8995 $get_name_url_param_arr[$param_num] = $param_value;
8996 }
8997 }
8998 }
8999
9003 $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]);
9004 } else {
9005 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
9006 }
9007 } else {
9008 $value = '';
9009 }
9010 }
9011 } else {
9012 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
9013 return 'Error bad setup of extrafield';
9014 }
9015 } else {
9016 $value = '';
9017 }
9018 } elseif ($type == 'password') {
9019 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9020 //$value = preg_replace('/./i', '*', $value);
9021 } elseif ($type == 'array') {
9022 if (is_array($value)) {
9023 $value = implode('<br>', $value);
9024 } else {
9025 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9026 return 'Error unexpected result from code evaluation';
9027 }
9028 } else { // text|html|varchar
9029 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9030 $value = str_replace(',', "\n", $value);
9031 }
9032 $value = dol_htmlentitiesbr((string) $value);
9033 }
9034
9035 //print $type.'-'.$size.'-'.$value;
9036 $out = $value;
9037
9038 return is_null($out) ? '' : $out;
9039 }
9040
9047 public function clearFieldError($fieldKey)
9048 {
9049 $this->error = '';
9050 unset($this->validateFieldsErrors[$fieldKey]);
9051 }
9052
9060 public function setFieldError($fieldKey, $msg = '')
9061 {
9062 global $langs;
9063 if (empty($msg)) {
9064 $msg = $langs->trans("UnknownError");
9065 }
9066
9067 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9068 }
9069
9076 public function getFieldError($fieldKey)
9077 {
9078 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9079 return $this->validateFieldsErrors[$fieldKey];
9080 }
9081 return '';
9082 }
9083
9092 public function validateField($fields, $fieldKey, $fieldValue)
9093 {
9094 global $langs;
9095
9096 if (!class_exists('Validate')) {
9097 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9098 }
9099
9100 $this->clearFieldError($fieldKey);
9101
9102 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9103 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9104 return false;
9105 }
9106
9107 $val = $fields[$fieldKey];
9108
9109 $param = array();
9110 $param['options'] = array();
9111 $type = $val['type'];
9112
9113 $required = false;
9114 if (isset($val['notnull']) && $val['notnull'] === 1) {
9115 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9116 $required = true;
9117 }
9118
9119 $maxSize = 0;
9120 $minSize = 0;
9121
9122 //
9123 // PREPARE Elements
9124 //
9125 $reg = array();
9126
9127 // Convert var to be able to share same code than showOutputField of extrafields
9128 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9129 $type = 'varchar'; // convert varchar(xx) int varchar
9130 $maxSize = (int) $reg[1];
9131 } elseif (preg_match('/varchar/', $type)) {
9132 $type = 'varchar'; // convert varchar(xx) int varchar
9133 }
9134
9135 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9136 $type = 'select';
9137 }
9138
9139 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9140 $type = 'link';
9141 }
9142
9143 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9144 $param['options'] = $val['arrayofkeyval'];
9145 }
9146
9147 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9148 $type = 'link';
9149 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9150 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9151 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9152 $type = 'sellist';
9153 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9154 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9155 $type = 'sellist';
9156 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9157 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9158 $type = 'sellist';
9159 }
9160
9161 //
9162 // TEST Value
9163 //
9164
9165 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9166 $validate = new Validate($this->db, $langs);
9167
9168
9169 // little trick : to perform tests with good performances sort tests by quick to low
9170
9171 //
9172 // COMMON TESTS
9173 //
9174
9175 // Required test and empty value
9176 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9177 $this->setFieldError($fieldKey, $validate->error);
9178 return false;
9179 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9180 // if no value sent and the field is not mandatory, no need to perform tests
9181 return true;
9182 }
9183
9184 // MAX Size test
9185 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9186 $this->setFieldError($fieldKey, $validate->error);
9187 return false;
9188 }
9189
9190 // MIN Size test
9191 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9192 $this->setFieldError($fieldKey, $validate->error);
9193 return false;
9194 }
9195
9196 //
9197 // TESTS for TYPE
9198 //
9199
9200 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9201 if (!$validate->isTimestamp($fieldValue)) {
9202 $this->setFieldError($fieldKey, $validate->error);
9203 return false;
9204 } else {
9205 return true;
9206 }
9207 } elseif ($type == 'duration') {
9208 if (!$validate->isDuration($fieldValue)) {
9209 $this->setFieldError($fieldKey, $validate->error);
9210 return false;
9211 } else {
9212 return true;
9213 }
9214 } elseif (in_array($type, array('double', 'real', 'price'))) {
9215 // is numeric
9216 if (!$validate->isNumeric($fieldValue)) {
9217 $this->setFieldError($fieldKey, $validate->error);
9218 return false;
9219 } else {
9220 return true;
9221 }
9222 } elseif ($type == 'boolean') {
9223 if (!$validate->isBool($fieldValue)) {
9224 $this->setFieldError($fieldKey, $validate->error);
9225 return false;
9226 } else {
9227 return true;
9228 }
9229 } elseif ($type == 'mail' || $type == 'email') {
9230 if (!$validate->isEmail($fieldValue)) {
9231 $this->setFieldError($fieldKey, $validate->error);
9232 return false;
9233 }
9234 } elseif ($type == 'url') {
9235 if (!$validate->isUrl($fieldValue)) {
9236 $this->setFieldError($fieldKey, $validate->error);
9237 return false;
9238 } else {
9239 return true;
9240 }
9241 } elseif ($type == 'phone') {
9242 if (!$validate->isPhone($fieldValue)) {
9243 $this->setFieldError($fieldKey, $validate->error);
9244 return false;
9245 } else {
9246 return true;
9247 }
9248 } elseif ($type == 'select' || $type == 'radio') {
9249 if (!isset($param['options'][$fieldValue])) {
9250 $this->error = $langs->trans('RequireValidValue');
9251 return false;
9252 } else {
9253 return true;
9254 }
9255 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9256 $param_list = array_keys($param['options']);
9257 $InfoFieldList = explode(":", $param_list[0]);
9258 $value_arr = explode(',', $fieldValue);
9259 $value_arr = array_map(array($this->db, 'escape'), $value_arr);
9260
9261 $selectkey = "rowid";
9262 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9263 $selectkey = $InfoFieldList[2];
9264 }
9265
9266 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9267 $this->setFieldError($fieldKey, $validate->error);
9268 return false;
9269 } else {
9270 return true;
9271 }
9272 } elseif ($type == 'link') {
9273 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9274 $InfoFieldList = explode(":", $param_list[0]);
9275 $classname = $InfoFieldList[0];
9276 $classpath = $InfoFieldList[1];
9277 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9278 $lastIsFetchableError = $validate->error;
9279
9280 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9281 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9282 return true;
9283 }
9284
9285 $this->setFieldError($fieldKey, $lastIsFetchableError);
9286 return false;
9287 } else {
9288 return true;
9289 }
9290 }
9291
9292 // if no test failed all is ok
9293 return true;
9294 }
9295
9309 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9310 {
9311 global $db, $conf, $langs, $action, $form, $hookmanager;
9312
9313 if (!is_object($form)) {
9314 $form = new Form($db);
9315 }
9316 if (!is_object($extrafields)) {
9317 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9318 return 'Bad parameter extrafields for showOptionals';
9319 }
9320 if (!is_array($extrafields->attributes[$this->table_element])) {
9321 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9322 }
9323
9324 $out = '';
9325
9326 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9327 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9328
9329 if (empty($reshook)) {
9330 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) {
9331 $out .= "\n";
9332 $out .= '<!-- commonobject:showOptionals --> ';
9333 $out .= "\n";
9334
9335 $nbofextrafieldsshown = 0;
9336 $e = 0; // var to manage the modulo (odd/even)
9337
9338 $lastseparatorkeyfound = '';
9339 $extrafields_collapse_num = '';
9340 $extrafields_collapse_num_old = '';
9341 $i = 0;
9342
9343 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9344 $i++;
9345
9346 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9347 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9348 continue;
9349 }
9350
9351 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9352 $enabled = 1;
9353 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9354 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9355 }
9356 if (empty($enabled)) {
9357 continue;
9358 }
9359
9360 $visibility = 1;
9361 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9362 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9363 }
9364
9365 $perms = 1;
9366 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9367 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9368 }
9369
9370 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9371 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9372 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9373 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9374 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9375 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9376 $ef_name = 'options_' . $key;
9377 $ef_value = isset($this->array_options[$ef_name]) ? $this->array_options[$ef_name] : '';
9378 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . dolPrintHTMLForAttribute($ef_value) . '" />' . "\n";
9379 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9380 } elseif ($mode == 'view' && empty($visibility)) {
9381 continue;
9382 }
9383 if (empty($perms)) {
9384 continue;
9385 }
9386
9387 // Load language if required
9388 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9389 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9390 }
9391
9392 $colspan = 0;
9393 $value = null;
9394 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9395 if (array_key_exists('cols', $params)) {
9396 $colspan = $params['cols'];
9397 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9398 $reg = array();
9399 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9400 $colspan = $reg[1];
9401 } else {
9402 $colspan = $params['colspan'];
9403 }
9404 }
9405 }
9406 $colspan = intval($colspan);
9407
9408 switch ($mode) {
9409 case "view":
9410 $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
9411 break;
9412 case "create":
9413 case "edit":
9414 // We get the value of property found with GETPOST so it takes into account:
9415 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9416 $check = 'alphanohtml';
9417 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9418 $check = 'restricthtml';
9419 }
9420 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9421 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9422 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9423 if (is_array($getposttemp)) {
9424 // $getposttemp is an array but following code expects a comma separated string
9425 $value = implode(",", $getposttemp);
9426 } else {
9427 $value = $getposttemp;
9428 }
9429 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9430 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9431 } else {
9432 $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.
9433 }
9434 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9435 break;
9436 }
9437
9438 $nbofextrafieldsshown++;
9439
9440 // Output value of the current field
9441 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9442 $extrafields_collapse_num = $key;
9443 /*
9444 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9445 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9446 $extrafield_param_list = array_keys($extrafield_param['options']);
9447
9448 if (count($extrafield_param_list) > 0) {
9449 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9450
9451 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9452 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9453 $extrafields_collapse_num = $key;
9454 }
9455 }
9456 }
9457 */
9458
9459 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9460 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9461
9462 $lastseparatorkeyfound = $key;
9463 } else {
9464 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9465
9466 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9467 $csstyle = '';
9468 if (is_array($params) && count($params) > 0) {
9469 if (array_key_exists('class', $params)) {
9470 $class .= $params['class'].' ';
9471 }
9472 if (array_key_exists('style', $params)) {
9473 $csstyle = $params['style'];
9474 }
9475 }
9476
9477 // add html5 elements
9478 $domData = ' data-element="extrafield"';
9479 $domData .= ' data-targetelement="'.$this->element.'"';
9480 $domData .= ' data-targetid="'.$this->id.'"';
9481
9482 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9483 if ($display_type == 'card') {
9484 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9485 $colspan = 0;
9486 }
9487
9488 if ($action == 'selectlines') {
9489 $colspan++;
9490 }
9491 }
9492
9493 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9494 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9495 $force_values_on_change_company = (
9496 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9497 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9498 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9499 );
9500 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9501 $value = $this->array_options['options_'.$key] ?? $value;
9502 }
9503
9504 // Convert date into timestamp format (value in memory must be a timestamp)
9505 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9506 $datenotinstring = null;
9507 if (array_key_exists('options_'.$key, $this->array_options)) {
9508 $datenotinstring = $this->array_options['options_'.$key];
9509 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9510 $datenotinstring = $this->db->jdate($datenotinstring);
9511 }
9512 }
9513 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9514 $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;
9515 }
9516 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9517 $datenotinstring = null;
9518 if (array_key_exists('options_'.$key, $this->array_options)) {
9519 $datenotinstring = $this->array_options['options_'.$key];
9520 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9521 $datenotinstring = $this->db->jdate($datenotinstring);
9522 }
9523 }
9524 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9525 $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;
9526 }
9527 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9528 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9529 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9530 $value = price2num($value);
9531 } elseif (isset($this->array_options['options_'.$key])) {
9532 $value = $this->array_options['options_'.$key];
9533 }
9534 }
9535
9536 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9537 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9538 if ($action == 'create' || $mode == 'create') {
9539 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9540 }
9541 }
9542
9543 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9544 if ($action == 'create') {
9545 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9546 }
9547 }
9548
9549
9550 $labeltoshow = $langs->trans($label);
9551 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9552 if ($display_type == 'card') {
9553 $out .= '<tr '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="field_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9554 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9555 $out .= '<td></td>';
9556 }
9557 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9558 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9559 $out .= ' tdtop';
9560 }
9561 } elseif ($display_type == 'line') {
9562 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9563 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9564 }
9565 //$out .= "titlefield";
9566 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9567 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9568 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9569 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9570 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9571 $out .= ' fieldrequired';
9572 }
9573 }
9574 $out .= '">';
9575 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9576 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9577 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9578 } else {
9579 $out .= $labeltoshow;
9580 }
9581 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9582 $out .= '&nbsp;<span style="color: red">*</span>';
9583 }
9584 } else {
9585 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9586 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9587 } else {
9588 $out .= $labeltoshow;
9589 }
9590 }
9591
9592 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9593
9594 // Second column
9595 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9596 if ($display_type == 'card') {
9597 // 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
9598 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9599 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9600 $out .= '>';
9601 } elseif ($display_type == 'line') {
9602 $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].'">';
9603 }
9604
9605 switch ($mode) {
9606 case "view":
9607 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9608 break;
9609 case "create":
9610 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9611 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9612 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9613 }
9614 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9615 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9616 break;
9617 case "edit":
9618 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9619 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9620 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9621 }
9622 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9623 break;
9624 }
9625
9626 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9627 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9628 $e++;
9629 }
9630 }
9631 $out .= "\n";
9632 // Add code to manage list depending on others
9633 if (!empty($conf->use_javascript_ajax)) {
9634 $out .= $this->getJSListDependancies();
9635 }
9636
9637 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9638
9639 if (empty($nbofextrafieldsshown)) {
9640 $out = '';
9641 }
9642 }
9643 }
9644
9645 $out .= $hookmanager->resPrint;
9646
9647 return $out;
9648 }
9649
9654 public function getJSListDependancies($type = '_extra')
9655 {
9656 $out = '
9657 <script nonce="'.getNonce().'">
9658 jQuery(document).ready(function() {
9659 function showOptions'.$type.'(child_list, parent_list, orig_select)
9660 {
9661 var val = $("select[name=\""+parent_list+"\"]").val();
9662 var parentVal = parent_list + ":" + val;
9663 if(typeof val == "string"){
9664 if(val != "") {
9665 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9666 $("select[name=\""+child_list+"\"] option[parent]").remove();
9667 $("select[name=\""+child_list+"\"]").append(options);
9668 } else {
9669 var options = orig_select.find("option[parent]").clone();
9670 $("select[name=\""+child_list+"\"] option[parent]").remove();
9671 $("select[name=\""+child_list+"\"]").append(options);
9672 }
9673 } else if(val > 0) {
9674 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9675 $("select[name=\""+child_list+"\"] option[parent]").remove();
9676 $("select[name=\""+child_list+"\"]").append(options);
9677 } else {
9678 var options = orig_select.find("option[parent]").clone();
9679 $("select[name=\""+child_list+"\"] option[parent]").remove();
9680 $("select[name=\""+child_list+"\"]").append(options);
9681 }
9682 }
9683 function setListDependencies'.$type.'() {
9684 jQuery("select option[parent]").parent().each(function() {
9685 var orig_select = {};
9686 var child_list = $(this).attr("name");
9687 orig_select[child_list] = $(this).clone();
9688 var parent = $(this).find("option[parent]:first").attr("parent");
9689 var infos = parent.split(":");
9690 var parent_list = infos[0];
9691
9692 //Hide daughters lists
9693 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9694 $("#"+child_list).hide();
9695 //Show mother lists
9696 } else if ($("#"+parent_list).val() != 0){
9697 $("#"+parent_list).show();
9698 }
9699 //Show the child list if the parent list value is selected
9700 $("select[name=\""+parent_list+"\"]").click(function() {
9701 if ($(this).val() != 0){
9702 $("#"+child_list).show()
9703 }
9704 });
9705
9706 //When we change parent list
9707 $("select[name=\""+parent_list+"\"]").change(function() {
9708 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9709 //Select the value 0 on child list after a change on the parent list
9710 $("#"+child_list).val(0).trigger("change");
9711 //Hide child lists if the parent value is set to 0
9712 if ($(this).val() == 0){
9713 $("#"+child_list).hide();
9714 }
9715 });
9716 });
9717 }
9718
9719 setListDependencies'.$type.'();
9720 });
9721 </script>'."\n";
9722 return $out;
9723 }
9724
9730 public function getRights()
9731 {
9732 global $user;
9733
9734 $module = empty($this->module) ? '' : $this->module;
9735 $element = $this->element;
9736
9737 if ($element == 'facturerec') {
9738 $element = 'facture';
9739 } elseif ($element == 'invoice_supplier_rec') {
9740 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9741 } elseif ($module && $user->hasRight($module, $element)) {
9742 // for modules built with ModuleBuilder
9743 return $user->hasRight($module, $element);
9744 }
9745
9746 return isset($user->rights->$element) ? $user->rights->$element : null;
9747 }
9748
9761 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9762 {
9763 global $hookmanager;
9764
9765 $parameters = array(
9766 'origin_id' => $origin_id,
9767 'dest_id' => $dest_id,
9768 'tables' => $tables,
9769 );
9770 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
9771 if ($reshook) {
9772 return true; // replacement code
9773 } elseif ($reshook < 0) {
9774 return $ignoreerrors === 1; // failure
9775 } // reshook = 0 => execute normal code
9776
9777 foreach ($tables as $table) {
9778 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
9779
9780 if (!$dbs->query($sql)) {
9781 if ($ignoreerrors) {
9782 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.
9783 }
9784 //$this->errors = $db->lasterror();
9785 return false;
9786 }
9787 }
9788
9789 return true;
9790 }
9791
9804 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9805 {
9806 foreach ($tables as $table) {
9807 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
9808
9809 if (!$dbs->query($sql)) {
9810 if ($ignoreerrors) {
9811 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.
9812 }
9813 //$this->errors = $db->lasterror();
9814 return false;
9815 }
9816 }
9817
9818 return true;
9819 }
9820
9833 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
9834 {
9835 global $conf;
9836
9837 $buyPrice = 0;
9838
9839 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
9840 // When ForceBuyingPriceIfNull is set
9841 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
9842 } else {
9843 // Get cost price for margin calculation
9844 if (!empty($fk_product) && $fk_product > 0) {
9845 $result = 0;
9846 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
9847 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9848 $product = new Product($this->db);
9849 $result = $product->fetch($fk_product);
9850 if ($result <= 0) {
9851 $this->errors[] = 'ErrorProductIdDoesNotExists';
9852 return -1;
9853 }
9854 if ($product->cost_price > 0) {
9855 $buyPrice = $product->cost_price;
9856 } elseif ($product->pmp > 0) {
9857 $buyPrice = $product->pmp;
9858 }
9859 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
9860 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9861 $product = new Product($this->db);
9862 $result = $product->fetch($fk_product);
9863 if ($result <= 0) {
9864 $this->errors[] = 'ErrorProductIdDoesNotExists';
9865 return -1;
9866 }
9867 if ($product->pmp > 0) {
9868 $buyPrice = $product->pmp;
9869 }
9870 }
9871
9872 if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
9873 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
9874 $productFournisseur = new ProductFournisseur($this->db);
9875 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
9876 $buyPrice = $productFournisseur->fourn_unitprice;
9877 } elseif ($result < 0) {
9878 $this->errors[] = $productFournisseur->error;
9879 return -2;
9880 }
9881 }
9882 }
9883 }
9884
9885 return (float) $buyPrice;
9886 }
9887
9895 public function getDataToShowPhoto($modulepart, $imagesize)
9896 {
9897 // See getDataToShowPhoto() implemented by Product for example.
9898 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
9899 }
9900
9901
9902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
9922 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')
9923 {
9924 // phpcs:enable
9925 global $user, $langs;
9926
9927 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
9928 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
9929
9930 $sortfield = 'position_name';
9931 $sortorder = 'asc';
9932
9933 $dir = $sdir.'/';
9934 $pdir = '/';
9935
9936 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9937 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9938
9939 // For backward compatibility
9940 if ($modulepart == 'product') {
9941 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
9942 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9943 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9944 }
9945 }
9946 if ($modulepart == 'category') {
9947 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9948 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9949 }
9950
9951 // Defined relative dir to DOL_DATA_ROOT
9952 $relativedir = '';
9953 if ($dir) {
9954 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
9955 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
9956 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
9957 }
9958
9959 $dirthumb = $dir.'thumbs/';
9960 $pdirthumb = $pdir.'thumbs/';
9961
9962 $return = '<!-- Photo -->'."\n";
9963 $nbphoto = 0;
9964
9965 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
9966
9967 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
9968 {
9969 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
9970 $filearray=array_merge($filearray, $filearrayold);
9971 }*/
9972
9973 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
9974 '@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';
9975
9976 if (count($filearray)) {
9977 if ($sortfield && $sortorder) {
9978 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
9979 }
9980
9981 foreach ($filearray as $key => $val) {
9982 $photo = '';
9983 $file = $val['name'];
9984
9985 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
9986 if (image_format_supported($file) >= 0) {
9987 $nbphoto++;
9988 $photo = $file;
9989 $viewfilename = $file;
9990
9991 if ($size == 1 || $size == 'small') { // Format vignette
9992 // Find name of thumb file
9993 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
9994 if (!dol_is_file($dirthumb.$photo_vignette)) {
9995 // The thumb does not exists, so we will use the original file
9996 $dirthumb = $dir;
9997 $pdirthumb = $pdir;
9998 $photo_vignette = basename($file);
9999 }
10000
10001 // Get filesize of original file
10002 $imgarray = dol_getImageSize($dir.$photo);
10003
10004 if ($nbbyrow > 0) {
10005 if ($nbphoto == 1) {
10006 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
10007 }
10008
10009 if ($nbphoto % $nbbyrow == 1) {
10010 $return .= '<tr class="center valignmiddle" style="border: 1px">';
10011 }
10012 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
10013 } elseif ($nbbyrow < 0) {
10014 $return .= '<div class="inline-block">'."\n";
10015 }
10016
10017 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
10018 if (empty($nolink)) {
10019 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10020 if ($urladvanced) {
10021 $return .= '<a href="'.$urladvanced.'">';
10022 } else {
10023 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10024 }
10025 }
10026
10027 // Show image (width height=$maxHeight)
10028 // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
10029 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10030 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10031 if ($overwritetitle) {
10032 if (is_numeric($overwritetitle)) {
10033 $alt = '';
10034 } else {
10035 $alt = $overwritetitle;
10036 }
10037 }
10038 if (empty($cache) && !empty($val['label'])) {
10039 // label is md5 of file
10040 // use it in url to say we want to cache this version of the file
10041 $cache = $val['label'];
10042 }
10043 if ($usesharelink) {
10044 if (array_key_exists('share', $val) && $val['share']) {
10045 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10046 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10047 $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).'">';
10048 } else {
10049 $return .= '<!-- Show original file -->';
10050 $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).'">';
10051 }
10052 } else {
10053 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10054 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10055 }
10056 } else {
10057 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10058 $return .= '<!-- Show thumb -->';
10059 $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).'">';
10060 } else {
10061 $return .= '<!-- Show original file -->';
10062 $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).'">';
10063 }
10064 }
10065
10066 if (empty($nolink)) {
10067 $return .= '</a>';
10068 }
10069
10070 if ($showfilename) {
10071 $return .= '<br>'.$viewfilename;
10072 }
10073 if ($showaction) {
10074 $return .= '<br>';
10075 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10076 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10077 $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>';
10078 }
10079 // Special case for product
10080 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10081 // Link to resize
10082 $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; ';
10083
10084 // Link to delete
10085 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10086 $return .= img_delete().'</a>';
10087 }
10088 }
10089 $return .= "\n";
10090
10091 if ($nbbyrow > 0) {
10092 $return .= '</td>';
10093 if (($nbphoto % $nbbyrow) == 0) {
10094 $return .= '</tr>';
10095 }
10096 } elseif ($nbbyrow < 0) {
10097 $return .= '</div>'."\n";
10098 }
10099 }
10100
10101 if (empty($size)) { // Format origine
10102 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10103
10104 if ($showfilename) {
10105 $return .= '<br>'.$viewfilename;
10106 }
10107 if ($showaction) {
10108 // Special case for product
10109 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10110 // Link to resize
10111 $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; ';
10112
10113 // Link to delete
10114 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10115 $return .= img_delete().'</a>';
10116 }
10117 }
10118 }
10119
10120 // On continue ou on arrete de boucler ?
10121 if ($nbmax && $nbphoto >= $nbmax) {
10122 break;
10123 }
10124 }
10125 }
10126
10127 if ($size == 1 || $size == 'small') {
10128 if ($nbbyrow > 0) {
10129 // Ferme tableau
10130 while ($nbphoto % $nbbyrow) {
10131 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10132 $nbphoto++;
10133 }
10134
10135 if ($nbphoto) {
10136 $return .= '</table>';
10137 }
10138 }
10139 }
10140 }
10141
10142 $this->nbphoto = $nbphoto;
10143
10144 return $return;
10145 }
10146
10147
10154 protected function isArray($info)
10155 {
10156 if (is_array($info)) {
10157 if (isset($info['type']) && $info['type'] == 'array') {
10158 return true;
10159 } else {
10160 return false;
10161 }
10162 }
10163 return false;
10164 }
10165
10172 public function isDate($info)
10173 {
10174 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10175 return true;
10176 }
10177 return false;
10178 }
10179
10186 public function isDuration($info)
10187 {
10188 if (is_array($info)) {
10189 if (isset($info['type']) && ($info['type'] == 'duration')) {
10190 return true;
10191 } else {
10192 return false;
10193 }
10194 } else {
10195 return false;
10196 }
10197 }
10198
10205 public function isInt($info)
10206 {
10207 if (is_array($info)) {
10208 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10209 return true;
10210 } else {
10211 return false;
10212 }
10213 } else {
10214 return false;
10215 }
10216 }
10217
10224 public function isFloat($info)
10225 {
10226 if (is_array($info)) {
10227 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10228 return true;
10229 } else {
10230 return false;
10231 }
10232 }
10233 return false;
10234 }
10235
10242 public function isText($info)
10243 {
10244 if (is_array($info)) {
10245 if (isset($info['type']) && $info['type'] == 'text') {
10246 return true;
10247 } else {
10248 return false;
10249 }
10250 }
10251 return false;
10252 }
10253
10260 protected function canBeNull($info)
10261 {
10262 if (is_array($info)) {
10263 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10264 return true;
10265 } else {
10266 return false;
10267 }
10268 }
10269 return true;
10270 }
10271
10278 protected function isForcedToNullIfZero($info)
10279 {
10280 if (is_array($info)) {
10281 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10282 return true;
10283 } else {
10284 return false;
10285 }
10286 }
10287 return false;
10288 }
10289
10296 protected function isIndex($info)
10297 {
10298 if (is_array($info)) {
10299 if (array_key_exists('index', $info) && $info['index'] == true) {
10300 return true;
10301 } else {
10302 return false;
10303 }
10304 }
10305 return false;
10306 }
10307
10308
10317 protected function setSaveQuery()
10318 {
10319 global $conf;
10320
10321 $queryarray = array();
10322 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10323 // Depending on field type ('datetime', ...)
10324 if ($this->isDate($info)) {
10325 if (empty($this->{$field})) {
10326 $queryarray[$field] = null;
10327 } else {
10328 $queryarray[$field] = $this->db->idate($this->{$field});
10329 }
10330 } elseif ($this->isDuration($info)) {
10331 // $this->{$field} may be null, '', 0, '0', 123, '123'
10332 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10333 if (!isset($this->{$field})) {
10334 if (!empty($info['default'])) {
10335 $queryarray[$field] = $info['default'];
10336 } else {
10337 $queryarray[$field] = 0;
10338 }
10339 } else {
10340 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10341 }
10342 } else {
10343 $queryarray[$field] = null;
10344 }
10345 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10346 if ($field == 'entity' && is_null($this->{$field})) {
10347 $queryarray[$field] = ((int) $conf->entity);
10348 } else {
10349 // $this->{$field} may be null, '', 0, '0', 123, '123'
10350 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10351 if (!isset($this->{$field})) {
10352 $queryarray[$field] = 0;
10353 } elseif ($this->isInt($info)) {
10354 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10355 } elseif ($this->isFloat($info)) {
10356 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10357 }
10358 } else {
10359 $queryarray[$field] = null;
10360 }
10361 }
10362 } else {
10363 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10364 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10365 $queryarray[$field] = $this->{$field};
10366 }
10367
10368 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10369 unset($queryarray[$field]);
10370 }
10371 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10372 $queryarray[$field] = null; // May force 0 to null
10373 }
10374 }
10375
10376 return $queryarray;
10377 }
10378
10385 public function setVarsFromFetchObj(&$obj)
10386 {
10387 global $db;
10388
10389 foreach ($this->fields as $field => $info) {
10390 if ($this->isDate($info)) {
10391 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') {
10392 $this->$field = '';
10393 } else {
10394 $this->$field = $db->jdate($obj->$field);
10395 }
10396 } elseif ($this->isInt($info)) {
10397 if ($field == 'rowid') {
10398 $this->id = (int) $obj->$field;
10399 } else {
10400 if ($this->isForcedToNullIfZero($info)) {
10401 if (empty($obj->$field)) {
10402 $this->$field = null;
10403 } else {
10404 $this->$field = (int) $obj->$field;
10405 }
10406 } else {
10407 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10408 $this->$field = (int) $obj->$field;
10409 } else {
10410 $this->$field = null;
10411 }
10412 }
10413 }
10414 } elseif ($this->isFloat($info)) {
10415 if ($this->isForcedToNullIfZero($info)) {
10416 if (empty($obj->$field)) {
10417 $this->$field = null;
10418 } else {
10419 $this->$field = (float) $obj->$field;
10420 }
10421 } else {
10422 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10423 $this->$field = (float) $obj->$field;
10424 } else {
10425 $this->$field = null;
10426 }
10427 }
10428 } else {
10429 $this->$field = isset($obj->$field) ? $obj->$field : null;
10430 }
10431 }
10432
10433 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10434 if (!isset($this->fields['ref']) && isset($this->id)) {
10435 $this->ref = (string) $this->id;
10436 }
10437 }
10438
10443 public function emtpyObjectVars()
10444 {
10445 foreach ($this->fields as $field => $arr) {
10446 $this->$field = null;
10447 }
10448 }
10449
10457 public function getFieldList($alias = '', $excludefields = array())
10458 {
10459 $keys = array_keys($this->fields);
10460 if (!empty($alias)) {
10461 $keys_with_alias = array();
10462 foreach ($keys as $fieldname) {
10463 if (!empty($excludefields)) {
10464 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10465 continue;
10466 }
10467 }
10468 $keys_with_alias[] = $alias . '.' . $fieldname;
10469 }
10470 return implode(', ', $keys_with_alias);
10471 } else {
10472 return implode(', ', $keys);
10473 }
10474 }
10475
10483 protected function quote($value, $fieldsentry)
10484 {
10485 if (is_null($value)) {
10486 return 'NULL';
10487 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10488 return price2num((string) $value);
10489 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10490 return (int) $value;
10491 } elseif ($fieldsentry['type'] == 'boolean') {
10492 if ($value) {
10493 return 'true';
10494 } else {
10495 return 'false';
10496 }
10497 } else {
10498 return "'".$this->db->escape($value)."'";
10499 }
10500 }
10501
10502
10510 public function createCommon(User $user, $notrigger = 0)
10511 {
10512 global $langs;
10513
10514 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10515
10516 $error = 0;
10517
10518 $now = dol_now();
10519
10520 $fieldvalues = $this->setSaveQuery();
10521
10522 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10523
10524 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10525 $fieldvalues['date_creation'] = $this->db->idate($now);
10526 $this->date_creation = $this->db->idate($now);
10527 }
10528 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10529 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10530 $fieldvalues['fk_user_creat'] = $user->id;
10531 $this->fk_user_creat = $user->id;
10532 }
10533 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10534 $fieldvalues['user_creation_id'] = $user->id;
10535 $this->user_creation_id = $user->id;
10536 }
10537 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10538 // @phan-suppress-next-line PhanUndeclaredProperty
10539 $tmparray = dol_hash($this->pass, '0', 0, 1);
10540 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10541 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10542 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10543 }
10544 }
10545 if (array_key_exists('ref', $fieldvalues)) {
10546 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10547 }
10548
10549 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10550
10551 $keys = array();
10552 $values = array(); // Array to store string forged for SQL syntax
10553 foreach ($fieldvalues as $k => $v) {
10554 $keys[$k] = $k;
10555 $value = $this->fields[$k];
10556 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10557 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10558 }
10559
10560 // Clean and check mandatory
10561 foreach ($keys as $key) {
10562 if (!isset($this->fields[$key])) {
10563 continue;
10564 }
10565 $key_fields = $this->fields[$key];
10566
10567 // If field is an implicit foreign key field (so type = 'integer:...')
10568 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10569 $values[$key] = '';
10570 }
10571 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10572 $values[$key] = '';
10573 }
10574
10575 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10576 $error++;
10577 $langs->load("errors");
10578 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10579 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10580 }
10581
10582 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10583 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && !is_null($key_fields['default'])) {
10584 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10585 }
10586
10587 // If field is an implicit foreign key field (so type = 'integer:...')
10588 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10589 if (isset($key_fields['default'])) {
10590 $values[$key] = ((int) $key_fields['default']);
10591 } else {
10592 $values[$key] = 'null';
10593 }
10594 }
10595 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10596 $values[$key] = 'null';
10597 }
10598 }
10599
10600 if ($error) {
10601 return -1;
10602 }
10603
10604 $this->db->begin();
10605
10606 if (!$error) {
10607 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10608 $sql .= " (".implode(", ", $keys).')';
10609 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10610
10611 $res = $this->db->query($sql);
10612 if (!$res) {
10613 $error++;
10614 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10615 $this->errors[] = "ErrorRefAlreadyExists";
10616 } else {
10617 $this->errors[] = $this->db->lasterror();
10618 }
10619 }
10620 }
10621
10622 if (!$error) {
10623 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10624 }
10625
10626 // If we have a field ref with a default value of (PROV)
10627 if (!$error) {
10628 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10629 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)') {
10630 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10631 $resqlupdate = $this->db->query($sql);
10632
10633 if ($resqlupdate === false) {
10634 $error++;
10635 $this->errors[] = $this->db->lasterror();
10636 } else {
10637 $this->ref = '(PROV'.$this->id.')';
10638 }
10639 }
10640 }
10641
10642 // Create extrafields
10643 if (!$error) {
10644 $result = $this->insertExtraFields();
10645 if ($result < 0) {
10646 $error++;
10647 }
10648 }
10649
10650 // Create lines
10651 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10652 foreach ($this->lines as $line) {
10653 $keyforparent = $this->fk_element;
10654 $line->$keyforparent = $this->id;
10655
10656 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10657 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10658 if (!is_object($line)) {
10659 $line = (object) $line;
10660 }
10661
10662 $result = 0;
10663 if (method_exists($line, 'insert')) {
10664 $result = $line->insert($user, 1);
10665 } elseif (method_exists($line, 'create')) {
10666 $result = $line->create($user, 1);
10667 }
10668 if ($result < 0) {
10669 $this->error = $line->error;
10670 $this->db->rollback();
10671 return -1;
10672 }
10673 }
10674 }
10675
10676 // Triggers
10677 if (!$error && !$notrigger) {
10678 // Call triggers
10679 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10680 if ($result < 0) {
10681 $error++;
10682 }
10683 // End call triggers
10684 }
10685
10686 // Commit or rollback
10687 if ($error) {
10688 $this->db->rollback();
10689 return -1;
10690 } else {
10691 $this->db->commit();
10692 return $this->id;
10693 }
10694 }
10695
10696
10706 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
10707 {
10708 if (empty($id) && empty($ref) && empty($morewhere)) {
10709 return -1;
10710 }
10711
10712 $fieldlist = $this->getFieldList('t');
10713 if (empty($fieldlist)) {
10714 return 0;
10715 }
10716
10717 $sql = "SELECT ".$fieldlist;
10718 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
10719
10720 if (!empty($id)) {
10721 $sql .= ' WHERE t.rowid = '.((int) $id);
10722 } elseif (!empty($ref)) {
10723 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
10724 } else {
10725 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
10726 }
10727 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
10728 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
10729 }
10730 if ($morewhere) {
10731 $sql .= $morewhere;
10732 }
10733 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
10734
10735 $res = $this->db->query($sql);
10736 if ($res) {
10737 $obj = $this->db->fetch_object($res);
10738 if ($obj) {
10739 $this->setVarsFromFetchObj($obj);
10740
10741 // Retrieve all extrafield
10742 // fetch optionals attributes and labels
10743 if (empty($noextrafields)) {
10744 $result = $this->fetch_optionals();
10745 if ($result < 0) {
10746 $this->error = $this->db->lasterror();
10747 $this->errors[] = $this->error;
10748 return -4;
10749 }
10750 }
10751
10752 return $this->id;
10753 } else {
10754 return 0;
10755 }
10756 } else {
10757 $this->error = $this->db->lasterror();
10758 $this->errors[] = $this->error;
10759 return -1;
10760 }
10761 }
10762
10770 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
10771 {
10772 $objectlineclassname = get_class($this).'Line';
10773 if (!class_exists($objectlineclassname)) {
10774 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
10775 return -1;
10776 }
10777
10778 $objectline = new $objectlineclassname($this->db);
10779 '@phan-var-force CommonObjectLine $objectline';
10780
10781 $sql = "SELECT ".$objectline->getFieldList('l');
10782 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
10783 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
10784 if ($morewhere) {
10785 $sql .= $morewhere;
10786 }
10787 if (isset($objectline->fields['position'])) {
10788 $sql .= $this->db->order('position', 'ASC');
10789 }
10790
10791 $resql = $this->db->query($sql);
10792 if ($resql) {
10793 $num_rows = $this->db->num_rows($resql);
10794 $i = 0;
10795 $this->lines = array();
10796 while ($i < $num_rows) {
10797 $obj = $this->db->fetch_object($resql);
10798 if ($obj) {
10799 $newline = new $objectlineclassname($this->db);
10800 '@phan-var-force CommonObjectLine $newline';
10801 $newline->setVarsFromFetchObj($obj);
10802
10803 // Load also extrafields for the line
10804 if (empty($noextrafields)) {
10805 $newline->fetch_optionals();
10806 }
10807
10808 $this->lines[] = $newline;
10809 }
10810 $i++;
10811 }
10812
10813 return 1;
10814 } else {
10815 $this->error = $this->db->lasterror();
10816 $this->errors[] = $this->error;
10817 return -1;
10818 }
10819 }
10820
10828 public function updateCommon(User $user, $notrigger = 0)
10829 {
10830 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
10831
10832 $error = 0;
10833
10834 $now = dol_now();
10835
10836 // $this->oldcopy should have been set by the caller of update
10837 //if (empty($this->oldcopy)) {
10838 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
10839 // $this->oldcopy = dol_clone($this, 2);
10840 //}
10841
10842 $fieldvalues = $this->setSaveQuery();
10843
10844 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10845
10846 if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) {
10847 $fieldvalues['date_modification'] = $this->db->idate($now);
10848 }
10849 if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) {
10850 $fieldvalues['fk_user_modif'] = $user->id;
10851 }
10852 if (array_key_exists('user_modification_id', $fieldvalues) && !($fieldvalues['user_modification_id'] > 0)) {
10853 $fieldvalues['user_modification_id'] = $user->id;
10854 }
10855 // @phan-suppress-next-line PhanUndeclaredProperty
10856 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
10857 // @phan-suppress-next-line PhanUndeclaredProperty
10858 $tmparray = dol_hash($this->pass, '0', 0, 1);
10859 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10860 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10861 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10862 }
10863 }
10864 if (array_key_exists('ref', $fieldvalues)) {
10865 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10866 }
10867
10868 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
10869
10870 // Add quotes and escape on fields with type string
10871 $keys = array();
10872 $values = array();
10873 $tmp = array();
10874 foreach ($fieldvalues as $k => $v) {
10875 $keys[$k] = $k;
10876 $value = $this->fields[$k];
10877 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10878 $values[$k] = $this->quote($v, $value);
10879 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
10880 // Clean values for text with selectbox
10881 $v = preg_replace('/\s/', ',', $v);
10882 $v = preg_replace('/,+/', ',', $v);
10883 }
10884 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10885 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
10886 }
10887
10888 // Clean and check mandatory fields
10889 foreach ($keys as $key) {
10890 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
10891 $values[$key] = ''; // This is an implicit foreign key field
10892 }
10893 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
10894 $values[$key] = ''; // This is an explicit foreign key field
10895 }
10896
10897 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
10898 /*
10899 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
10900 {
10901 $error++;
10902 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
10903 }*/
10904 }
10905
10906 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
10907
10908 $this->db->begin();
10909
10910 if (!$error) {
10911 $res = $this->db->query($sql);
10912 if (!$res) {
10913 $error++;
10914 $this->errors[] = $this->db->lasterror();
10915 }
10916 }
10917
10918 // Update extrafield
10919 if (!$error) {
10920 $result = $this->insertExtraFields(); // This delete and reinsert extrafields
10921 if ($result < 0) {
10922 $error++;
10923 }
10924 }
10925
10926 // Triggers
10927 if (!$error && !$notrigger) {
10928 // Call triggers
10929 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
10930 if ($result < 0) {
10931 $error++;
10932 } //Do also here what you must do to rollback action if trigger fail
10933 // End call triggers
10934 }
10935
10936 // Commit or rollback
10937 if ($error) {
10938 $this->db->rollback();
10939 return -1;
10940 } else {
10941 $this->db->commit();
10942 return $this->id;
10943 }
10944 }
10945
10954 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
10955 {
10956 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
10957
10958 $error = 0;
10959
10960 $this->db->begin();
10961
10962 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
10963 foreach ($this->childtables as $table) {
10964 $sql = "DELETE FROM ".$this->db->prefix().$table." WHERE ".$this->fk_element." = ".((int) $this->id);
10965 $resql = $this->db->query($sql);
10966 if (!$resql) {
10967 $this->error = $this->db->lasterror();
10968 $this->errors[] = $this->error;
10969 $this->db->rollback();
10970 return -1;
10971 }
10972 }
10973 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
10974 $objectisused = $this->isObjectUsed($this->id);
10975 if (!empty($objectisused)) {
10976 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
10977 $this->error = 'ErrorRecordHasChildren';
10978 $this->errors[] = $this->error;
10979 $this->db->rollback();
10980 return 0;
10981 }
10982 }
10983
10984 // Delete cascade first
10985 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
10986 foreach ($this->childtablesoncascade as $tabletodelete) {
10987 $deleteFromObject = explode(':', $tabletodelete, 4);
10988 if (count($deleteFromObject) >= 2) {
10989 $className = str_replace('@', '', $deleteFromObject[0]);
10990 $filePath = $deleteFromObject[1];
10991 $columnName = $deleteFromObject[2];
10992 $filter = '';
10993 if (!empty($deleteFromObject[3])) {
10994 $filter = $deleteFromObject[3];
10995 }
10996
10997 if (dol_include_once($filePath)) {
10998 $childObject = new $className($this->db);
10999 if (method_exists($childObject, 'deleteByParentField')) {
11000 '@phan-var-force CommonObject $childObject';
11001 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
11002 if ($result < 0) {
11003 $error++;
11004 $this->errors[] = $childObject->error;
11005 break;
11006 }
11007 } else {
11008 $error++;
11009 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
11010 break;
11011 }
11012 } else {
11013 $error++;
11014 $this->errors[] = 'Cannot include child class file '.$filePath;
11015 break;
11016 }
11017 } else {
11018 // Delete record in child table
11019 $sql = "DELETE FROM ".$this->db->prefix().$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
11020
11021 $resql = $this->db->query($sql);
11022 if (!$resql) {
11023 $error++;
11024 $this->error = $this->db->lasterror();
11025 $this->errors[] = $this->error;
11026 break;
11027 }
11028 }
11029 }
11030 }
11031
11032 if (!$error) {
11033 if (!$notrigger) {
11034 // Call triggers
11035 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11036 if ($result < 0) {
11037 $error++;
11038 } // Do also here what you must do to rollback action if trigger fail
11039 // End call triggers
11040 }
11041 }
11042
11043 // Delete llx_ecm_files
11044 if (!$error) {
11045 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11046 if (!$res) {
11047 $error++;
11048 }
11049 }
11050
11051 // Delete linked object
11052 $res = $this->deleteObjectLinked();
11053 if ($res < 0) {
11054 $error++;
11055 }
11056
11057 if (!$error && !empty($this->isextrafieldmanaged)) {
11058 $result = $this->deleteExtraFields();
11059 if ($result < 0) {
11060 $error++;
11061 }
11062 }
11063
11064 if (!$error) {
11065 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11066
11067 $resql = $this->db->query($sql);
11068 if (!$resql) {
11069 $error++;
11070 $this->errors[] = $this->db->lasterror();
11071 }
11072 }
11073
11074 // Commit or rollback
11075 if ($error) {
11076 $this->db->rollback();
11077 return -1;
11078 } else {
11079 $this->db->commit();
11080 return 1;
11081 }
11082 }
11083
11095 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11096 {
11097 global $user;
11098
11099 $error = 0;
11100 $deleted = 0;
11101
11102 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11103
11104 if (!empty($parentId) && !empty($parentField)) {
11105 if (empty($this->table_element)) {
11106 $this->error = 'Property table_element for object is not defined';
11107 $this->errors[] = $this->error;
11108 $error++;
11109 return -1;
11110 }
11111 if (!method_exists($this, 'fetch')) {
11112 $this->error = 'Method fetch for object is not defined';
11113 $this->errors[] = $this->error;
11114 $error++;
11115 return -1;
11116 }
11117 if (!method_exists($this, 'delete')) {
11118 $this->error = 'Method delete for object is not defined';
11119 $this->errors[] = $this->error;
11120 $error++;
11121 return -1;
11122 }
11123
11124 $this->db->begin();
11125
11126 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11127 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11128
11129 // Manage filter
11130 $errormessage = '';
11131 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11132 if ($errormessage) {
11133 $this->errors[] = $errormessage;
11134 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11135 return -1;
11136 }
11137
11138 $resql = $this->db->query($sql);
11139 if (!$resql) {
11140 $this->errors[] = $this->db->lasterror();
11141 $error++;
11142 } else {
11143 while ($obj = $this->db->fetch_object($resql)) {
11144 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11145 if ($result < 0) {
11146 $error++;
11147 $this->errors[] = $this->error;
11148 } else {
11149 $result = $this->delete($user); // @phpstan-ignore-line
11150 if ($result < 0) {
11151 $error++;
11152 $this->errors[] = $this->error;
11153 } else {
11154 $deleted++;
11155 }
11156 }
11157 }
11158 }
11159
11160 if (empty($error)) {
11161 $this->db->commit();
11162 return $deleted;
11163 } else {
11164 $this->error = implode(', ', $this->errors);
11165 $this->db->rollback();
11166 return $error * -1;
11167 }
11168 }
11169
11170 return $deleted;
11171 }
11172
11181 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11182 {
11183 $error = 0;
11184
11185 $tmpforobjectclass = get_class($this);
11186 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11187
11188 if ($this->id > 0 && !empty($this->fk_element)) {
11189 // The line must belong to this object
11190 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
11191 $sql .= " WHERE rowid = ".((int) $idline)." AND ".$this->db->sanitize($this->fk_element)." = ".((int) $this->id);
11192 $resql = $this->db->query($sql);
11193 if (!$resql || !$this->db->num_rows($resql)) {
11194 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
11195 return -1;
11196 }
11197 }
11198
11199 $this->db->begin();
11200
11201 // Call trigger
11202 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11203 if ($result < 0) {
11204 $error++;
11205 }
11206 // End call triggers
11207
11208 if (empty($error)) {
11209 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11210 $sql .= " WHERE rowid = ".((int) $idline);
11211
11212 $resql = $this->db->query($sql);
11213 if (!$resql) {
11214 $this->error = "Error ".$this->db->lasterror();
11215 $error++;
11216 }
11217 }
11218
11219 if (empty($error)) {
11220 // Remove extrafields
11221 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11222 '@phan-var-force CommonObjectLine $tmpobjectline';
11223 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11224 $tmpobjectline->id = $idline;
11225 $result = $tmpobjectline->deleteExtraFields();
11226 if ($result < 0) {
11227 $error++;
11228 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11229 }
11230 }
11231 }
11232
11233 if (empty($error)) {
11234 $this->db->commit();
11235 return 1;
11236 } else {
11237 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11238 $this->db->rollback();
11239 return -1;
11240 }
11241 }
11242
11243
11253 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11254 {
11255 $error = 0;
11256
11257 $this->db->begin();
11258
11259 $statusfield = 'status';
11260 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11261 $statusfield = 'fk_statut';
11262 }
11263
11264 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11265 $sql .= " SET ".$statusfield." = ".((int) $status);
11266 $sql .= " WHERE rowid = ".((int) $this->id);
11267
11268 if ($this->db->query($sql)) {
11269 if (!$error) {
11270 $this->oldcopy = clone $this;
11271 }
11272
11273 if (!$error && !$notrigger) {
11274 // Call trigger
11275 $result = $this->call_trigger($triggercode, $user);
11276 if ($result < 0) {
11277 $error++;
11278 }
11279 }
11280
11281 if (!$error) {
11282 $this->status = $status;
11283 if (property_exists($this, 'statut')) { // For backward compatibility
11284 $this->statut = $status;
11285 }
11286 $this->db->commit();
11287 return 1;
11288 } else {
11289 $this->db->rollback();
11290 return -1;
11291 }
11292 } else {
11293 $this->error = $this->db->error();
11294 $this->db->rollback();
11295 return -1;
11296 }
11297 }
11298
11305 public function initAsSpecimenCommon()
11306 {
11307 global $user;
11308
11309 $this->id = 0;
11310 $this->specimen = 1;
11311 $fields = array(
11312 'label' => 'This is label',
11313 'ref' => 'ABCD1234',
11314 'description' => 'This is a description',
11315 'qty' => 123.12,
11316 'note_public' => 'Public note',
11317 'note_private' => 'Private note',
11318 'date_creation' => (dol_now() - 3600 * 48),
11319 'date_modification' => (dol_now() - 3600 * 24),
11320 'fk_user_creat' => $user->id,
11321 'fk_user_modif' => $user->id,
11322 'date' => dol_now(),
11323 );
11324 foreach ($fields as $key => $value) {
11325 if (array_key_exists($key, $this->fields)) {
11326 $this->{$key} = $value; // @phpstan-ignore-line
11327 }
11328 }
11329
11330 // Force values to default values when known
11331 if (property_exists($this, 'fields')) {
11332 foreach ($this->fields as $key => $value) {
11333 // If fields are already set, do nothing
11334 if (array_key_exists($key, $fields)) {
11335 continue;
11336 }
11337
11338 if (!empty($value['default'])) {
11339 $this->$key = $value['default'];
11340 }
11341 }
11342 }
11343
11344 return 1;
11345 }
11346
11347
11348 /* Part for comments */
11349
11355 public function fetchComments()
11356 {
11357 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11358
11359 $comment = new Comment($this->db);
11360 $result = $comment->fetchAllFor($this->element, $this->id);
11361 if ($result < 0) {
11362 $this->errors = array_merge($this->errors, $comment->errors);
11363 return -1;
11364 } else {
11365 $this->comments = $comment->comments;
11366 }
11367 return count($this->comments);
11368 }
11369
11375 public function getNbComments()
11376 {
11377 return count($this->comments);
11378 }
11379
11386 public function trimParameters($parameters)
11387 {
11388 if (!is_array($parameters)) {
11389 return;
11390 }
11391 foreach ($parameters as $parameter) {
11392 if (isset($this->$parameter)) {
11393 $this->$parameter = trim($this->$parameter);
11394 }
11395 }
11396 }
11397
11398 /* Part for categories/tags */
11399
11410 public function getCategoriesCommon($type_categ)
11411 {
11412 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11413
11414 // Get current categories
11415 $c = new Categorie($this->db);
11416 $existing = $c->containing($this->id, $type_categ, 'id');
11417
11418 return $existing;
11419 }
11420
11433 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11434 {
11435 // Handle single category
11436 if (!is_array($categories)) {
11437 $categories = array($categories);
11438 }
11439
11440 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11441
11442 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11443
11444 if (empty($type_categ)) {
11445 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11446 return -1;
11447 }
11448
11449 // Get current categories
11450 $c = new Categorie($this->db);
11451 $existing = $c->containing($this->id, $type_categ, 'id');
11452 if ($remove_existing) {
11453 // Diff
11454 if (is_array($existing)) {
11455 $to_del = array_diff($existing, $categories);
11456 $to_add = array_diff($categories, $existing);
11457 } else {
11458 $to_del = array(); // Nothing to delete
11459 $to_add = $categories;
11460 }
11461 } else {
11462 $to_del = array(); // Nothing to delete
11463 $to_add = array_diff($categories, $existing);
11464 }
11465
11466 $error = 0;
11467 $ok = 0;
11468
11469 // Process
11470 foreach ($to_del as $del) {
11471 if ($c->fetch($del) > 0) {
11472 $result = $c->del_type($this, $type_categ);
11473 if ($result < 0) {
11474 $error++;
11475 $this->error = $c->error;
11476 $this->errors = $c->errors;
11477 break;
11478 } else {
11479 $ok += $result;
11480 }
11481 }
11482 }
11483 foreach ($to_add as $add) {
11484 if ($c->fetch($add) > 0) {
11485 $result = $c->add_type($this, $type_categ);
11486 if ($result < 0) {
11487 $error++;
11488 $this->error = $c->error;
11489 $this->errors = $c->errors;
11490 break;
11491 } else {
11492 $ok += $result;
11493 }
11494 }
11495 }
11496
11497 return $error ? (-1 * $error) : $ok;
11498 }
11499
11508 public function cloneCategories($fromId, $toId, $type = '')
11509 {
11510 $this->db->begin();
11511
11512 if (empty($type)) {
11513 $type = $this->table_element;
11514 }
11515
11516 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11517 $categorystatic = new Categorie($this->db);
11518
11519 $sql = "INSERT INTO ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type])." (fk_categorie, fk_product)";
11520 $sql .= " SELECT fk_categorie, $toId FROM ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11521 $sql .= " WHERE fk_product = ".((int) $fromId);
11522
11523 if (!$this->db->query($sql)) {
11524 $this->error = $this->db->lasterror();
11525 $this->db->rollback();
11526 return -1;
11527 }
11528
11529 $this->db->commit();
11530 return 1;
11531 }
11532
11539 public function deleteEcmFiles($mode = 0)
11540 {
11541 global $conf;
11542
11543 $this->db->begin();
11544
11545 // Delete in database with mode 0
11546 if ($mode == 0) {
11547 switch ($this->element) {
11548 case 'propal':
11549 $element = 'propale';
11550 break;
11551 case 'product':
11552 $element = 'produit';
11553 break;
11554 case 'order_supplier':
11555 $element = 'fournisseur/commande';
11556 break;
11557 case 'invoice_supplier':
11558 // Special cases that need to use get_exdir to get real dir of object
11559 // In future, all object should use this to define path of documents.
11560 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11561 break;
11562 case 'shipping':
11563 $element = 'expedition/sending';
11564 break;
11565 case 'task':
11566 case 'project_task':
11567 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11568
11569 $project_result = $this->fetchProject();
11570 if ($project_result >= 0) {
11571 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11572 }
11573 // no break
11574 case 'contrat':
11575 $element = 'contract';
11576 break;
11577 default:
11578 $element = $this->element;
11579 }
11580 '@phan-var-force string $element';
11581
11582 // Delete ecm_files_extrafields with mode 0 (using name)
11583 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11584 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11585 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11586 $sql .= ")";
11587
11588 if (!$this->db->query($sql)) {
11589 $this->error = $this->db->lasterror();
11590 $this->db->rollback();
11591 return false;
11592 }
11593
11594 // Delete ecm_files with mode 0 (using name)
11595 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11596 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11597 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11598
11599 if (!$this->db->query($sql)) {
11600 $this->error = $this->db->lasterror();
11601 $this->db->rollback();
11602 return false;
11603 }
11604 }
11605
11606 // Delete in database with mode 1
11607 if ($mode == 1) {
11608 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11609 $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).")";
11610 $resql = $this->db->query($sql);
11611 if (!$resql) {
11612 $this->error = $this->db->lasterror();
11613 $this->db->rollback();
11614 return false;
11615 }
11616
11617 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11618 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11619 $resql = $this->db->query($sql);
11620 if (!$resql) {
11621 $this->error = $this->db->lasterror();
11622 $this->db->rollback();
11623 return false;
11624 }
11625 }
11626
11627 $this->db->commit();
11628 return true;
11629 }
11630}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:48
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
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
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.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
fetchObjectFrom($table, $field, $key, $element=null)
Load object from specific field.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
static deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
Function used to remove all items linked to an object id in association table.
setFieldError($fieldKey, $msg='')
set validation error message a field
validateField($fields, $fieldKey, $fieldValue)
Return validation test result for a field.
getDataToShowPhoto($modulepart, $imagesize)
Function used to get the logos or photos of an object.
setMulticurrencyCode($code)
Change the multicurrency code.
add_element_resource($resource_id, $resource_type, $busy=0, $mandatory=0)
Add resources to the current object : add entry into llx_element_resources Need $this->element & $thi...
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.
setErrorsFromObject($object)
setErrorsFromObject
setShippingMethod($shipping_method_id, $notrigger=0, $userused=null)
Change the shipping method.
update_note_public($note)
Update public note (kept for backward compatibility)
getSpecialCode($lineid)
Get special code of a line.
clearObjectLinkedCache()
Clear the cache saying that all linked object were already loaded.
update_ref_ext($ref_ext)
Update external ref of element.
fetchOneLike($ref)
Looks for an object with ref matching the wildcard provided It does only work when $this->table_ref_f...
hasProductsOrServices($predefined=-1)
Function to say how many lines object contains.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
updateRangOfLine($rowid, $rang)
Update position of line (rang)
getDefaultCreateValueFor($fieldname, $alternatevalue=null, $type='alphanohtml')
Return the default value to use for a field when showing the create form of object.
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.
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='fk_statut')
Set status of an object.
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...
delete_resource($rowid, $element, $notrigger=0)
Delete a link to resource line.
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.
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.
call_trigger($triggerName, $user)
Call trigger based on this instance.
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.
Class to manage triggers.
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:244
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_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:63
dol_delete_preview($object)
Delete all preview files linked to object instance.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
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_print_phone($phone, $countrycode='', $cid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
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 '.
getCallerInfoString()
Get caller info as a string that can be appended to a log message.
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.
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
dol_now($mode='auto')
Return date for now.
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_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
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, ....
dol_print_email($email, $cid=0, $socid=0, $addlink=0, $max=64, $showinvalid=1, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
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.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($object, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
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...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
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_sanitizePathName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a path name.
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.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
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:162
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:158
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.