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 $this->db->begin();
1431
1432 if (!$error && empty($notrigger)) {
1433 // Call trigger
1434 $this->context['contact_id'] = ((int) $rowid);
1435 $result = $this->call_trigger(strtoupper($this->element).'_DELETE_CONTACT', $user);
1436 if ($result < 0) {
1437 $error++;
1438 }
1439 // End call triggers
1440 }
1441
1442 if (!$error) {
1443 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1444
1445 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1446 $sql .= " WHERE rowid = ".((int) $rowid);
1447
1448 $result = $this->db->query($sql);
1449 if (!$result) {
1450 $error++;
1451 $this->errors[] = $this->db->lasterror();
1452 }
1453 }
1454
1455 if (!$error) {
1456 $this->db->commit();
1457 return 1;
1458 } else {
1459 $this->error = $this->db->lasterror();
1460 $this->db->rollback();
1461 return -1;
1462 }
1463 }
1464
1465 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1473 public function delete_linked_contact($source = '', $code = '')
1474 {
1475 // phpcs:enable
1476 $listId = '';
1477 $temp = array();
1478 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1479
1480 if (!empty($typeContact)) {
1481 foreach ($typeContact as $key => $value) {
1482 array_push($temp, $key);
1483 }
1484 $listId = implode(",", $temp);
1485 }
1486
1487 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1488 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1489 if (empty($listId)) {
1490 return 0;
1491 }
1492
1493 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1494 $sql .= " WHERE element_id = ".((int) $this->id);
1495 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1496
1497 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1498 if ($this->db->query($sql)) {
1499 return 1;
1500 } else {
1501 $this->error = $this->db->lasterror();
1502 return -1;
1503 }
1504 }
1505
1506 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1518 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1519 {
1520 // phpcs:enable
1521 global $langs;
1522
1523 $tab = array();
1524
1525 $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
1526 if ($source == 'internal') {
1527 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender, t.fk_country as country_id";
1528 }
1529 if ($source == 'external' || $source == 'thirdparty') {
1530 $sql .= ", t.fk_soc as socid, t.statut as statuscontact, t.fk_pays as country_id";
1531 }
1532 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email, t.address, t.zip, t.town";
1533 if (empty($arrayoftcids)) {
1534 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label, co.label as country";
1535 }
1536 $sql .= " FROM";
1537 if (empty($arrayoftcids)) {
1538 $sql .= " ".$this->db->prefix()."c_type_contact as tc,";
1539 }
1540 $sql .= " ".$this->db->prefix()."element_contact as ec";
1541 if ($source == 'internal') { // internal contact (user)
1542 $sql .= " LEFT JOIN ".$this->db->prefix()."user as t on ec.fk_socpeople = t.rowid";
1543 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_country";
1544 }
1545 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1546 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople as t on ec.fk_socpeople = t.rowid";
1547 $sql .= " LEFT JOIN ".$this->db->prefix()."c_country as co ON co.rowid = t.fk_pays";
1548 }
1549 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1550 if (empty($arrayoftcids)) {
1551 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1552 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1553 if ($code) {
1554 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1555 }
1556 if ($source == 'internal') {
1557 $sql .= " AND tc.source = 'internal'";
1558 }
1559 if ($source == 'external' || $source == 'thirdparty') {
1560 $sql .= " AND tc.source = 'external'";
1561 }
1562 $sql .= " AND tc.active = 1";
1563 } else {
1564 $sql .= " AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', $arrayoftcids)).")";
1565 }
1566 if ($status >= 0) {
1567 $sql .= " AND t.statut = ".((int) $status); // t is llx_user or llx_socpeople
1568 }
1569 if ($statusoflink >= 0) {
1570 $sql .= " AND ec.statut = ".((int) $statusoflink);
1571 }
1572 $sql .= " ORDER BY t.lastname ASC";
1573
1574 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1575 $resql = $this->db->query($sql);
1576 if ($resql) {
1577 $num = $this->db->num_rows($resql);
1578 $i = 0;
1579 while ($i < $num) {
1580 $obj = $this->db->fetch_object($resql);
1581
1582 if (!$list) {
1583 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1584 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1585 $tab[$i] = array(
1586 'parentId' => $this->id,
1587 'source' => $obj->source,
1588 'socid' => $obj->socid,
1589 'id' => $obj->id,
1590 'nom' => $obj->lastname, // For backward compatibility
1591 'civility' => $obj->civility,
1592 'lastname' => $obj->lastname,
1593 'firstname' => $obj->firstname,
1594 'email' => $obj->email,
1595 'address' => $obj->address,
1596 'zip' => $obj->zip,
1597 'town' => $obj->town,
1598 'country_id' => $obj->country_id,
1599 'country' => $obj->country,
1600 'login' => (empty($obj->login) ? '' : $obj->login),
1601 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1602 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1603 'statuscontact' => $obj->statuscontact,
1604 'rowid' => $obj->rowid,
1605 'code' => $obj->code,
1606 'libelle' => $libelle_type,
1607 'status' => (int) $obj->statuslink,
1608 'fk_c_type_contact' => $obj->fk_c_type_contact
1609 );
1610 } else {
1611 $tab[$i] = $obj->id;
1612 }
1613
1614 $i++;
1615 }
1616
1617 return $tab;
1618 } else {
1619 $this->error = $this->db->lasterror();
1620 dol_print_error($this->db);
1621 return -1;
1622 }
1623 }
1624
1625
1632 public function swapContactStatus($rowid)
1633 {
1634 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1635 $sql .= " tc.code, tc.libelle as type_label";
1636 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1637 $sql .= " WHERE ec.rowid =".((int) $rowid);
1638 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1639 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1640
1641 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1642 $resql = $this->db->query($sql);
1643 if ($resql) {
1644 $obj = $this->db->fetch_object($resql);
1645 $newstatut = ($obj->statut == 4) ? 5 : 4;
1646 $result = $this->update_contact($rowid, $newstatut);
1647 $this->db->free($resql);
1648 return $result;
1649 } else {
1650 $this->error = $this->db->error();
1651 dol_print_error($this->db);
1652 return -1;
1653 }
1654 }
1655
1656 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1667 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1668 {
1669 // phpcs:enable
1670 global $langs;
1671
1672 if (empty($order)) {
1673 $order = 'position';
1674 }
1675 if ($order == 'position') {
1676 $order .= ',code';
1677 }
1678
1679 $tab = array();
1680
1681 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1682 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1683 $sql .= " WHERE tc.element = '".$this->db->escape($this->element)."'";
1684 if ($activeonly == 1) {
1685 $sql .= " AND tc.active = 1"; // only the active types
1686 }
1687 if (!empty($source) && $source != 'all') {
1688 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1689 }
1690 if (!empty($code)) {
1691 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1692 }
1693 $sql .= $this->db->order($order, 'ASC');
1694
1695 //print "sql=".$sql;
1696 $resql = $this->db->query($sql);
1697 if (!$resql) {
1698 $this->error = $this->db->lasterror();
1699 //dol_print_error($this->db);
1700 return null;
1701 }
1702
1703 $num = $this->db->num_rows($resql);
1704 $i = 0;
1705 while ($i < $num) {
1706 $obj = $this->db->fetch_object($resql);
1707
1708 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1709 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $langs->trans($obj->type_label));
1710 if (empty($option)) {
1711 $tab[$obj->rowid] = $libelle_type;
1712 } elseif ($option == 1) {
1713 $tab[$obj->code] = $libelle_type;
1714 } else {
1715 $tab[$obj->rowid] = array('id' => $obj->rowid, 'code' => $obj->code, 'label' => $libelle_type);
1716 }
1717 $i++;
1718 }
1719
1720 return $tab;
1721 }
1722
1734 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1735 {
1736 global $langs;
1737
1738 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1739
1740 $tab = array();
1741
1742 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1743 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1744
1745 $sqlWhere = array();
1746 if (!empty($element)) {
1747 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1748 }
1749 if (!empty($excludeelement)) {
1750 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1751 }
1752
1753 if ($activeonly == 1) {
1754 $sqlWhere[] = " tc.active=1"; // only the active types
1755 }
1756
1757 if (!empty($source) && $source != 'all') {
1758 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1759 }
1760
1761 if (!empty($code)) {
1762 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1763 }
1764
1765 if (count($sqlWhere) > 0) {
1766 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1767 }
1768
1769 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1770
1771 dol_syslog(__METHOD__, LOG_DEBUG);
1772 $resql = $this->db->query($sql);
1773 if ($resql) {
1774 $num = $this->db->num_rows($resql);
1775 if ($num > 0) {
1776 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1777
1778 while ($obj = $this->db->fetch_object($resql)) {
1779 $modulename = $obj->module ?? $obj->element;
1780 if (strpos($obj->element, 'project') !== false) {
1781 $modulename = 'projet';
1782 } elseif ($obj->element == 'contrat') {
1783 $element = 'contract';
1784 } elseif ($obj->element == 'action') {
1785 $modulename = 'agenda';
1786 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1787 $modulename = 'fournisseur';
1788 }
1789 if (isModEnabled($modulename)) {
1790 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1791 $tmpelement = $obj->element;
1792 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1793 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1794 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1795 }
1796 }
1797 }
1798 return $tab;
1799 } else {
1800 $this->error = $this->db->lasterror();
1801 return null;
1802 }
1803 }
1804
1816 public function getIdContact($source, $code, $status = 0)
1817 {
1818 $result = array();
1819 $i = 0;
1820 // Particular case for shipping
1821 if (!getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && $this->element == 'shipping' && $this->origin_id != 0) {
1822 $id = $this->origin_id;
1823 $element = 'commande';
1824 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1825 $id = $this->origin_id;
1826 $element = 'order_supplier';
1827 } else {
1828 $id = $this->id;
1829 $element = $this->element;
1830 }
1831
1832 $sql = "SELECT ec.fk_socpeople";
1833 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1834 if ($source == 'internal') {
1835 $sql .= " ".$this->db->prefix()."user as c,";
1836 }
1837 if ($source == 'external') {
1838 $sql .= " ".$this->db->prefix()."socpeople as c,";
1839 }
1840 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1841 $sql .= " WHERE ec.element_id = ".((int) $id);
1842 $sql .= " AND ec.fk_socpeople = c.rowid";
1843 if ($source == 'internal') {
1844 $sql .= " AND c.entity IN (".getEntity('user').")";
1845 }
1846 if ($source == 'external') {
1847 $sql .= " AND c.entity IN (".getEntity('societe').")";
1848 }
1849 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1850 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1851 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1852 if ($code) {
1853 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1854 }
1855 $sql .= " AND tc.active = 1";
1856 if ($status) {
1857 $sql .= " AND ec.statut = ".((int) $status);
1858 }
1859
1860 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1861 $resql = $this->db->query($sql);
1862 if ($resql) {
1863 while ($obj = $this->db->fetch_object($resql)) {
1864 $result[$i] = $obj->fk_socpeople;
1865 $i++;
1866 }
1867 } else {
1868 $this->error = $this->db->error();
1869 return null;
1870 }
1871
1872 return $result;
1873 }
1874
1875 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1882 public function fetch_contact($contactid = null)
1883 {
1884 // phpcs:enable
1885 if (empty($contactid)) {
1886 $contactid = $this->contact_id;
1887 }
1888
1889 if (empty($contactid)) {
1890 return 0;
1891 }
1892
1893 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1894 $contact = new Contact($this->db);
1895 $result = $contact->fetch($contactid);
1896 $this->contact = $contact;
1897 return $result;
1898 }
1899
1900 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1908 public function fetch_thirdparty($force_thirdparty_id = 0)
1909 {
1910 // phpcs:enable
1911 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1912 return 0;
1913 }
1914
1915 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1916
1917 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1918 if ($force_thirdparty_id) {
1919 $idtofetch = $force_thirdparty_id;
1920 }
1921
1922 if ($idtofetch) {
1923 $thirdparty = new Societe($this->db);
1924 $result = $thirdparty->fetch($idtofetch);
1925 if ($result < 0) {
1926 $this->errors = array_merge($this->errors, $thirdparty->errors);
1927 }
1928 $this->thirdparty = $thirdparty;
1929
1930 // Use first price level if level not defined for third party
1931 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($this->thirdparty->price_level)) {
1932 $this->thirdparty->price_level = 1;
1933 }
1934
1935 return $result;
1936 } else {
1937 return -1;
1938 }
1939 }
1940
1941
1949 public function fetchOneLike($ref)
1950 {
1951 if (!$this->table_ref_field) {
1952 return 0;
1953 }
1954
1955 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
1956 $sql .= " WHERE ".$this->table_ref_field." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
1957 $sql .= " LIMIT 1";
1958
1959 $query = $this->db->query($sql);
1960
1961 if (!$this->db->num_rows($query)) {
1962 return 0;
1963 }
1964
1965 $result = $this->db->fetch_object($query);
1966
1967 if (method_exists($this, 'fetch')) {
1968 return $this->fetch($result->rowid);
1969 } else {
1970 $this->error = 'Fetch method not implemented on '.get_class($this);
1971 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
1972 array_push($this->errors, $this->error);
1973 return -1;
1974 }
1975 }
1976
1977 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1986 public function fetch_barcode()
1987 {
1988 // phpcs:enable
1989 return $this->fetchBarCode();
1990 }
1991
1999 public function fetchBarCode()
2000 {
2001 dol_syslog(get_class($this).'::fetchBarCode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
2002
2003 $idtype = $this->barcode_type;
2004 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
2005 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2006 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2007 } elseif ($this->element == 'societe') {
2008 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2009 } else {
2010 dol_syslog('Call fetchBarCode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2011 }
2012 }
2013
2014 if ($idtype > 0) {
2015 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
2016 $sql = "SELECT rowid, code, libelle as label, coder";
2017 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
2018 $sql .= " WHERE rowid = ".((int) $idtype);
2019
2020 $resql = $this->db->query($sql);
2021 if ($resql) {
2022 $obj = $this->db->fetch_object($resql);
2023
2024 $this->barcode_type = $obj->rowid;
2025 $this->barcode_type_code = $obj->code;
2026 $this->barcode_type_label = $obj->label;
2027 $this->barcode_type_coder = $obj->coder;
2028 return 1;
2029 } else {
2030 dol_print_error($this->db);
2031 return -1;
2032 }
2033 }
2034 }
2035 return 0;
2036 }
2037
2043 public function fetchProject()
2044 {
2045 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2046
2047 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2048 $this->fk_project = $this->fk_projet; // For backward compatibility
2049 }
2050 if (empty($this->fk_project)) {
2051 return 0;
2052 }
2053
2054 $project = new Project($this->db);
2055 $result = $project->fetch($this->fk_project);
2056
2057 $this->projet = $project; // deprecated
2058 $this->project = $project;
2059
2060 return $result;
2061 }
2062
2063 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2071 public function fetch_project()
2072 {
2073 // phpcs:enable
2074 return $this->fetchProject();
2075 }
2076
2077 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2085 public function fetch_projet()
2086 {
2087 // phpcs:enable
2088 return $this->fetchProject();
2089 }
2090
2091 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2097 public function fetch_product()
2098 {
2099 // phpcs:enable
2100 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2101
2102 // @phan-suppress-next-line PhanUndeclaredProperty
2103 if (empty($this->fk_product)) {
2104 return 0;
2105 }
2106
2107 $product = new Product($this->db);
2108 // @phan-suppress-next-line PhanUndeclaredProperty
2109 $result = $product->fetch($this->fk_product);
2110
2111 $this->product = $product;
2112 return $result;
2113 }
2114
2115 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2122 public function fetch_user($userid)
2123 {
2124 // phpcs:enable
2125 $user = new User($this->db);
2126 $result = $user->fetch($userid);
2127 $this->user = $user;
2128 return $result;
2129 }
2130
2131 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2138 public function fetch_origin()
2139 {
2140 // phpcs:enable
2141 $origin = $this->origin ? $this->origin : $this->origin_type;
2142
2143 // Manage classes with non standard name
2144 if ($origin == 'shipping') {
2145 $origin = 'expedition';
2146 }
2147 if ($origin == 'delivery') {
2148 $origin = 'livraison';
2149 }
2150 if ($origin == 'order_supplier' || $origin == 'supplier_order') {
2151 $origin = 'commandeFournisseur';
2152 }
2153
2154 $classname = ucfirst($origin);
2155 $this->origin_object = new $classname($this->db);
2156 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2157 $this->origin_object->fetch($this->origin_id);
2158 }
2159
2169 public function fetchObjectFrom($table, $field, $key, $element = null)
2170 {
2171 global $conf;
2172
2173 $result = false;
2174
2175 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2176 $sql .= " WHERE ".$field." = '".$this->db->escape($key)."'";
2177 if (!empty($element)) {
2178 $sql .= " AND entity IN (".getEntity($element).")";
2179 } else {
2180 $sql .= " AND entity = ".((int) $conf->entity);
2181 }
2182
2183 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2184 $resql = $this->db->query($sql);
2185 if ($resql) {
2186 $obj = $this->db->fetch_object($resql);
2187 // Test for avoid error -1
2188 if ($obj) {
2189 if (method_exists($this, 'fetch')) {
2190 $result = $this->fetch($obj->rowid);
2191 } else {
2192 $this->error = 'fetch() method not implemented on '.get_class($this);
2193 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2194 array_push($this->errors, $this->error);
2195 $result = -1;
2196 }
2197 }
2198 }
2199
2200 return $result;
2201 }
2202
2211 public function getValueFrom($table, $id, $field)
2212 {
2213 $result = false;
2214 if (!empty($id) && !empty($field) && !empty($table)) {
2215 $sql = "SELECT ".$field." FROM ".$this->db->prefix().$table;
2216 $sql .= " WHERE rowid = ".((int) $id);
2217
2218 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2219 $resql = $this->db->query($sql);
2220 if ($resql) {
2221 $row = $this->db->fetch_row($resql);
2222 $result = $row[0];
2223 }
2224 }
2225 return $result;
2226 }
2227
2244 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2245 {
2246 global $user;
2247
2248 if (empty($table)) {
2249 $table = $this->table_element;
2250 }
2251 if (empty($id)) {
2252 $id = $this->id;
2253 }
2254 if (empty($format)) {
2255 $format = 'text';
2256 }
2257 if (empty($id_field)) {
2258 $id_field = 'rowid';
2259 }
2260
2261 // Special case
2262 if ($table == 'product' && $field == 'note_private') {
2263 $field = 'note';
2264 }
2265
2266 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2267 $fk_user_field = 'fk_user_mod';
2268 }
2269 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2270 $fk_user_field = '';
2271 }
2272
2273 $oldvalue = null;
2274 if ($trigkey) {
2275 $sql = "SELECT " . $field;
2276 $sql .= " FROM " . MAIN_DB_PREFIX . $table;
2277 $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2278
2279 $resql = $this->db->query($sql);
2280 if ($resql) {
2281 if ($obj = $this->db->fetch_object($resql)) {
2282 if ($format == 'date') {
2283 $oldvalue = $this->db->jdate($obj->$field);
2284 } else {
2285 $oldvalue = $obj->$field;
2286 }
2287 }
2288 } else {
2289 $this->error = $this->db->lasterror();
2290 return -1;
2291 }
2292 }
2293
2294 $error = 0;
2295
2296 dol_syslog(__METHOD__, LOG_DEBUG);
2297
2298 $this->db->begin();
2299
2300 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2301
2302 if ($format == 'text') {
2303 $sql .= $field." = '".$this->db->escape($value)."'";
2304 } elseif ($format == 'int') {
2305 $sql .= $field." = ".((int) $value);
2306 } elseif ($format == 'date') {
2307 $sql .= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2308 } elseif ($format == 'dategmt') {
2309 $sql .= $field." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2310 }
2311
2312 if ($fk_user_field) {
2313 if (!empty($fuser) && is_object($fuser)) {
2314 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $fuser->id);
2315 } elseif (empty($fuser) || $fuser != 'none') {
2316 $sql .= ", ".$this->db->sanitize($fk_user_field)." = ".((int) $user->id);
2317 }
2318 }
2319
2320 $sql .= " WHERE ".$id_field." = ".((int) $id);
2321
2322 $resql = $this->db->query($sql);
2323 if ($resql) {
2324 if ($trigkey) {
2325 // call trigger with updated object values
2326 if (method_exists($this, 'fetch')) {
2327 $result = $this->fetch($id);
2328 } else {
2329 $result = $this->fetchCommon($id);
2330 }
2331 $this->oldcopy = clone $this;
2332 if (property_exists($this->oldcopy, $field)) {
2333 $this->oldcopy->$field = $oldvalue;
2334 }
2335
2336 if ($result >= 0) {
2337 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2338 }
2339 if ($result < 0) {
2340 $error++;
2341 }
2342 }
2343
2344 if (!$error) {
2345 if (property_exists($this, $field)) {
2346 $this->$field = $value;
2347 }
2348 $this->db->commit();
2349 return 1;
2350 } else {
2351 $this->db->rollback();
2352 return -2;
2353 }
2354 } else {
2355 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2356 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2357 } else {
2358 $this->error = $this->db->lasterror();
2359 }
2360 $this->db->rollback();
2361 return -1;
2362 }
2363 }
2364
2365 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2376 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2377 {
2378 // phpcs:enable
2379 global $conf, $user;
2380
2381 if (!$this->table_element) {
2382 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2383 return -1;
2384 }
2385 if ($fieldid == 'none') {
2386 return 1;
2387 }
2388
2389 // For backward compatibility
2390 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2391 $fieldid = 'titre';
2392 }
2393
2394 // Security on socid
2395 $socid = 0;
2396 if ($user->socid > 0) {
2397 $socid = $user->socid;
2398 }
2399
2400 // this->ismultientitymanaged contains
2401 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2402 $aliastablesociete = 's';
2403 if ($this->element == 'societe') {
2404 $aliastablesociete = 'te'; // te as table_element
2405 }
2406 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2407 $sql = "SELECT MAX(te.".$fieldid.")";
2408 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2409 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2410 $tmparray = explode('@', $this->ismultientitymanaged);
2411 $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
2412 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2413 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2414 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2415 $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
2416 }
2417 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2418 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2419 }
2420 if ($fieldid == 'rowid') {
2421 $sql .= " WHERE te.".$fieldid." < ".((int) $this->id);
2422 } else {
2423 $sql .= " WHERE te.".$fieldid." < '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2424 }
2425 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2426 $sql .= " AND sc.fk_user = ".((int) $user->id);
2427 }
2428 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2429 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2430 }
2431
2432 $filtermax = $filter;
2433
2434 // Manage filter
2435 $errormessage = '';
2436 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2437 if ($errormessage) {
2438 if (!preg_match('/^\s*AND/i', $filtermax)) {
2439 $sql .= " AND ";
2440 }
2441 $sql .= $filtermax;
2442 } else {
2443 $sql .= $tmpsql;
2444 }
2445
2446 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2447 $tmparray = explode('@', $this->ismultientitymanaged);
2448 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2449 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2450 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2451 }
2452 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2453 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2454 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2455 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2456 } else {
2457 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2458 }
2459 } else {
2460 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2461 }
2462 }
2463 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2464 $tmparray = explode('@', $this->ismultientitymanaged);
2465 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2466 }
2467 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2468 $sql .= ' AND te.fk_soc = '.((int) $socid);
2469 }
2470 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2471 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2472 }
2473 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2474 $sql .= ' AND te.rowid = '.((int) $socid);
2475 }
2476 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2477
2478 $result = $this->db->query($sql);
2479 if (!$result) {
2480 $this->error = $this->db->lasterror();
2481 return -1;
2482 }
2483 $row = $this->db->fetch_row($result);
2484 $this->ref_previous = $row[0];
2485
2486 $sql = "SELECT MIN(te.".$fieldid.")";
2487 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2488 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2489 $tmparray = explode('@', $this->ismultientitymanaged);
2490 $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
2491 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2492 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2493 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2494 $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
2495 }
2496 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2497 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2498 }
2499 if ($fieldid == 'rowid') {
2500 $sql .= " WHERE te.".$fieldid." > ".((int) $this->id);
2501 } else {
2502 $sql .= " WHERE te.".$fieldid." > '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2503 }
2504 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2505 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2506 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2507 $userschilds = $user->getAllChildIds();
2508 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2509 }
2510 $sql .= ')';
2511 }
2512 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2513 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2514 }
2515
2516 $filtermin = $filter;
2517
2518 // Manage filter
2519 $errormessage = '';
2520 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2521 if ($errormessage) {
2522 if (!preg_match('/^\s*AND/i', $filtermin)) {
2523 $sql .= " AND ";
2524 }
2525 $sql .= $filtermin;
2526
2527 $filtermin = '';
2528 } else {
2529 $sql .= $tmpsql;
2530 }
2531
2532 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2533 $tmparray = explode('@', $this->ismultientitymanaged);
2534 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2535 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2536 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2537 }
2538 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2539 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2540 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2541 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2542 } else {
2543 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2544 }
2545 } else {
2546 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2547 }
2548 }
2549 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2550 $tmparray = explode('@', $this->ismultientitymanaged);
2551 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2552 }
2553 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2554 $sql .= ' AND te.fk_soc = '.((int) $socid);
2555 }
2556 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2557 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2558 }
2559 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2560 $sql .= ' AND te.rowid = '.((int) $socid);
2561 }
2562 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2563 // 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
2564
2565 $result = $this->db->query($sql);
2566 if (!$result) {
2567 $this->error = $this->db->lasterror();
2568 return -2;
2569 }
2570 $row = $this->db->fetch_row($result);
2571 $this->ref_next = $row[0];
2572
2573 return 1;
2574 }
2575
2576
2584 public function getListContactId($source = 'external')
2585 {
2586 $contactAlreadySelected = array();
2587 $tab = $this->liste_contact(-1, $source);
2588 $num = count($tab);
2589 $i = 0;
2590 while ($i < $num) {
2591 if ($source == 'thirdparty') {
2592 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2593 } else {
2594 $contactAlreadySelected[$i] = $tab[$i]['id'];
2595 }
2596 $i++;
2597 }
2598 return $contactAlreadySelected;
2599 }
2600
2601
2609 public function setProject($projectid, $notrigger = 0)
2610 {
2611 global $user;
2612 $error = 0;
2613
2614 if (!$this->table_element) {
2615 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2616 return -1;
2617 }
2618
2619 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2620 // @phan-suppress-next-line PhanTypeMismatchProperty
2621 if (!empty($this->fields['fk_project'])) { // Common case
2622 if ($projectid) {
2623 $sql .= " SET fk_project = ".((int) $projectid);
2624 } else {
2625 $sql .= " SET fk_project = NULL";
2626 }
2627 $sql .= ' WHERE rowid = '.((int) $this->id);
2628 } elseif ($this->table_element == 'actioncomm') { // Special case for actioncomm
2629 if ($projectid) {
2630 $sql .= " SET fk_project = ".((int) $projectid);
2631 } else {
2632 $sql .= " SET fk_project = NULL";
2633 }
2634 $sql .= ' WHERE id = '.((int) $this->id);
2635 } else { // Special case for old architecture objects
2636 if ($projectid) {
2637 $sql .= ' SET fk_projet = '.((int) $projectid);
2638 } else {
2639 $sql .= ' SET fk_projet = NULL';
2640 }
2641 $sql .= " WHERE rowid = ".((int) $this->id);
2642 }
2643
2644 $this->db->begin();
2645
2646 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2647 if ($this->db->query($sql)) {
2648 $this->fk_project = ((int) $projectid);
2649 } else {
2650 dol_print_error($this->db);
2651 $error++;
2652 }
2653
2654 // Triggers
2655 if (!$error && !$notrigger) {
2656 // Call triggers
2657 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2658 if ($result < 0) {
2659 $error++;
2660 } //Do also here what you must do to rollback action if trigger fail
2661 // End call triggers
2662 }
2663
2664 // Commit or rollback
2665 if ($error) {
2666 $this->db->rollback();
2667 return -1;
2668 } else {
2669 $this->db->commit();
2670 return 1;
2671 }
2672 }
2673
2680 public function setPaymentMethods($id)
2681 {
2682 global $user;
2683
2684 $error = 0;
2685 $notrigger = 0;
2686
2687 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2688
2689 if ($this->status >= 0 || $this->element == 'societe') {
2690 // TODO uniformize field name
2691 $fieldname = 'fk_mode_reglement';
2692 if ($this->element == 'societe') {
2693 $fieldname = 'mode_reglement';
2694 }
2695 if (get_class($this) == 'Fournisseur') {
2696 $fieldname = 'mode_reglement_supplier';
2697 }
2698 if (get_class($this) == 'Tva') {
2699 $fieldname = 'fk_typepayment';
2700 }
2701 if (get_class($this) == 'Salary') {
2702 $fieldname = 'fk_typepayment';
2703 }
2704
2705 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2706 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2707 $sql .= ' WHERE rowid='.((int) $this->id);
2708
2709 if ($this->db->query($sql)) {
2710 $this->mode_reglement_id = $id;
2711 // for supplier
2712 if (get_class($this) == 'Fournisseur') {
2713 $this->mode_reglement_supplier_id = $id;
2714 }
2715 // Triggers
2716 if (!$error && !$notrigger) {
2717 // Call triggers
2718 if (get_class($this) == 'Commande') {
2719 $result = $this->call_trigger('ORDER_MODIFY', $user);
2720 } else {
2721 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
2722 }
2723 if ($result < 0) {
2724 $error++;
2725 }
2726 // End call triggers
2727 }
2728 return 1;
2729 } else {
2730 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2731 $this->error = $this->db->error();
2732 return -1;
2733 }
2734 } else {
2735 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2736 $this->error = 'Status of the object is incompatible '.$this->status;
2737 return -2;
2738 }
2739 }
2740
2747 public function setMulticurrencyCode($code)
2748 {
2749 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2750 if ($this->status >= 0 || $this->element == 'societe') {
2751 $fieldname = 'multicurrency_code';
2752
2753 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2754 $sql .= " SET ".$fieldname." = '".$this->db->escape($code)."'";
2755 $sql .= ' WHERE rowid='.((int) $this->id);
2756
2757 if ($this->db->query($sql)) {
2758 $this->multicurrency_code = $code;
2759
2760 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2761 if ($rate) {
2762 $this->setMulticurrencyRate($rate, 2);
2763 }
2764
2765 return 1;
2766 } else {
2767 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2768 $this->error = $this->db->error();
2769 return -1;
2770 }
2771 } else {
2772 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2773 $this->error = 'Status of the object is incompatible '.$this->status;
2774 return -2;
2775 }
2776 }
2777
2785 public function setMulticurrencyRate($rate, $mode = 1)
2786 {
2787 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2788 if ($this->status >= 0 || $this->element == 'societe') {
2789 $fieldname = 'multicurrency_tx';
2790
2791 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2792 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".((float) $rate);
2793 $sql .= ' WHERE rowid='.((int) $this->id);
2794
2795 if ($this->db->query($sql)) {
2796 $this->multicurrency_tx = $rate;
2797
2798 // Update line price
2799 if (!empty($this->lines)) {
2800 foreach ($this->lines as &$line) {
2801 // Amounts in company currency will be recalculated
2802 if ($mode == 1) {
2803 $line->subprice = 0;
2804 }
2805
2806 // Amounts in foreign currency will be recalculated
2807 if ($mode == 2) {
2808 $line->multicurrency_subprice = 0;
2809 }
2810
2811 switch ($this->element) {
2812 case 'propal':
2815 '@phan-var-force Propal $this';
2816 '@phan-var-force PropaleLigne $line';
2817 $this->updateline(
2818 $line->id,
2819 $line->subprice,
2820 $line->qty,
2821 $line->remise_percent,
2822 $line->tva_tx,
2823 $line->localtax1_tx,
2824 $line->localtax2_tx,
2825 ($line->description ? $line->description : $line->desc),
2826 'HT',
2827 $line->info_bits,
2828 $line->special_code,
2829 $line->fk_parent_line,
2830 $line->skip_update_total,
2831 $line->fk_fournprice,
2832 $line->pa_ht,
2833 $line->label,
2834 $line->product_type,
2835 $line->date_start,
2836 $line->date_end,
2837 $line->array_options,
2838 $line->fk_unit,
2839 $line->multicurrency_subprice
2840 );
2841 break;
2842 case 'commande':
2845 '@phan-var-force Commande $this';
2846 '@phan-var-force OrderLine $line';
2847 $this->updateline(
2848 $line->id,
2849 ($line->description ? $line->description : $line->desc),
2850 $line->subprice,
2851 $line->qty,
2852 $line->remise_percent,
2853 $line->tva_tx,
2854 $line->localtax1_tx,
2855 $line->localtax2_tx,
2856 'HT',
2857 $line->info_bits,
2858 $line->date_start,
2859 $line->date_end,
2860 $line->product_type,
2861 $line->fk_parent_line,
2862 $line->skip_update_total,
2863 $line->fk_fournprice,
2864 $line->pa_ht,
2865 $line->label,
2866 $line->special_code,
2867 $line->array_options,
2868 $line->fk_unit,
2869 $line->multicurrency_subprice
2870 );
2871 break;
2872 case 'facture':
2875 '@phan-var-force Facture $this';
2876 '@phan-var-force FactureLigne $line';
2877 $this->updateline(
2878 $line->id,
2879 ($line->description ? $line->description : $line->desc),
2880 $line->subprice,
2881 $line->qty,
2882 $line->remise_percent,
2883 $line->date_start,
2884 $line->date_end,
2885 $line->tva_tx,
2886 $line->localtax1_tx,
2887 $line->localtax2_tx,
2888 'HT',
2889 $line->info_bits,
2890 $line->product_type,
2891 $line->fk_parent_line,
2892 $line->skip_update_total,
2893 $line->fk_fournprice,
2894 $line->pa_ht,
2895 $line->label,
2896 $line->special_code,
2897 $line->array_options,
2898 $line->situation_percent,
2899 $line->fk_unit,
2900 $line->multicurrency_subprice
2901 );
2902 break;
2903 case 'facturerec':
2906 '@phan-var-force FactureRec $this';
2907 '@phan-var-force FactureLigneRec $line';
2908 $this->updateline(
2909 $line->id,
2910 ($line->description ? $line->description : $line->desc),
2911 $line->subprice,
2912 $line->qty,
2913 $line->tva_tx,
2914 $line->localtax1_tx,
2915 $line->localtax2_tx,
2916 $line->fk_product,
2917 $line->remise_percent,
2918 'HT',
2919 $line->info_bits,
2920 0,
2921 0,
2922 $line->product_type,
2923 $line->rang,
2924 $line->special_code,
2925 $line->label,
2926 $line->fk_unit,
2927 $line->multicurrency_subprice,
2928 0,
2929 $line->date_start,
2930 $line->date_end,
2931 $line->fk_fournprice,
2932 $line->pa_ht,
2933 $line->fk_parent_line
2934 );
2935 break;
2936 case 'supplier_proposal':
2939 '@phan-var-force SupplierProposal $this';
2940 '@phan-var-force SupplierProposalLine $line';
2941 $this->updateline(
2942 $line->id,
2943 $line->subprice,
2944 $line->qty,
2945 $line->remise_percent,
2946 $line->tva_tx,
2947 $line->localtax1_tx,
2948 $line->localtax2_tx,
2949 ($line->description ? $line->description : $line->desc),
2950 'HT',
2951 $line->info_bits,
2952 $line->special_code,
2953 $line->fk_parent_line,
2954 $line->skip_update_total,
2955 $line->fk_fournprice,
2956 $line->pa_ht,
2957 $line->label,
2958 $line->product_type,
2959 $line->array_options,
2960 $line->ref_fourn,
2961 (int) $line->fk_unit,
2962 $line->multicurrency_subprice
2963 );
2964 break;
2965 case 'order_supplier':
2968 '@phan-var-force CommandeFournisseur $this';
2969 '@phan-var-force CommandeFournisseurLigne $line';
2970 $this->updateline(
2971 $line->id,
2972 ($line->description ? $line->description : $line->desc),
2973 $line->subprice,
2974 $line->qty,
2975 $line->remise_percent,
2976 $line->tva_tx,
2977 $line->localtax1_tx,
2978 $line->localtax2_tx,
2979 'HT',
2980 $line->info_bits,
2981 $line->product_type,
2982 0,
2983 $line->date_start,
2984 $line->date_end,
2985 $line->array_options,
2986 $line->fk_unit,
2987 $line->multicurrency_subprice,
2988 $line->ref_supplier
2989 );
2990 break;
2991 case 'invoice_supplier':
2994 '@phan-var-force FactureFournisseur $this';
2995 '@phan-var-force SupplierInvoiceLIne $line';
2996 $this->updateline(
2997 $line->id,
2998 ($line->description ? $line->description : $line->desc),
2999 $line->subprice,
3000 $line->tva_tx,
3001 $line->localtax1_tx,
3002 $line->localtax2_tx,
3003 $line->qty,
3004 0,
3005 'HT',
3006 $line->info_bits,
3007 $line->product_type,
3008 $line->remise_percent,
3009 0,
3010 $line->date_start,
3011 $line->date_end,
3012 $line->array_options,
3013 $line->fk_unit,
3014 $line->multicurrency_subprice,
3015 $line->ref_supplier
3016 );
3017 break;
3018 default:
3019 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3020 break;
3021 }
3022 }
3023 }
3024
3025 return 1;
3026 } else {
3027 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
3028 $this->error = $this->db->error();
3029 return -1;
3030 }
3031 } else {
3032 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
3033 $this->error = 'Status of the object is incompatible '.$this->status;
3034 return -2;
3035 }
3036 }
3037
3045 public function setPaymentTerms($id, $deposit_percent = null)
3046 {
3047 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.var_export($deposit_percent, true).')');
3048 if ($this->status >= 0 || $this->element == 'societe') {
3049 // TODO uniformize field name
3050 $fieldname = 'fk_cond_reglement';
3051 if ($this->element == 'societe') {
3052 $fieldname = 'cond_reglement';
3053 }
3054 if (get_class($this) == 'Fournisseur') {
3055 $fieldname = 'cond_reglement_supplier';
3056 }
3057
3058 if (empty($deposit_percent) || $deposit_percent < 0) {
3059 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3060 }
3061
3062 if ($deposit_percent > 100) {
3063 $deposit_percent = 100;
3064 }
3065
3066 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3067 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3068 if (in_array($this->table_element, array('propal', 'commande', 'societe'))) {
3069 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape((string) $deposit_percent)."'");
3070 }
3071 $sql .= ' WHERE rowid='.((int) $this->id);
3072
3073 if ($this->db->query($sql)) {
3074 $this->cond_reglement_id = $id;
3075 // for supplier
3076 if (get_class($this) == 'Fournisseur') {
3077 $this->cond_reglement_supplier_id = $id;
3078 }
3079 $this->deposit_percent = $deposit_percent;
3080 return 1;
3081 } else {
3082 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
3083 $this->error = $this->db->error();
3084 return -1;
3085 }
3086 } else {
3087 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
3088 $this->error = 'Status of the object is incompatible '.$this->status;
3089 return -2;
3090 }
3091 }
3092
3099 public function setTransportMode($id)
3100 {
3101 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
3102 if ($this->status >= 0 || $this->element == 'societe') {
3103 $fieldname = 'fk_transport_mode';
3104 if ($this->element == 'societe') {
3105 $fieldname = 'transport_mode';
3106 }
3107 if (get_class($this) == 'Fournisseur') {
3108 $fieldname = 'transport_mode_supplier';
3109 }
3110
3111 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3112 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
3113 $sql .= ' WHERE rowid='.((int) $this->id);
3114
3115 if ($this->db->query($sql)) {
3116 $this->transport_mode_id = $id;
3117 // for supplier
3118 if (get_class($this) == 'Fournisseur') {
3119 $this->transport_mode_supplier_id = $id;
3120 }
3121 return 1;
3122 } else {
3123 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3124 $this->error = $this->db->error();
3125 return -1;
3126 }
3127 } else {
3128 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3129 $this->error = 'Status of the object is incompatible '.$this->status;
3130 return -2;
3131 }
3132 }
3133
3141 {
3142 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
3143 if ($this->status >= 0 || $this->element == 'societe') {
3144 $fieldname = 'retained_warranty_fk_cond_reglement';
3145
3146 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3147 $sql .= " SET ".$fieldname." = ".((int) $id);
3148 $sql .= ' WHERE rowid='.((int) $this->id);
3149
3150 if ($this->db->query($sql)) {
3151 $this->retained_warranty_fk_cond_reglement = $id;
3152 return 1;
3153 } else {
3154 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
3155 $this->error = $this->db->error();
3156 return -1;
3157 }
3158 } else {
3159 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3160 $this->error = 'Status of the object is incompatible '.$this->status;
3161 return -2;
3162 }
3163 }
3164
3172 public function setDeliveryAddress($id)
3173 {
3174 $fieldname = 'fk_delivery_address';
3175 if ($this->element == 'delivery' || $this->element == 'shipping') {
3176 $fieldname = 'fk_address';
3177 }
3178
3179 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$fieldname." = ".((int) $id);
3180 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3181
3182 if ($this->db->query($sql)) {
3183 $this->fk_delivery_address = $id;
3184 return 1;
3185 } else {
3186 $this->error = $this->db->error();
3187 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3188 return -1;
3189 }
3190 }
3191
3192
3201 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3202 {
3203 global $user;
3204
3205 if (empty($userused)) {
3206 $userused = $user;
3207 }
3208
3209 $error = 0;
3210
3211 if (!$this->table_element) {
3212 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3213 return -1;
3214 }
3215
3216 $this->db->begin();
3217
3218 if ($shipping_method_id < 0) {
3219 $shipping_method_id = 'NULL';
3220 }
3221 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3222
3223 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3224 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3225 $sql .= " WHERE rowid=".((int) $this->id);
3226 $resql = $this->db->query($sql);
3227 if (!$resql) {
3228 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3229 $this->error = $this->db->lasterror();
3230 $error++;
3231 } else {
3232 if (!$notrigger) {
3233 // Call trigger
3234 $this->context = array('shippingmethodupdate' => 1);
3235 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
3236 if ($result < 0) {
3237 $error++;
3238 }
3239 // End call trigger
3240 }
3241 }
3242 if ($error) {
3243 $this->db->rollback();
3244 return -1;
3245 } else {
3246 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3247 $this->db->commit();
3248 return 1;
3249 }
3250 }
3251
3252
3259 public function setWarehouse($warehouse_id)
3260 {
3261 if (!$this->table_element) {
3262 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3263 return -1;
3264 }
3265 if ($warehouse_id < 0) {
3266 $warehouse_id = 'NULL';
3267 }
3268 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3269
3270 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3271 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3272 $sql .= " WHERE rowid=".((int) $this->id);
3273
3274 if ($this->db->query($sql)) {
3275 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3276 return 1;
3277 } else {
3278 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3279 $this->error = $this->db->error();
3280 return 0;
3281 }
3282 }
3283
3284
3292 public function setDocModel($user, $modelpdf)
3293 {
3294 if (!$this->table_element) {
3295 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3296 return -1;
3297 }
3298
3299 $newmodelpdf = dol_trunc($modelpdf, 255);
3300
3301 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3302 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3303 $sql .= " WHERE rowid = ".((int) $this->id);
3304
3305 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3306 $resql = $this->db->query($sql);
3307 if ($resql) {
3308 $this->model_pdf = $modelpdf;
3309 return 1;
3310 } else {
3311 dol_print_error($this->db);
3312 return 0;
3313 }
3314 }
3315
3316
3325 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3326 {
3327 global $user;
3328
3329 if (empty($userused)) {
3330 $userused = $user;
3331 }
3332
3333 $error = 0;
3334
3335 if (!$this->table_element) {
3336 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3337 return -1;
3338 }
3339 $this->db->begin();
3340
3341 if ($fk_account < 0) {
3342 $fk_account = 'NULL';
3343 }
3344 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3345
3346 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3347 $sql .= " SET fk_account = ".((int) $fk_account);
3348 $sql .= " WHERE rowid=".((int) $this->id);
3349
3350 $resql = $this->db->query($sql);
3351 if (!$resql) {
3352 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3353 $this->error = $this->db->lasterror();
3354 $error++;
3355 } else {
3356 if (!$notrigger) {
3357 // Call trigger
3358 $this->context['bankaccountupdate'] = 1;
3359 $triggerName = strtoupper(get_class($this)).'_MODIFY';
3360 // Special cases
3361 if ($triggerName == 'FACTUREREC_MODIFY') {
3362 $triggerName = 'BILLREC_MODIFY';
3363 }
3364 $result = $this->call_trigger($triggerName, $userused);
3365 if ($result < 0) {
3366 $error++;
3367 }
3368 // End call trigger
3369 }
3370 }
3371 if ($error) {
3372 $this->db->rollback();
3373 return -1;
3374 } else {
3375 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3376 $this->db->commit();
3377 return 1;
3378 }
3379 }
3380
3381
3382 // TODO: Move line related operations to CommonObjectLine?
3383
3384 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3394 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3395 {
3396 // phpcs:enable
3397 if (!$this->table_element_line) {
3398 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3399 return -1;
3400 }
3401 if (!$this->fk_element) {
3402 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3403 return -1;
3404 }
3405
3406 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3407 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3408 $fieldposition = 'position';
3409 }
3410
3411 // Count number of lines to reorder (according to choice $renum)
3412 $nl = 0;
3413 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->table_element_line;
3414 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3415 if (!$renum) {
3416 $sql .= " AND " . $fieldposition . " = 0";
3417 }
3418 if ($renum) {
3419 $sql .= " AND " . $fieldposition . " <> 0";
3420 }
3421
3422 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3423 $resql = $this->db->query($sql);
3424 if ($resql) {
3425 $row = $this->db->fetch_row($resql);
3426 $nl = $row[0];
3427 } else {
3428 dol_print_error($this->db);
3429 }
3430 if ($nl > 0) {
3431 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3432 $rows = array();
3433
3434 // We first search all lines that are parent lines (for multilevel details lines)
3435 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3436 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3437 if ($fk_parent_line) {
3438 $sql .= ' AND fk_parent_line IS NULL';
3439 }
3440 $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3441
3442 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3443 $resql = $this->db->query($sql);
3444 if ($resql) {
3445 $i = 0;
3446 $num = $this->db->num_rows($resql);
3447 $grandchild = getDolGlobalInt('MAIN_CARE_GRANDCHILD');
3448 while ($i < $num) {
3449 $row = $this->db->fetch_row($resql);
3450 $rows[] = $row[0]; // Add parent line into array rows
3451 if ($fk_parent_line) {
3452 $children = $this->getChildrenOfLine($row[0], $grandchild);
3453 }
3454 if (!empty($children)) {
3455 foreach ($children as $child) {
3456 array_push($rows, $child);
3457 }
3458 }
3459 $i++;
3460 }
3461
3462 // Now we set a new number for each lines (parent and children with children included into parent tree)
3463 if (!empty($rows)) {
3464 foreach ($rows as $key => $row) {
3465 $this->updateRangOfLine($row, ($key + 1));
3466 }
3467 }
3468 } else {
3469 dol_print_error($this->db);
3470 }
3471 }
3472 return 1;
3473 }
3474
3482 public function getChildrenOfLine($id, $includealltree = 0)
3483 {
3484 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3485 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3486 $fieldposition = 'position';
3487 }
3488
3489 $rows = array();
3490
3491 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3492 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3493 $sql .= ' AND fk_parent_line = '.((int) $id);
3494 $sql .= " ORDER BY " . $fieldposition . " ASC";
3495
3496 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3497
3498 $resql = $this->db->query($sql);
3499 if (!$resql || $this->db->num_rows($resql) <= 0) {
3500 return array();
3501 }
3502
3503 while ($row = $this->db->fetch_row($resql)) {
3504 $rows[] = $row[0];
3505 if (!empty($includealltree) && $includealltree <= 1000) { // Test <= 1000 is a protection in depth of recursive call to avoid infinite loop
3506 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree + 1));
3507 }
3508 }
3509 return $rows;
3510 }
3511
3512 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3520 public function line_up($rowid, $fk_parent_line = true)
3521 {
3522 // phpcs:enable
3523 $this->line_order(false, 'ASC', $fk_parent_line);
3524
3525 // Get rang of line
3526 $rang = $this->getRangOfLine($rowid);
3527
3528 // Update position of line
3529 $this->updateLineUp($rowid, $rang);
3530 }
3531
3532 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3540 public function line_down($rowid, $fk_parent_line = true)
3541 {
3542 // phpcs:enable
3543 $this->line_order(false, 'ASC', $fk_parent_line);
3544
3545 // Get rang of line
3546 $rang = $this->getRangOfLine($rowid);
3547
3548 // Get max value for rang
3549 $max = $this->line_max();
3550
3551 // Update position of line
3552 $this->updateLineDown($rowid, $rang, $max);
3553 }
3554
3562 public function updateRangOfLine($rowid, $rang)
3563 {
3564 global $hookmanager;
3565 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3566 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3567 $fieldposition = 'position';
3568 }
3569
3570 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3571 $sql .= ' WHERE rowid = '.((int) $rowid);
3572
3573 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3574 if (!$this->db->query($sql)) {
3575 dol_print_error($this->db);
3576 return -1;
3577 }
3578
3579 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3580 $action = '';
3581 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3582 return 1;
3583 }
3584
3585 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3592 public function line_ajaxorder($rows)
3593 {
3594 // phpcs:enable
3595 $num = count($rows);
3596 for ($i = 0; $i < $num; $i++) {
3597 $this->updateRangOfLine($rows[$i], ($i + 1));
3598 }
3599 }
3600
3608 public function updateLineUp($rowid, $rang)
3609 {
3610 if ($rang <= 1) {
3611 return -1;
3612 }
3613
3614 $fieldposition = 'rang';
3615 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3616 $fieldposition = 'position';
3617 }
3618
3619 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3620 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3621 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3622 if (!$this->db->query($sql)) {
3623 $this->error = $this->db->lasterror();
3624 return -1;
3625 }
3626
3627 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3628 $sql .= ' WHERE rowid = '.((int) $rowid);
3629 if (!$this->db->query($sql)) {
3630 $this->error = $this->db->lasterror();
3631 return -1;
3632 }
3633
3634 return 1;
3635 }
3636
3645 public function updateLineDown($rowid, $rang, $max)
3646 {
3647 if ($rang >= $max) {
3648 return -1;
3649 }
3650
3651 $fieldposition = 'rang';
3652 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3653 $fieldposition = 'position';
3654 }
3655
3656 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3657 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3658 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3659 if (!$this->db->query($sql)) {
3660 $this->error = $this->db->lasterror();
3661 return -1;
3662 }
3663
3664 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3665 $sql .= ' WHERE rowid = '.((int) $rowid);
3666 if (!$this->db->query($sql)) {
3667 $this->error = $this->db->lasterror();
3668 return -1;
3669 }
3670
3671 return 1;
3672 }
3673
3680 public function getRangOfLine($rowid)
3681 {
3682 $fieldposition = 'rang';
3683 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3684 $fieldposition = 'position';
3685 }
3686
3687 $sql = "SELECT " . $fieldposition . " FROM ".$this->db->prefix().$this->table_element_line;
3688 $sql .= " WHERE rowid = ".((int) $rowid);
3689
3690 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3691 $resql = $this->db->query($sql);
3692 if (!$resql) {
3693 return 0;
3694 }
3695
3696 $row = $this->db->fetch_row($resql);
3697 return $row[0];
3698 }
3699
3706 public function getIdOfLine($rang)
3707 {
3708 $fieldposition = 'rang';
3709 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3710 $fieldposition = 'position';
3711 }
3712
3713 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3714 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3715 $sql .= " AND " . $fieldposition . " = ".((int) $rang);
3716 $resql = $this->db->query($sql);
3717 if (!$resql) {
3718 return 0;
3719 }
3720
3721 $row = $this->db->fetch_row($resql);
3722 return $row[0];
3723 }
3724
3725 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3732 public function line_max($fk_parent_line = 0)
3733 {
3734 // phpcs:enable
3735 $positionfield = 'rang';
3736 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3737 $positionfield = 'position';
3738 }
3739
3740 $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3741 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3742
3743 // Search the last rang with fk_parent_line
3744 if ($fk_parent_line) {
3745 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3746 }
3747
3748 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3749 $resql = $this->db->query($sql);
3750 if ($resql) {
3751 $row = $this->db->fetch_row($resql);
3752
3753 if ($fk_parent_line) {
3754 if (!empty($row[0])) {
3755 return $row[0];
3756 } else {
3757 return $this->getRangOfLine($fk_parent_line);
3758 }
3759 } else {
3760 return $row[0];
3761 }
3762 }
3763
3764 return 0;
3765 }
3766
3767 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3774 public function update_ref_ext($ref_ext)
3775 {
3776 // phpcs:enable
3777 if (!$this->table_element) {
3778 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3779 return -1;
3780 }
3781
3782 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3783 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3784 // @phan-suppress-next-line PhanUndeclaredProperty
3785 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3786
3787 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3788 if ($this->db->query($sql)) {
3789 $this->ref_ext = $ref_ext;
3790 return 1;
3791 } else {
3792 $this->error = $this->db->error();
3793 return -1;
3794 }
3795 }
3796
3797 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3806 public function update_note($note, $suffix = '', $notrigger = 0)
3807 {
3808 // phpcs:enable
3809 global $user;
3810
3811 if (!$this->table_element) {
3812 $this->error = 'update_note was called on object with property table_element not defined';
3813 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3814 return -1;
3815 }
3816 if (!in_array($suffix, array('', '_public', '_private'))) {
3817 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3818 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3819 return -2;
3820 }
3821
3822 $newsuffix = $suffix;
3823
3824 // Special case
3825 if ($this->table_element == 'product' && $newsuffix == '_private') {
3826 $newsuffix = '';
3827 }
3828 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3829 $fieldusermod = "fk_user_mod";
3830 } elseif ($this->table_element == 'ecm_files') {
3831 $fieldusermod = "fk_user_m";
3832 } else {
3833 $fieldusermod = "fk_user_modif";
3834 }
3835 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3836 $sql .= " SET note".$this->db->sanitize($newsuffix)." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3837 $sql .= ", ".$this->db->sanitize($fieldusermod)." = ".((int) $user->id);
3838 $sql .= " WHERE rowid = ".((int) $this->id);
3839
3840 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3841 if ($this->db->query($sql)) {
3842 if ($suffix == '_public') {
3843 $this->note_public = $note;
3844 } elseif ($suffix == '_private') {
3845 $this->note_private = $note;
3846 } else {
3847 $this->note = $note; // deprecated
3848 $this->note_private = $note;
3849 }
3850 if (empty($notrigger)) {
3851 switch ($this->element) {
3852 case 'societe':
3853 $trigger_name = 'COMPANY_MODIFY';
3854 break;
3855 case 'commande':
3856 $trigger_name = 'ORDER_MODIFY';
3857 break;
3858 case 'facture':
3859 $trigger_name = 'BILL_MODIFY';
3860 break;
3861 case 'invoice_supplier':
3862 $trigger_name = 'BILL_SUPPLIER_MODIFY';
3863 break;
3864 case 'facturerec':
3865 $trigger_name = 'BILLREC_MODIFIY';
3866 break;
3867 case 'expensereport':
3868 $trigger_name = 'EXPENSE_REPORT_MODIFY';
3869 break;
3870 default:
3871 $trigger_name = strtoupper($this->element) . '_MODIFY';
3872 }
3873 $ret = $this->call_trigger($trigger_name, $user);
3874 if ($ret < 0) {
3875 return -1;
3876 }
3877 }
3878 return 1;
3879 } else {
3880 $this->error = $this->db->lasterror();
3881 return -1;
3882 }
3883 }
3884
3885 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3894 public function update_note_public($note)
3895 {
3896 // phpcs:enable
3897 return $this->update_note($note, '_public');
3898 }
3899
3900 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3911 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
3912 {
3913 // phpcs:enable
3914 global $conf, $hookmanager, $action;
3915
3916 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3917 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3918 if ($reshook > 0) {
3919 return 1; // replacement code
3920 } elseif ($reshook < 0) {
3921 return -1; // failure
3922 } // reshook = 0 => execute normal code
3923
3924 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3925 $isElementForSupplier = false;
3926 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
3927 $MODULE = "";
3928 if ($this->element == 'propal') {
3929 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3930 } elseif ($this->element == 'commande' || $this->element == 'order') {
3931 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3932 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
3933 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3934 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
3935 $isElementForSupplier = true;
3936 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3937 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3938 $isElementForSupplier = true;
3939 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3940 } elseif ($this->element == 'supplier_proposal') {
3941 $isElementForSupplier = true;
3942 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3943 }
3944 if ($isElementForSupplier) {
3945 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
3946 }
3947
3948 if (!empty($MODULE)) {
3949 if (getDolGlobalString($MODULE)) {
3950 $modsactivated = explode(',', getDolGlobalString($MODULE));
3951 foreach ($modsactivated as $mod) {
3952 if (isModEnabled($mod)) {
3953 return 1; // update was disabled by specific setup
3954 }
3955 }
3956 }
3957 }
3958
3959 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3960
3961 $forcedroundingmode = $roundingadjust;
3962 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
3963 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
3964 } elseif ($forcedroundingmode == 'auto') {
3965 $forcedroundingmode = '0';
3966 }
3967
3968 $error = 0;
3969
3970 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3971
3972 // Define constants to find lines to sum (field name int the table_element_line not into table_element)
3973 $fieldtva = 'total_tva';
3974 $fieldlocaltax1 = 'total_localtax1';
3975 $fieldlocaltax2 = 'total_localtax2';
3976 $fieldup = 'subprice';
3977 $base_price_type = 'HT';
3978 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3979 $fieldtva = 'tva';
3980 $fieldup = 'pu_ht';
3981 }
3982 if ($this->element == 'invoice_supplier_rec') {
3983 $fieldup = 'pu_ht';
3984 }
3985 if ($this->element == 'expensereport') {
3986 // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
3987 // 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
3988 // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
3989 $forcedroundingmode = '0';
3990 $fieldup = 'value_unit';
3991 $base_price_type = 'TTC';
3992 }
3993
3994 $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,";
3995 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
3996 if ($this->table_element_line == 'facturedet') {
3997 $sql .= ', situation_percent';
3998 }
3999 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4000 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
4001 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4002 if ($exclspec) {
4003 $product_field = 'product_type';
4004 if ($this->table_element_line == 'contratdet') {
4005 $product_field = ''; // contratdet table has no product_type field
4006 }
4007 if ($product_field) {
4008 $sql .= " AND ".$product_field." <> 9";
4009 }
4010 }
4011 $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
4012
4013 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4014
4015 $resql = $this->db->query($sql);
4016 if ($resql) {
4017 $this->total_ht = 0;
4018 $this->total_tva = 0;
4019 $this->total_localtax1 = 0;
4020 $this->total_localtax2 = 0;
4021 $this->total_ttc = 0;
4022 $total_ht_by_vats = array();
4023 $total_tva_by_vats = array();
4024 $total_ttc_by_vats = array();
4025 $this->multicurrency_total_ht = 0;
4026 $this->multicurrency_total_tva = 0;
4027 $this->multicurrency_total_ttc = 0;
4028
4029 $this->db->begin();
4030
4031 $num = $this->db->num_rows($resql);
4032 $i = 0;
4033 while ($i < $num) {
4034 $obj = $this->db->fetch_object($resql);
4035
4036 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
4037 $parameters = array('fk_element' => $obj->rowid);
4038 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4039
4040 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'
4041 // This part of code is to fix data. We should not call it too often.
4042 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4043 $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);
4044
4045 $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.
4046 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4047 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4048 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4049
4050 if ($diff_on_current_total) {
4051 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4052 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4053 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4054 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4055 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4056 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);
4057 $resqlfix = $this->db->query($sqlfix);
4058 if (!$resqlfix) {
4059 dol_print_error($this->db, 'Failed to update line');
4060 }
4061 $obj->total_tva = $tmpcal[1];
4062 $obj->total_ttc = $tmpcal[2];
4063 $obj->multicurrency_total_tva = $tmpcal[17];
4064 $obj->multicurrency_total_ttc = $tmpcal[18];
4065 } elseif ($diff_when_using_price_ht) {
4066 // 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
4067 // 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).
4068 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
4069 // 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,
4070 // 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.
4071
4072 // 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.
4073 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
4074 $sqlfix .= " SET ".$this->db->sanitize($fieldtva)." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
4075 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
4076 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
4077 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);
4078 $resqlfix = $this->db->query($sqlfix);
4079 if (!$resqlfix) {
4080 dol_print_error($this->db, 'Failed to update line');
4081 }
4082 $obj->total_tva = $tmpcal[1];
4083 $obj->total_ttc = $tmpcal[2];
4084 $obj->multicurrency_total_tva = $tmpcal[17];
4085 $obj->multicurrency_total_ttc = $tmpcal[18];
4086 }
4087 }
4088 }
4089
4090 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4091 $this->total_tva += $obj->total_tva;
4092 $this->total_localtax1 += $obj->total_localtax1;
4093 $this->total_localtax2 += $obj->total_localtax2;
4094 $this->total_ttc += $obj->total_ttc;
4095 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4096 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4097 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4098
4099 if (!isset($total_ht_by_vats[$obj->vatrate])) {
4100 $total_ht_by_vats[$obj->vatrate] = 0;
4101 }
4102 if (!isset($total_tva_by_vats[$obj->vatrate])) {
4103 $total_tva_by_vats[$obj->vatrate] = 0;
4104 }
4105 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4106 $total_ttc_by_vats[$obj->vatrate] = 0;
4107 }
4108 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4109 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4110 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4111
4112 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4113 if ($base_price_type == 'TTC') {
4114 $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4115 } else {
4116 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4117 }
4118 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
4119 //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";
4120 if ($diff) {
4121 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
4122 if (abs((float) $diff) > $maxdiff) {
4123 // If error is more than 10 times the accuracy of rounding. This should not happen.
4124 $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.';
4125 dol_syslog($errmsg, LOG_WARNING);
4126 $this->error = $errmsg;
4127 $error++;
4128 break;
4129 }
4130
4131 if ($base_price_type == 'TTC') {
4132 $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);
4133 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);
4134 } else {
4135 $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);
4136 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);
4137 }
4138
4139 $resqlfix = $this->db->query($sqlfix);
4140
4141 if (!$resqlfix) {
4142 dol_print_error($this->db, 'Failed to update line');
4143 }
4144
4145 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4146 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4147 if ($base_price_type == 'TTC') {
4148 $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4149 $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4150 } else {
4151 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4152 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4153 }
4154 }
4155 }
4156
4157 $i++;
4158 }
4159
4160 // Add revenue stamp to total
4161 // @phan-suppress-next-line PhanUndeclaredProperty
4162 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4163 // @phan-suppress-next-line PhanUndeclaredProperty
4164 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4165
4166 // Situations totals
4167 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
4168 '@phan-var-force Facture $this';
4169 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
4170 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4171 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4172 $prev_sits = $this->get_prev_sits();
4173
4174 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4175 $this->total_ht -= $sit->total_ht;
4176 $this->total_tva -= $sit->total_tva;
4177 $this->total_localtax1 -= $sit->total_localtax1;
4178 $this->total_localtax2 -= $sit->total_localtax2;
4179 $this->total_ttc -= $sit->total_ttc;
4180 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4181 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4182 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4183 }
4184 }
4185 }
4186 }
4187
4188 // Clean total
4189 $this->total_ht = (float) price2num($this->total_ht);
4190 $this->total_tva = (float) price2num($this->total_tva);
4191 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4192 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4193 $this->total_ttc = (float) price2num($this->total_ttc);
4194
4195 $this->db->free($resql);
4196
4197 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4198 $fieldht = 'total_ht';
4199 $fieldtva = 'tva';
4200 $fieldlocaltax1 = 'localtax1';
4201 $fieldlocaltax2 = 'localtax2';
4202 $fieldttc = 'total_ttc';
4203 // Specific code for backward compatibility with old field names
4204 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4205 $fieldtva = 'total_tva';
4206 }
4207
4208 if (!$error && empty($nodatabaseupdate)) {
4209 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4210 $sql .= " ".$this->db->sanitize($fieldht)." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4211 $sql .= " ".$this->db->sanitize($fieldtva)." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4212 $sql .= " ".$this->db->sanitize($fieldlocaltax1)." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4213 $sql .= " ".$this->db->sanitize($fieldlocaltax2)." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4214 $sql .= " ".$this->db->sanitize($fieldttc)." = ".((float) price2num($this->total_ttc, 'MT', 1));
4215 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4216 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4217 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4218 $sql .= " WHERE rowid = ".((int) $this->id);
4219
4220 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4221 $resql = $this->db->query($sql);
4222
4223 if (!$resql) {
4224 $error++;
4225 $this->error = $this->db->lasterror();
4226 $this->errors[] = $this->db->lasterror();
4227 }
4228 }
4229
4230 if (!$error) {
4231 $this->db->commit();
4232 return 1;
4233 } else {
4234 $this->db->rollback();
4235 return -1;
4236 }
4237 } else {
4238 dol_print_error($this->db, 'Bad request in update_price');
4239 return -1;
4240 }
4241 }
4242
4243 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4254 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4255 {
4256 // phpcs:enable
4257 global $user, $hookmanager, $action;
4258
4259 if (empty($this->origin_type) && !empty($this->origin)) {
4260 $this->origin_type = $this->origin;
4261 }
4262 $origin = (!empty($origin) ? $origin : $this->origin_type);
4263 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4264 $f_user = isset($f_user) ? $f_user : $user;
4265
4266 // Special case
4267 if ($origin == 'order') {
4268 $origin = 'commande';
4269 }
4270 if ($origin == 'invoice') {
4271 $origin = 'facture';
4272 }
4273 if ($origin == 'invoice_template') {
4274 $origin = 'facturerec';
4275 }
4276 if ($origin == 'supplierorder') {
4277 $origin = 'order_supplier';
4278 }
4279
4280 // Add module part to target type
4281 $targettype = $this->getElementType();
4282
4283 $parameters = array('targettype' => $targettype);
4284 // Hook for explicitly set the targettype if it must be different than $this->element
4285 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4286 if ($reshook > 0) {
4287 if (!empty($hookmanager->resArray['targettype'])) {
4288 $targettype = $hookmanager->resArray['targettype'];
4289 }
4290 }
4291
4292 $this->db->begin();
4293 $error = 0;
4294
4295 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4296 $sql .= "fk_source";
4297 $sql .= ", sourcetype";
4298 $sql .= ", fk_target";
4299 $sql .= ", targettype";
4300 $sql .= ") VALUES (";
4301 $sql .= ((int) $origin_id);
4302 $sql .= ", '" . $this->db->escape($origin) . "'";
4303 $sql .= ", " . ((int) $this->id);
4304 $sql .= ", '" . $this->db->escape($targettype) . "'";
4305 $sql .= ")";
4306
4307 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4308 if ($this->db->query($sql)) {
4309 if (!$notrigger) {
4310 // Call trigger
4311 $this->context['link_origin'] = $origin;
4312 $this->context['link_origin_id'] = $origin_id;
4313 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
4314 if ($result < 0) {
4315 $error++;
4316 }
4317 // End call triggers
4318 }
4319 } else {
4320 $this->error = $this->db->lasterror();
4321 $error++;
4322 }
4323
4324 if (!$error) {
4325 $this->db->commit();
4326 return 1;
4327 } else {
4328 $this->db->rollback();
4329 return 0;
4330 }
4331 }
4332
4338 public function getElementType()
4339 {
4340 // 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.
4341 // 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).
4342 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4343 // Add module part to target type if object has $module property and isn't in core modules.
4344 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4345 }
4346
4347
4370 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4371 {
4372 global $hookmanager, $action;
4373
4374 // Important for pdf generation time reduction
4375 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4376 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4377 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4378 return 1;
4379 }
4380
4381 $this->linkedObjectsIds = array();
4382 $this->linkedObjects = array();
4383
4384 $justsource = false;
4385 $justtarget = false;
4386 $withtargettype = false;
4387 $withsourcetype = false;
4388
4389 // Hook for explicitly set the targettype if it must be differtent than $this->element
4390 if (is_object($hookmanager)) {
4391 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4392 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4393 if ($reshook > 0) {
4394 if (!empty($hookmanager->resArray['sourcetype'])) {
4395 $sourcetype = $hookmanager->resArray['sourcetype'];
4396 }
4397 if (!empty($hookmanager->resArray['sourceid'])) {
4398 $sourceid = $hookmanager->resArray['sourceid'];
4399 }
4400 if (!empty($hookmanager->resArray['targettype'])) {
4401 $targettype = $hookmanager->resArray['targettype'];
4402 }
4403 if (!empty($hookmanager->resArray['targetid'])) {
4404 $targetid = $hookmanager->resArray['targetid'];
4405 }
4406 }
4407 }
4408
4409 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4410 $justsource = true; // the source (id and type) is a search criteria
4411 if (!empty($targettype)) {
4412 $withtargettype = true;
4413 }
4414 }
4415 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4416 $justtarget = true; // the target (id and type) is a search criteria
4417 if (!empty($sourcetype)) {
4418 $withsourcetype = true;
4419 }
4420 }
4421
4422 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4423 $targetid = (!empty($targetid) ? $targetid : $this->id);
4424 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4425 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4426
4427 /*if (empty($sourceid) && empty($targetid))
4428 {
4429 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4430 return -1;
4431 }*/
4432
4433 // Links between objects are stored in table element_element
4434 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4435 $sql .= " FROM ".$this->db->prefix()."element_element";
4436 $sql .= " WHERE ";
4437 if ($justsource || $justtarget) {
4438 if ($justsource) {
4439 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4440 if ($withtargettype) {
4441 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4442 }
4443 } elseif ($justtarget) {
4444 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4445 if ($withsourcetype) {
4446 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4447 }
4448 }
4449 } else {
4450 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4451 $sql .= " ".$this->db->sanitize($clause)." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4452 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4453 $this->linkedObjectsFullLoaded[$this->id] = true;
4454 }
4455 }
4456 $sql .= " ORDER BY ".$orderby;
4457
4458 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4459 $resql = $this->db->query($sql);
4460 if ($resql) {
4461 $num = $this->db->num_rows($resql);
4462 $i = 0;
4463 while ($i < $num) {
4464 $obj = $this->db->fetch_object($resql);
4465 if ($justsource || $justtarget) {
4466 if ($justsource) {
4467 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4468 } elseif ($justtarget) {
4469 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4470 }
4471 } else {
4472 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4473 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4474 }
4475 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4476 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4477 }
4478 }
4479 $i++;
4480 }
4481
4482 if (!empty($this->linkedObjectsIds)) {
4483 $tmparray = $this->linkedObjectsIds;
4484 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4485 $element_properties = getElementProperties($objecttype);
4486 $element = $element_properties['element'];
4487 $classPath = $element_properties['classpath'];
4488 $classFile = $element_properties['classfile'];
4489 $className = $element_properties['classname'];
4490 $module = $element_properties['module'];
4491
4492 // Here $module, $classFile and $className are set, we can use them.
4493 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4494 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4495 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4496 if (class_exists($className)) {
4497 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4498 $object = new $className($this->db);
4499 '@phan-var-force CommonObject $object';
4500 $ret = $object->fetch($objectid);
4501 if ($ret >= 0) {
4502 $this->linkedObjects[$objecttype][$i] = $object;
4503 }
4504 }
4505 }
4506 }
4507 } else {
4508 unset($this->linkedObjectsIds[$objecttype]);
4509 }
4510 }
4511 }
4512 return 1;
4513 } else {
4514 dol_print_error($this->db);
4515 return -1;
4516 }
4517 }
4518
4525 public function clearObjectLinkedCache()
4526 {
4527 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4528 unset($this->linkedObjectsFullLoaded[$this->id]);
4529 }
4530
4531 return 1;
4532 }
4533
4546 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4547 {
4548 global $user;
4549 $updatesource = false;
4550 $updatetarget = false;
4551 $f_user = isset($f_user) ? $f_user : $user;
4552
4553 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4554 $updatesource = true;
4555 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4556 $updatetarget = true;
4557 }
4558
4559 $this->db->begin();
4560 $error = 0;
4561
4562 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4563 if ($updatesource) {
4564 $sql .= "fk_source = " . ((int) $sourceid);
4565 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4566 $sql .= " WHERE fk_target = " . ((int) $this->id);
4567 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4568 } elseif ($updatetarget) {
4569 $sql .= "fk_target = " . ((int) $targetid);
4570 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4571 $sql .= " WHERE fk_source = " . ((int) $this->id);
4572 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4573 }
4574
4575 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4576 if ($this->db->query($sql)) {
4577 if (!$notrigger) {
4578 // Call trigger
4579 $this->context['link_source_id'] = $sourceid;
4580 $this->context['link_source_type'] = $sourcetype;
4581 $this->context['link_target_id'] = $targetid;
4582 $this->context['link_target_type'] = $targettype;
4583 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user);
4584 if ($result < 0) {
4585 $error++;
4586 }
4587 // End call triggers
4588 }
4589 } else {
4590 $this->error = $this->db->lasterror();
4591 $error++;
4592 }
4593
4594 if (!$error) {
4595 $this->db->commit();
4596 return 1;
4597 } else {
4598 $this->db->rollback();
4599 return -1;
4600 }
4601 }
4602
4616 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4617 {
4618 global $user;
4619 $deletesource = false;
4620 $deletetarget = false;
4621 $f_user = isset($f_user) ? $f_user : $user;
4622
4623 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4624 $deletesource = true;
4625 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4626 $deletetarget = true;
4627 }
4628
4629 $element = $this->getElementType();
4630 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4631 $sourcetype = (!empty($sourcetype) ? $sourcetype : $element);
4632 $targetid = (!empty($targetid) ? $targetid : $this->id);
4633 $targettype = (!empty($targettype) ? $targettype : $element);
4634 $this->db->begin();
4635 $error = 0;
4636
4637 if (!$notrigger) {
4638 // Call trigger
4639 $this->context['link_id'] = $rowid;
4640 $this->context['link_source_id'] = $sourceid;
4641 $this->context['link_source_type'] = $sourcetype;
4642 $this->context['link_target_id'] = $targetid;
4643 $this->context['link_target_type'] = $targettype;
4644 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
4645 if ($result < 0) {
4646 $error++;
4647 }
4648 // End call triggers
4649 }
4650
4651 if (!$error) {
4652 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4653 $sql .= " WHERE";
4654 if ($rowid > 0) {
4655 $sql .= " rowid = " . ((int) $rowid);
4656 } else {
4657 if ($deletesource) {
4658 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4659 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "'";
4660 } elseif ($deletetarget) {
4661 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4662 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "'";
4663 } else {
4664 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($element) . "')";
4665 $sql .= " OR";
4666 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($element) . "')";
4667 }
4668 }
4669
4670 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4671 if (!$this->db->query($sql)) {
4672 $this->error = $this->db->lasterror();
4673 $this->errors[] = $this->error;
4674 $error++;
4675 }
4676 }
4677
4678 if (!$error) {
4679 $this->db->commit();
4680 return 1;
4681 } else {
4682 $this->db->rollback();
4683 return 0;
4684 }
4685 }
4686
4696 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4697 {
4698 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4699 return -1;
4700 }
4701 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4702 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4703 }
4704
4705 global $db;
4706
4707 $sql = "SELECT ".$field_select." FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4708 $resql = $db->query($sql);
4709
4710 $TRes = array();
4711 if (!empty($resql)) {
4712 while ($res = $db->fetch_object($resql)) {
4713 $TRes[] = $res->{$field_select};
4714 }
4715 }
4716
4717 return $TRes;
4718 }
4719
4728 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4729 {
4730 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4731 return -1;
4732 }
4733
4734 global $db;
4735
4736 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4737 $resql = $db->query($sql);
4738 $n = 0;
4739 if ($resql) {
4740 $res = $db->fetch_object($resql);
4741 if ($res) {
4742 $n = $res->nb;
4743 }
4744 }
4745
4746 return $n;
4747 }
4748
4757 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4758 {
4759 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4760 return -1;
4761 }
4762
4763 global $db;
4764
4765 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4766 $resql = $db->query($sql);
4767
4768 if (empty($resql)) {
4769 return 0;
4770 }
4771
4772 return 1;
4773 }
4774
4785 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = 'fk_statut')
4786 {
4787 global $user;
4788
4789 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4790
4791 $elementId = (!empty($elementId) ? $elementId : $this->id);
4792 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4793
4794 $this->db->begin();
4795
4796 if ($elementTable == 'facture_rec') {
4797 $fieldstatus = "suspended";
4798 }
4799 if ($elementTable == 'mailing') {
4800 $fieldstatus = "statut";
4801 }
4802 if ($elementTable == 'cronjob') {
4803 $fieldstatus = "status";
4804 }
4805 if ($elementTable == 'user') {
4806 $fieldstatus = "statut";
4807 }
4808 if ($elementTable == 'expensereport') {
4809 $fieldstatus = "fk_statut";
4810 }
4811 if ($elementTable == 'receptiondet_batch') {
4812 $fieldstatus = "status";
4813 }
4814 if ($elementTable == 'prelevement_bons') {
4815 $fieldstatus = "statut";
4816 }
4817 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4818 $fieldstatus = 'status';
4819 }
4820
4821 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
4822 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
4823 // If status = 1 = validated, update also fk_user_valid
4824 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields
4825 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4826 $sql .= ", fk_user_valid = ".((int) $user->id);
4827 }
4828 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4829 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
4830 }
4831 if ($status == 1 && in_array($elementTable, array('inventory'))) {
4832 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
4833 }
4834 $sql .= " WHERE rowid = ".((int) $elementId);
4835 $sql .= " AND ".$fieldstatus." <> ".((int) $status); // We avoid update if status already correct
4836
4837 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4838 $resql = $this->db->query($sql);
4839 if ($resql) {
4840 $error = 0;
4841
4842 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
4843
4844 if ($nb_rows_affected > 0) {
4845 if (empty($trigkey)) {
4846 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
4847 if ($this->element == 'supplier_proposal' && $status == 2) {
4848 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4849 }
4850 if ($this->element == 'supplier_proposal' && $status == 3) {
4851 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4852 }
4853 if ($this->element == 'supplier_proposal' && $status == 4) {
4854 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4855 }
4856 if ($this->element == 'fichinter' && $status == 3) {
4857 $trigkey = 'FICHINTER_CLASSIFY_DONE';
4858 }
4859 if ($this->element == 'fichinter' && $status == 2) {
4860 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
4861 }
4862 if ($this->element == 'fichinter' && $status == 1) {
4863 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4864 }
4865 }
4866
4867 $this->context = array_merge($this->context, array('newstatus' => $status));
4868
4869 if ($trigkey) {
4870 $this->oldcopy = dol_clone($this);
4871
4872 // Call trigger
4873 $result = $this->call_trigger($trigkey, $user);
4874 if ($result < 0) {
4875 $error++;
4876 }
4877 // End call triggers
4878 }
4879 } else {
4880 // The status was probably already good. We do nothing more, no triggers.
4881 }
4882
4883 if (!$error) {
4884 $this->db->commit();
4885
4886 if (empty($savElementId)) {
4887 // If the element we update is $this (so $elementId was provided as null)
4888 if ($fieldstatus == 'tosell') {
4889 $this->status = $status;
4890 } elseif ($fieldstatus == 'tobuy') {
4891 $this->status_buy = $status; // @phpstan-ignore-line
4892 } elseif ($fieldstatus == 'tobatch') {
4893 $this->status_batch = $status; // @phpstan-ignore-line
4894 } else {
4895 $this->status = $status;
4896 }
4897 }
4898
4899 return 1;
4900 } else {
4901 $this->db->rollback();
4902 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
4903 return -1;
4904 }
4905 } else {
4906 $this->error = $this->db->lasterror();
4907 $this->db->rollback();
4908 return -1;
4909 }
4910 }
4911
4912
4920 public function getCanvas($id = 0, $ref = '')
4921 {
4922 if (empty($id) && empty($ref)) {
4923 return 0;
4924 }
4925 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
4926 return 0; // To increase speed. Not enabled by default.
4927 }
4928
4929 // Clean parameters
4930 $ref = trim($ref);
4931
4932 $sql = "SELECT rowid, canvas";
4933 $sql .= " FROM ".$this->db->prefix().$this->table_element;
4934 $sql .= " WHERE entity IN (".getEntity($this->element).")";
4935 if (!empty($id)) {
4936 $sql .= " AND rowid = ".((int) $id);
4937 }
4938 if (!empty($ref)) {
4939 $sql .= " AND ref = '".$this->db->escape($ref)."'";
4940 }
4941
4942 $resql = $this->db->query($sql);
4943 if ($resql) {
4944 $obj = $this->db->fetch_object($resql);
4945 if ($obj) {
4946 $this->canvas = $obj->canvas;
4947 return 1;
4948 } else {
4949 return 0;
4950 }
4951 } else {
4952 dol_print_error($this->db);
4953 return -1;
4954 }
4955 }
4956
4957
4964 public function getSpecialCode($lineid)
4965 {
4966 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->table_element_line;
4967 $sql .= " WHERE rowid = ".((int) $lineid);
4968 $resql = $this->db->query($sql);
4969 if ($resql) {
4970 $row = $this->db->fetch_row($resql);
4971 return (!empty($row[0]) ? $row[0] : 0);
4972 }
4973
4974 return 0;
4975 }
4976
4985 public function isObjectUsed($id = 0, $entity = 0)
4986 {
4987 global $langs;
4988
4989 if (empty($id)) {
4990 $id = $this->id;
4991 }
4992
4993 // Check parameters
4994 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
4995 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
4996 return -1;
4997 }
4998
4999 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
5000 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
5001 $tmparray = array_keys($this->childtables);
5002 if (is_numeric($tmparray[0])) {
5003 $arraytoscan = array_flip($this->childtables);
5004 }
5005
5006 // Test if child exists
5007 $haschild = 0;
5008 foreach ($arraytoscan as $table => $element) {
5009 //print $id.'-'.$table.'-'.$elementname.'<br>';
5010
5011 // Check if module is enabled (to avoid error if tables of module not created)
5012 if (isset($element['enabled']) && !empty($element['enabled'])) {
5013 $enabled = (int) dol_eval((string) $element['enabled'], 1);
5014 if (empty($enabled)) {
5015 continue;
5016 }
5017 }
5018
5019 // Check if element can be deleted
5020 $sql = "SELECT COUNT(*) as nb";
5021 $sql .= " FROM ".$this->db->prefix().$table." as c";
5022 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5023 $sql .= ", ".$this->db->prefix().$element['parent']." as p";
5024 }
5025 if (!empty($element['fk_element'])) {
5026 $sql .= " WHERE c.".$element['fk_element']." = ".((int) $id);
5027 } else {
5028 $sql .= " WHERE c.".$this->fk_element." = ".((int) $id);
5029 }
5030 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5031 $sql .= " AND c.".$element['parentkey']." = p.rowid";
5032 }
5033 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
5034 $sql .= " AND c.".$element['parenttypefield']." = '".$this->db->escape($element['parenttypevalue'])."'";
5035 }
5036 if (!empty($entity)) {
5037 if (!empty($element['parent']) && !empty($element['parentkey'])) {
5038 $sql .= " AND p.entity = ".((int) $entity);
5039 } else {
5040 $sql .= " AND c.entity = ".((int) $entity);
5041 }
5042 }
5043
5044 $resql = $this->db->query($sql);
5045 if ($resql) {
5046 $obj = $this->db->fetch_object($resql);
5047 if ($obj->nb > 0) {
5048 $langs->load("errors");
5049 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
5050 $haschild += $obj->nb;
5051 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
5052 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
5053 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
5054 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
5055 } else { // new usage: $element['name']=Translation key
5056 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
5057 }
5058 break; // We found at least one, we stop here
5059 }
5060 } else {
5061 $this->errors[] = $this->db->lasterror();
5062 return -1;
5063 }
5064 }
5065 if ($haschild > 0) {
5066 $this->errors[] = "ErrorRecordHasChildren";
5067 return $haschild;
5068 } else {
5069 return 0;
5070 }
5071 }
5072
5079 public function hasProductsOrServices($predefined = -1)
5080 {
5081 $nb = 0;
5082
5083 foreach ($this->lines as $key => $val) {
5084 $qualified = 0;
5085 if ($predefined == -1) {
5086 $qualified = 1;
5087 }
5088 if ($predefined == 1 && $val->fk_product > 0) {
5089 $qualified = 1;
5090 }
5091 if ($predefined == 0 && $val->fk_product <= 0) {
5092 $qualified = 1;
5093 }
5094 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5095 $qualified = 1;
5096 }
5097 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5098 $qualified = 1;
5099 }
5100 if ($qualified) {
5101 $nb++;
5102 }
5103 }
5104 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
5105 return $nb;
5106 }
5107
5113 public function getTotalDiscount()
5114 {
5115 if (!empty($this->table_element_line) && ($this->table_element_line != 'expeditiondet')) {
5116 $total_discount = 0.00;
5117
5118 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5119 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
5120 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
5121
5122 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
5123 $resql = $this->db->query($sql);
5124 if ($resql) {
5125 $num = $this->db->num_rows($resql);
5126 $i = 0;
5127 while ($i < $num) {
5128 $obj = $this->db->fetch_object($resql);
5129
5130 $pu_ht = $obj->pu_ht;
5131 $qty = $obj->qty;
5132 $total_ht = $obj->total_ht;
5133
5134 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
5135 $total_discount += $total_discount_line;
5136
5137 $i++;
5138 }
5139 }
5140
5141 //print $total_discount; exit;
5142 return (float) price2num($total_discount);
5143 }
5144
5145 return null;
5146 }
5147
5148
5155 public function getTotalWeightVolume()
5156 {
5157 $totalWeight = 0;
5158 $totalVolume = 0;
5159 // defined for shipment only
5160 $totalOrdered = 0;
5161 // defined for shipment only
5162 $totalToShip = 0;
5163
5164 if (empty($this->lines)) {
5165 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5166 }
5167
5168 foreach ($this->lines as $line) {
5169 if (isset($line->qty_asked)) {
5170 $totalOrdered += $line->qty_asked; // defined for shipment only
5171 }
5172 if (isset($line->qty_shipped)) {
5173 $totalToShip += $line->qty_shipped; // defined for shipment only
5174 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5175 if (empty($totalToShip)) {
5176 $totalToShip = 0;
5177 }
5178 $totalToShip += $line->qty; // defined for reception only
5179 }
5180
5181 // Define qty, weight, volume, weight_units, volume_units
5182 if ($this->element == 'shipping') {
5183 // for shipments
5184 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5185 } else {
5186 $qty = $line->qty ? $line->qty : 0;
5187 }
5188
5189 $weight = !empty($line->weight) ? $line->weight : 0;
5190 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5191 $volume = !empty($line->volume) ? $line->volume : 0;
5192 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5193
5194 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5195 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5196 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5197 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5198
5199 $weightUnit = 0;
5200 $volumeUnit = 0;
5201 if (!empty($weight_units)) {
5202 $weightUnit = $weight_units;
5203 }
5204 if (!empty($volume_units)) {
5205 $volumeUnit = $volume_units;
5206 }
5207
5208 if (empty($totalWeight)) {
5209 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5210 }
5211 if (empty($totalVolume)) {
5212 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5213 }
5214
5215 //var_dump($line->volume_units);
5216 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5217 $trueWeightUnit = pow(10, $weightUnit);
5218 $totalWeight += $weight * $qty * $trueWeightUnit;
5219 } else {
5220 if ($weight_units == 99) {
5221 // conversion 1 Pound = 0.45359237 KG
5222 $trueWeightUnit = 0.45359237;
5223 $totalWeight += $weight * $qty * $trueWeightUnit;
5224 } elseif ($weight_units == 98) {
5225 // conversion 1 Ounce = 0.0283495 KG
5226 $trueWeightUnit = 0.0283495;
5227 $totalWeight += $weight * $qty * $trueWeightUnit;
5228 } else {
5229 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5230 }
5231 }
5232 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5233 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5234 $trueVolumeUnit = pow(10, $volumeUnit);
5235 //print $line->volume;
5236 $totalVolume += $volume * $qty * $trueVolumeUnit;
5237 } else {
5238 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5239 }
5240 }
5241
5242 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5243 }
5244
5245
5251 public function setExtraParameters()
5252 {
5253 $this->db->begin();
5254
5255 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5256 $extraparams = dol_trunc($extraparams, 250);
5257
5258 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5259 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5260 $sql .= " WHERE rowid = ".((int) $this->id);
5261
5262 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5263 $resql = $this->db->query($sql);
5264 if (!$resql) {
5265 $this->error = $this->db->lasterror();
5266 $this->db->rollback();
5267 return -1;
5268 } else {
5269 $this->db->commit();
5270 return 1;
5271 }
5272 }
5273
5274
5275 // --------------------
5276 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5277 // --------------------
5278
5279 /* This is to show add lines */
5280
5290 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5291 {
5292 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5293
5294 // Line extrafield
5295 if (!is_object($extrafields)) {
5296 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5297 $extrafields = new ExtraFields($this->db);
5298 }
5299 $extrafields->fetch_name_optionals_label($this->table_element_line);
5300
5301 // Output template part (modules that overwrite templates must declare this into descriptor)
5302 // Use global variables + $dateSelector + $seller and $buyer
5303 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5304 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5305 foreach ($dirtpls as $module => $reldir) {
5306 if (!empty($module)) {
5307 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5308 } else {
5309 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5310 }
5311
5312 if (empty($conf->file->strict_mode)) {
5313 $res = @include $tpl;
5314 } else {
5315 $res = include $tpl; // for debug
5316 }
5317 if ($res) {
5318 break;
5319 }
5320 }
5321 }
5322
5323
5324
5325 /* This is to show array of line of details */
5326
5327
5342 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5343 {
5344 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5345 // TODO We should not use global var for this
5346 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5347
5348 // Define $usemargins (used by objectline_xxx.tpl.php files)
5349 $usemargins = 0;
5350 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5351 $usemargins = 1;
5352 }
5353
5354 $num = count($this->lines);
5355
5356 // Line extrafield
5357 if (!is_object($extrafields)) {
5358 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5359 $extrafields = new ExtraFields($this->db);
5360 }
5361 $extrafields->fetch_name_optionals_label($this->table_element_line);
5362
5363 if (method_exists($this, 'loadExpeditions')) {
5364 // 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.
5365 $this->loadExpeditions();
5366 }
5367
5368 $parameters = array();
5369 $reshook = $hookmanager->executeHooks('printObjectLinesBlock', $parameters, $this, $action);
5370 if (empty($reshook)) {
5371 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5372 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5373 if (empty($reshook)) {
5374 // Output template part (modules that overwrite templates must declare this into descriptor)
5375 // Use global variables + $dateSelector + $seller and $buyer
5376 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5377 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5378 foreach ($dirtpls as $module => $reldir) {
5379 $res = 0;
5380 if (!empty($module)) {
5381 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5382 } else {
5383 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5384 }
5385 if (file_exists($tpl)) {
5386 if (empty($conf->file->strict_mode)) {
5387 $res = @include $tpl;
5388 } else {
5389 $res = include $tpl; // for debug
5390 }
5391 }
5392 if ($res) {
5393 break;
5394 }
5395 }
5396 }
5397
5398 $i = 0;
5399
5400 print "<!-- begin printObjectLines() -->\n";
5401 foreach ($this->lines as $line) {
5402 // Line extrafield. TODO Remove this. extrafields should be already loaded.
5403 //$line->fetch_optionals();
5404
5405 if (is_object($hookmanager)) {
5406 if (empty($line->fk_parent_line)) {
5407 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5408 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5409 } else {
5410 $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);
5411 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5412 }
5413 }
5414 if (empty($reshook)) {
5415 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5416 }
5417
5418 $i++;
5419 }
5420 print "<!-- end printObjectLines() -->\n";
5421 }
5422 }
5423
5441 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5442 {
5443 global $conf, $langs, $user, $object, $hookmanager;
5444 global $form;
5445 global $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5446
5447 // var used into tpl
5448 $text = '';
5449 $description = '';
5450
5451 // Line in view mode
5452 if ($action != 'editline' || $selected != $line->id) {
5453 // Product
5454 if (!empty($line->fk_product) && $line->fk_product > 0) {
5455 $product_static = new Product($this->db);
5456 $product_static->fetch($line->fk_product);
5457
5458 $product_static->ref = $line->ref; //can change ref in hook
5459 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5460
5461 $text = $product_static->getNomUrl(1);
5462
5463 // Define output language and label
5464 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5465 // @phan-suppress-next-line PhanUndeclaredProperty
5466 if (property_exists($this, 'socid') && !empty($this->socid) && !is_object($this->thirdparty)) {
5467 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5468 return;
5469 }
5470
5471 $prod = new Product($this->db);
5472 $prod->fetch($line->fk_product);
5473
5474 $outputlangs = $langs;
5475 $newlang = '';
5476 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5477 $newlang = GETPOST('lang_id', 'aZ09');
5478 }
5479 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5480 $newlang = $this->thirdparty->default_lang; // To use language of customer
5481 }
5482 if (!empty($newlang)) {
5483 $outputlangs = new Translate("", $conf);
5484 $outputlangs->setDefaultLang($newlang);
5485 }
5486
5487 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5488 } else {
5489 $label = $line->product_label;
5490 }
5491
5492 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5493 $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.
5494 }
5495
5496 $line->pu_ttc = price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5497
5498 // Output template part (modules that overwrite templates must declare this into descriptor)
5499 // Use global variables + $dateSelector + $seller and $buyer
5500 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5501
5502 $qty_shipped = 0;
5503 if (isset($this->expeditions[$line->id])) {
5504 $qty_shipped = $this->expeditions[$line->id];
5505 }
5506 $disableedit = ($qty_shipped > 0) && ($qty_shipped >= $line->qty);
5507
5508 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5509 foreach ($dirtpls as $module => $reldir) {
5510 $res = 0;
5511 if (!empty($module)) {
5512 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5513 } else {
5514 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5515 }
5516 //var_dump($tpl);
5517 if (file_exists($tpl)) {
5518 if (empty($conf->file->strict_mode)) {
5519 $res = @include $tpl;
5520 } else {
5521 $res = include $tpl; // for debug
5522 }
5523 }
5524 if ($res) {
5525 break;
5526 }
5527 }
5528 }
5529
5530 // Line in update mode
5531 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5532 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5533
5534 $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5535
5536 // Output template part (modules that overwrite templates must declare this into descriptor)
5537 // Use global variables + $dateSelector + $seller and $buyer
5538 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5539 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5540 foreach ($dirtpls as $module => $reldir) {
5541 if (!empty($module)) {
5542 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5543 } else {
5544 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5545 }
5546
5547 if (empty($conf->file->strict_mode)) {
5548 $res = @include $tpl;
5549 } else {
5550 $res = include $tpl; // for debug
5551 }
5552 if ($res) {
5553 break;
5554 }
5555 }
5556 }
5557 }
5558
5559
5560 /* This is to show array of line of details of source object */
5561
5562
5573 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5574 {
5575 global $langs, $hookmanager, $form, $action;
5576
5577 print '<!-- printOriginLinesList '.get_class($this).' -->'."\n";
5578 print '<tr class="liste_titre">';
5579 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5580 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5581 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5582 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5583 if (isModEnabled("multicurrency")) {
5584 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5585 }
5586 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5587 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5588 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5589 }
5590 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5591 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5592 print '<td class="center">';
5593 print $form->showCheckAddButtons('checkforselect', 1);
5594 print '</td>';
5595 print '</tr>';
5596 $i = 0;
5597
5598 if (!empty($this->lines)) {
5599 foreach ($this->lines as $line) {
5600 $reshook = 0;
5601 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5602 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5603 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5604 if (!empty($line->fk_parent_line)) {
5605 $parameters['fk_parent_line'] = $line->fk_parent_line;
5606 }
5607 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5608 }
5609 if (empty($reshook)) {
5610 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5611 }
5612
5613 $i++;
5614 }
5615 }
5616 }
5617
5631 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5632 {
5633 global $langs, $conf;
5634
5635 //var_dump($line);
5636 if (!empty($line->date_start)) {
5637 // @phan-suppress-next-line PhanUndeclaredProperty
5638 $date_start = $line->date_start;
5639 } else {
5640 $date_start = $line->date_debut_prevue;
5641 if ($line->date_debut_reel) {
5642 $date_start = $line->date_debut_reel;
5643 }
5644 }
5645 if (!empty($line->date_end)) {
5646 // @phan-suppress-next-line PhanUndeclaredProperty
5647 $date_end = $line->date_end;
5648 } else {
5649 $date_end = $line->date_fin_prevue;
5650 if ($line->date_fin_reel) {
5651 $date_end = $line->date_fin_reel;
5652 }
5653 }
5654
5655 // Set thevalue into ->tpl[] array.
5656 $this->tpl['id'] = $line->id;
5657
5658 $this->tpl['label'] = '';
5659 if (!empty($line->fk_parent_line)) {
5660 $this->tpl['label'] .= img_picto('', 'rightarrow');
5661 }
5662
5663 if (($line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5664 $discount = new DiscountAbsolute($this->db);
5665 if (property_exists($this, 'socid')) {
5666 $discount->fk_soc = $this->socid;
5667 $discount->socid = $this->socid;
5668 }
5669 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5670 } elseif (!empty($line->fk_product)) {
5671 $productstatic = new Product($this->db);
5672 $productstatic->id = $line->fk_product;
5673 $productstatic->ref = $line->ref;
5674 $productstatic->type = $line->fk_product_type;
5675 if (empty($productstatic->ref)) {
5676 $line->fetch_product();
5677 $productstatic = $line->product;
5678 }
5679
5680 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5681 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5682 // Dates
5683 if ($line->product_type == 1 && ($date_start || $date_end)) {
5684 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5685 }
5686 } else {
5687 $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"')));
5688 if (!empty($line->desc)) {
5689 $this->tpl['label'] .= $line->desc;
5690 } else {
5691 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5692 }
5693
5694 // Dates
5695 if ($line->product_type == 1 && ($date_start || $date_end)) {
5696 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5697 }
5698 }
5699
5700 if (!empty($line->desc)) {
5701 '@phan-var-force OrderLine|FactureLigne|ContratLigne|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line';
5702 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5703 $discount = new DiscountAbsolute($this->db);
5704 $discount->fetch($line->fk_remise_except);
5705 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5706 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5707 $discount = new DiscountAbsolute($this->db);
5708 $discount->fetch($line->fk_remise_except);
5709 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5710 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5711 $discount = new DiscountAbsolute($this->db);
5712 $discount->fetch($line->fk_remise_except);
5713 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5714 } elseif ($line->desc == '(EXCESS PAID)') {
5715 $discount = new DiscountAbsolute($this->db);
5716 $discount->fetch($line->fk_remise_except);
5717 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5718 } else {
5719 $this->tpl['description'] = dol_trunc($line->desc, 60);
5720 }
5721 } else {
5722 $this->tpl['description'] = '&nbsp;';
5723 }
5724
5725 // VAT Rate
5726 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5727 $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
5728 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5729 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5730 }
5731
5732 $this->tpl['price'] = price($line->subprice);
5733 $this->tpl['total_ht'] = price($line->total_ht);
5734 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5735 $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5736 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5737 $this->tpl['unit'] = $line->getLabelOfUnit('long', $langs);
5738 $this->tpl['unit_short'] = $line->getLabelOfUnit('short', $langs);
5739 //$this->tpl['unit_code'] = $line->getLabelOfUnit('code');
5740 }
5741 $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate((string) $line->remise_percent, true) : '&nbsp;';
5742
5743 // Is the line strike or not
5744 $this->tpl['strike'] = 0;
5745 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5746 $this->tpl['strike'] = 1;
5747 } elseif ($line->special_code == SUBTOTALS_SPECIAL_CODE) {
5748 $this->tpl['strike'] = 1;
5749 }
5750
5751 // Output template part (modules that overwrite templates must declare this into descriptor)
5752 // Use global variables + $dateSelector + $seller and $buyer
5753 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5754 foreach ($dirtpls as $module => $reldir) {
5755 if (!empty($module)) {
5756 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5757 } else {
5758 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5759 }
5760
5761 if (empty($conf->file->strict_mode)) {
5762 $res = @include $tpl;
5763 } else {
5764 $res = include $tpl; // for debug
5765 }
5766 if ($res) {
5767 break;
5768 }
5769 }
5770 }
5771
5772
5773 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5784 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
5785 {
5786 // phpcs:enable
5787 $this->db->begin();
5788
5789 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5790 $sql .= "resource_id";
5791 $sql .= ", resource_type";
5792 $sql .= ", element_id";
5793 $sql .= ", element_type";
5794 $sql .= ", busy";
5795 $sql .= ", mandatory";
5796 $sql .= ") VALUES (";
5797 $sql .= ((int) $resource_id);
5798 $sql .= ", '".$this->db->escape($resource_type)."'";
5799 $sql .= ", '".$this->db->escape((string) $this->id)."'";
5800 $sql .= ", '".$this->db->escape($this->element)."'";
5801 $sql .= ", '".$this->db->escape((string) $busy)."'";
5802 $sql .= ", '".$this->db->escape((string) $mandatory)."'";
5803 $sql .= ")";
5804
5805 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5806 if ($this->db->query($sql)) {
5807 $this->db->commit();
5808 return 1;
5809 } else {
5810 $this->error = $this->db->lasterror();
5811 $this->db->rollback();
5812 return 0;
5813 }
5814 }
5815
5816 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5825 public function delete_resource($rowid, $element, $notrigger = 0)
5826 {
5827 // phpcs:enable
5828 global $user;
5829
5830 $this->db->begin();
5831
5832 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
5833 $sql .= " WHERE rowid = ".((int) $rowid);
5834
5835 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5836
5837 $resql = $this->db->query($sql);
5838 if (!$resql) {
5839 $this->error = $this->db->lasterror();
5840 $this->db->rollback();
5841 return -1;
5842 } else {
5843 if (!$notrigger) {
5844 $result = $this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
5845 if ($result < 0) {
5846 $this->db->rollback();
5847 return -1;
5848 }
5849 }
5850 $this->db->commit();
5851 return 1;
5852 }
5853 }
5854
5855
5861 public function __clone()
5862 {
5863 // Force a copy of this->lines, otherwise it will point to same object.
5864 if (isset($this->lines) && is_array($this->lines)) {
5865 $nboflines = count($this->lines);
5866 for ($i = 0; $i < $nboflines; $i++) {
5867 if (is_object($this->lines[$i])) {
5868 $this->lines[$i] = clone $this->lines[$i];
5869 }
5870 }
5871 }
5872 }
5873
5887 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5888 {
5889 global $conf, $langs, $hookmanager, $action;
5890
5891 $srctemplatepath = '';
5892
5893 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
5894 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5895
5896 if (!empty($reshook)) {
5897 return $reshook;
5898 }
5899
5900 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5901
5902 if (empty($modele)) {
5903 $this->error = 'BadValueForParameterModele';
5904 return -1;
5905 }
5906
5907 // Increase limit for PDF build
5908 $err = error_reporting();
5909 error_reporting(0);
5910 @set_time_limit(120);
5911 error_reporting($err);
5912
5913 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5914 $tmp = explode(':', $modele, 2);
5915 $saved_model = $modele;
5916 if (!empty($tmp[1])) {
5917 $modele = $tmp[0];
5918 $srctemplatepath = $tmp[1];
5919 }
5920
5921 // Search template files
5922 $file = '';
5923 $classname = '';
5924 $filefound = '';
5925 $dirmodels = array('/');
5926 if (is_array($conf->modules_parts['models'])) {
5927 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5928 }
5929 foreach ($dirmodels as $reldir) {
5930 foreach (array('doc', 'pdf') as $prefix) {
5931 if (in_array(get_class($this), array('Adherent'))) {
5932 // Member module use prefix_modele.class.php
5933 $file = $prefix."_".$modele.".class.php";
5934 } else {
5935 // Other module use prefix_modele.modules.php
5936 $file = $prefix."_".$modele.".modules.php";
5937 }
5938
5939 $file = dol_sanitizeFileName($file);
5940
5941 // We check if the file exists
5942 $file = dol_buildpath($reldir.$modelspath.$file, 0);
5943 if (file_exists($file)) {
5944 $filefound = $file;
5945 $classname = $prefix.'_'.$modele;
5946 break;
5947 }
5948 }
5949 if ($filefound) {
5950 break;
5951 }
5952 }
5953
5954 if ($filefound === '' || $classname === '') {
5955 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
5956 $this->errors[] = $this->error;
5957 dol_syslog($this->error, LOG_ERR);
5958 return -1;
5959 }
5960
5961 // Sanitize $filefound
5962 $filefound = dol_sanitizePathName($filefound);
5963
5964 // If generator was found
5965 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
5966
5967 require_once $filefound;
5968
5969 $obj = new $classname($this->db);
5970
5971 // TODO: Check the following classes that seem possible for $obj, but removed for compatibility:
5972 // ModeleBankAccountDoc|ModeleExpenseReport|ModelePDFBom|ModelePDFCommandes|ModelePDFContract|
5973 // ModelePDFDeliveryOrder|ModelePDFEvaluation|ModelePDFFactures|ModelePDFFicheinter|
5974 // ModelePDFMo|ModelePDFMovement|ModelePDFProduct|ModelePDFProjects|ModelePDFPropales|
5975 // ModelePDFRecruitmentJobPosition|ModelePDFSupplierProposal|ModelePDFSuppliersInvoices|
5976 // ModelePDFSuppliersOrders|ModelePDFSuppliersPayments|ModelePdfExpedition|ModelePdfReception|
5977 // ModelePDFStock|ModelePDFStockTransfer|
5978 // ModeleDon|ModelePDFTask|
5979 // ModelePDFAsset|ModelePDFTicket|ModelePDFUserGroup|ModeleThirdPartyDoc|ModelePDFUser
5980 // Has no write_file: ModeleBarCode|ModeleImports|ModeleExports|
5981 '@phan-var-force ModelePDFMember $obj';
5982 // '@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';
5983
5984 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
5985 if ($obj->type == 'odt' && empty($srctemplatepath)) {
5986 $varfortemplatedir = $obj->scandir;
5987 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
5988 $dirtoscan = getDolGlobalString($varfortemplatedir);
5989
5990 $listoffiles = array();
5991
5992 // Now we add first model found in directories scanned
5993 $listofdir = explode(',', $dirtoscan);
5994 foreach ($listofdir as $key => $tmpdir) {
5995 $tmpdir = trim($tmpdir);
5996 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
5997 if (!$tmpdir) {
5998 unset($listofdir[$key]);
5999 continue;
6000 }
6001 if (is_dir($tmpdir)) {
6002 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6003 if (count($tmpfiles)) {
6004 $listoffiles = array_merge($listoffiles, $tmpfiles);
6005 }
6006 }
6007 }
6008
6009 if (count($listoffiles)) {
6010 foreach ($listoffiles as $record) {
6011 $srctemplatepath = $record['fullname'];
6012 break;
6013 }
6014 }
6015 }
6016
6017 if (empty($srctemplatepath)) {
6018 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6019 return -1;
6020 }
6021 }
6022
6023 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6024 if (!dol_is_file($srctemplatepath)) {
6025 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
6026 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6027 return -1;
6028 }
6029 }
6030
6031 // We save charset_output to restore it because write_file can change it if needed for
6032 // output format that does not support UTF8.
6033 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
6034
6035 // update model_pdf in object
6036 $this->model_pdf = $saved_model;
6037
6038 if ($obj instanceof ModelePDFMember) {
6039 if ($this instanceof Adherent) {
6040 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards');
6041 } else {
6042 $resultwritefile = -1;
6043 dol_syslog("Error generating document - Provided ".get_class($this)." to ".get_class($obj)."::write_file()", LOG_ERR);
6044 }
6045 } elseif ($obj instanceof ModeleDon) {
6046 // Only 3 arguments
6047 if ($this instanceof Don) {
6048 $resultwritefile = $obj->write_file($this, $outputlangs /*, $currency */);
6049 } else {
6050 $resultwritefile = -1;
6051 dol_syslog("Error generating document - Provided ".get_class($this)." to Don::write_file()", LOG_ERR);
6052 }
6053 } else {
6054 // TODO: Try to set type above again
6055 '@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';
6056 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams); // @phan-suppress-line-PhanTypeMismatchArgument
6057 }
6058 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6059
6060 if ($resultwritefile > 0) {
6061 $outputlangs->charset_output = $sav_charset_output;
6062
6063 // We delete old preview
6064 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6065 dol_delete_preview($this);
6066
6067 // Index file in database
6068 if (!empty($obj->result['fullpath'])) {
6069 $destfull = $obj->result['fullpath'];
6070
6071 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
6072 $update_main_doc_field = 0;
6073 if (!empty($obj->update_main_doc_field)) {
6074 $update_main_doc_field = 1;
6075 }
6076
6077 // Check that the file exists, before indexing it.
6078 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
6079 if (dol_is_file($destfull)) {
6080 $this->indexFile($destfull, $update_main_doc_field);
6081 }
6082 } else {
6083 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);
6084 }
6085
6086 // Success in building document. We build meta file.
6087 dol_meta_create($this);
6088
6089 return 1;
6090 } else {
6091 $outputlangs->charset_output = $sav_charset_output;
6092 $this->error = $obj->error;
6093 $this->errors = $obj->errors;
6094 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
6095 return -1;
6096 }
6097 }
6098
6108 public function indexFile($destfull, $update_main_doc_field)
6109 {
6110 global $conf, $user;
6111
6112 $upload_dir = dirname($destfull);
6113 $destfile = basename($destfull);
6114 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
6115
6116 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
6117 $filename = basename($destfile);
6118 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6119 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6120
6121 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
6122 $ecmfile = new EcmFiles($this->db);
6123 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
6124
6125 // Set the public "share" key
6126 $setsharekey = false;
6127 if ($this->element == 'propal' || $this->element == 'proposal') {
6128 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
6129 $setsharekey = true; // feature to make online signature is not set or set to on (default)
6130 }
6131 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6132 $setsharekey = true;
6133 }
6134 }
6135 if ($this->element == 'commande' && getDolGlobalInt("ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
6136 $setsharekey = true;
6137 }
6138 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
6139 $setsharekey = true;
6140 }
6141 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
6142 $setsharekey = true;
6143 }
6144 if ($this->element == 'product' && getDolGlobalInt("PRODUCT_ALLOW_EXTERNAL_DOWNLOAD")) {
6145 $setsharekey = true;
6146 }
6147 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
6148 $setsharekey = true;
6149 }
6150 if ($this->element == 'fichinter' && getDolGlobalInt("FICHINTER_ALLOW_EXTERNAL_DOWNLOAD")) {
6151 $setsharekey = true;
6152 }
6153 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
6154 $setsharekey = true;
6155 }
6156 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
6157 $setsharekey = true;
6158 }
6159
6160 if ($setsharekey) {
6161 if (empty($ecmfile->share)) { // Because object not found or share not set yet
6162 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
6163 $ecmfile->share = getRandomPassword(true);
6164 }
6165 }
6166
6167 if ($result > 0) {
6168 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
6169 $ecmfile->fullpath_orig = '';
6170 $ecmfile->gen_or_uploaded = 'generated';
6171 $ecmfile->description = ''; // indexed content
6172 $ecmfile->keywords = ''; // keyword content
6173 $ecmfile->src_object_type = $this->table_element;
6174 $ecmfile->src_object_id = $this->id;
6175 $result = $ecmfile->update($user);
6176 if ($result < 0) {
6177 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6178 return -1;
6179 }
6180 } else {
6181 $ecmfile->entity = $conf->entity;
6182 $ecmfile->filepath = $rel_dir;
6183 $ecmfile->filename = $filename;
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; // $this->table_name is 'myobject' or 'mymodule_myobject'.
6190 $ecmfile->src_object_id = $this->id;
6191
6192 $result = $ecmfile->create($user);
6193 if ($result < 0) {
6194 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6195 return -1;
6196 }
6197 }
6198
6199 /*$this->result['fullname']=$destfull;
6200 $this->result['filepath']=$ecmfile->filepath;
6201 $this->result['filename']=$ecmfile->filename;*/
6202 //var_dump($obj->update_main_doc_field);exit;
6203
6204 if ($update_main_doc_field && !empty($this->table_element)) {
6205 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
6206 $sql .= " WHERE rowid = ".((int) $this->id);
6207
6208 $resql = $this->db->query($sql);
6209 if (!$resql) {
6210 dol_print_error($this->db);
6211 return -1;
6212 } else {
6213 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
6214 }
6215 }
6216 }
6217
6218 return 1;
6219 }
6220
6229 public function addThumbs($file, $quality = 50)
6230 {
6231 $file_osencoded = dol_osencode($file);
6232
6233 if (file_exists($file_osencoded)) {
6234 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6235
6236 $tmparraysize = getDefaultImageSizes();
6237 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6238 $maxheightsmall = $tmparraysize['maxheightsmall'];
6239 $maxwidthmini = $tmparraysize['maxwidthmini'];
6240 $maxheightmini = $tmparraysize['maxheightmini'];
6241 //$quality = $tmparraysize['quality'];
6242
6243 // Create small thumbs for company (Ratio is near 16/9)
6244 // Used on logon for example
6245 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6246
6247 // Create mini thumbs for company (Ratio is near 16/9)
6248 // Used on menu or for setup page for example
6249 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6250 }
6251 }
6252
6260 public function delThumbs($file)
6261 {
6262 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6263 dol_delete_file($imgThumbName);
6264 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6265 dol_delete_file($imgThumbName);
6266 }
6267
6268
6269 /* Functions common to commonobject and commonobjectline */
6270
6271 /* For default values */
6272
6286 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6287 {
6288 // If param here has been posted, we use this value first.
6289 if (GETPOSTISSET($fieldname)) {
6290 return GETPOST($fieldname, $type, 3);
6291 }
6292
6293 if (isset($alternatevalue)) {
6294 return $alternatevalue;
6295 }
6296
6297 $newelement = $this->element;
6298 if ($newelement == 'facture') {
6299 $newelement = 'invoice';
6300 }
6301 if ($newelement == 'commande') {
6302 $newelement = 'order';
6303 }
6304 if (empty($newelement)) {
6305 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6306 return '';
6307 }
6308
6309 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6310 //var_dump($keyforfieldname);
6311 if (getDolGlobalString($keyforfieldname)) {
6312 return getDolGlobalString($keyforfieldname);
6313 }
6314
6315 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6316 // store content into $conf->cache['overwrite_default']
6317
6318 return '';
6319 }
6320
6321
6322 /* For triggers */
6323
6324
6325 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6336 public function call_trigger($triggerName, $user)
6337 {
6338 // phpcs:enable
6339 global $langs, $conf;
6340
6341 if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
6342 dol_print_error(null, 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
6343 exit;
6344 }
6345 if (!is_object($langs)) { // If lang was not defined, we set it. It is required by run_triggers().
6346 dol_syslog("call_trigger was called with no langs variable defined".getCallerInfoString(), LOG_WARNING);
6347 include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
6348 $langs = new Translate('', $conf);
6349 $langs->load("main");
6350 }
6351
6352 include_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
6353 $interface = new Interfaces($this->db);
6354 $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
6355
6356 if ($result < 0) {
6357 if (!empty($this->errors)) {
6358 $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.
6359 } else {
6360 $this->errors = $interface->errors;
6361 }
6362 }
6363 return $result;
6364 }
6365
6366
6367 /* Functions for data in other language */
6368
6369
6379 {
6380 // To avoid SQL errors. Probably not the better solution though
6381 if (!$this->element) {
6382 return 0;
6383 }
6384 if (!($this->id > 0)) {
6385 return 0;
6386 }
6387 if (is_array($this->array_languages)) {
6388 return 1;
6389 }
6390
6391 $this->array_languages = array();
6392
6393 $element = $this->element;
6394 if ($element == 'categorie') {
6395 $element = 'categories'; // For compatibility
6396 }
6397
6398 // Request to get translation values for object
6399 $sql = "SELECT rowid, property, lang , value";
6400 $sql .= " FROM ".$this->db->prefix()."object_lang";
6401 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6402 $sql .= " AND fk_object = ".((int) $this->id);
6403
6404 $resql = $this->db->query($sql);
6405 if ($resql) {
6406 $numrows = $this->db->num_rows($resql);
6407 if ($numrows) {
6408 $i = 0;
6409 while ($i < $numrows) {
6410 $obj = $this->db->fetch_object($resql);
6411 $key = $obj->property;
6412 $value = $obj->value;
6413 $codelang = $obj->lang;
6414 $type = $this->fields[$key]['type'];
6415
6416 // we can add this attribute to object
6417 if (preg_match('/date/', $type)) {
6418 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6419 } else {
6420 $this->array_languages[$key][$codelang] = $value;
6421 }
6422
6423 $i++;
6424 }
6425 }
6426
6427 $this->db->free($resql);
6428
6429 if ($numrows) {
6430 return $numrows;
6431 } else {
6432 return 0;
6433 }
6434 } else {
6435 dol_print_error($this->db);
6436 return -1;
6437 }
6438 }
6439
6447 public function setValuesForExtraLanguages($onlykey = '')
6448 {
6449 // Get extra fields
6450 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6451 $tmparray = explode('-', $postfieldkey);
6452 if ($tmparray[0] != 'field') {
6453 continue;
6454 }
6455
6456 $element = $tmparray[1];
6457 $key = $tmparray[2];
6458 $codelang = $tmparray[3];
6459 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6460
6461 if (!empty($onlykey) && $key != $onlykey) {
6462 continue;
6463 }
6464 if ($element != $this->element) {
6465 continue;
6466 }
6467
6468 $key_type = $this->fields[$key]['type'];
6469
6470 $enabled = 1;
6471 if (isset($this->fields[$key]['enabled'])) {
6472 $enabled = (int) dol_eval((string) $this->fields[$key]['enabled'], 1, 1, '1');
6473 }
6474 /*$perms = 1;
6475 if (isset($this->fields[$key]['perms']))
6476 {
6477 $perms = (int) dol_eval((string) $this->fields[$key]['perms'], 1, 1, '1');
6478 }*/
6479 if (empty($enabled)) {
6480 continue;
6481 }
6482 //if (empty($perms)) continue;
6483
6484 if (in_array($key_type, array('date'))) {
6485 // Clean parameters
6486 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6487 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6488 } elseif (in_array($key_type, array('datetime'))) {
6489 // Clean parameters
6490 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6491 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6492 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6493 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6494 if (!empty($value_arr)) {
6495 $value_key = implode(',', $value_arr);
6496 } else {
6497 $value_key = '';
6498 }
6499 } elseif (in_array($key_type, array('price', 'double'))) {
6500 $value_arr = GETPOST($postfieldkey, 'alpha');
6501 $value_key = price2num($value_arr);
6502 } else {
6503 $value_key = GETPOST($postfieldkey);
6504 if (in_array($key_type, array('link')) && $value_key == '-1') {
6505 $value_key = '';
6506 }
6507 }
6508
6509 $this->array_languages[$key][$codelang] = $value_key;
6510
6511 /*if ($nofillrequired) {
6512 $langs->load('errors');
6513 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6514 return -1;
6515 }*/
6516 }
6517
6518 return 1;
6519 }
6520
6521
6522 /* Functions for extrafields */
6523
6530 public function fetchNoCompute($id)
6531 {
6532 global $conf;
6533
6534 $savDisableCompute = $conf->disable_compute;
6535 $conf->disable_compute = 1;
6536
6537 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6538
6539 $conf->disable_compute = $savDisableCompute;
6540
6541 return $ret;
6542 }
6543
6544 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6554 public function fetch_optionals($rowid = null, $optionsArray = null)
6555 {
6556 // phpcs:enable
6557 global $conf, $extrafields;
6558
6559 if (empty($rowid)) {
6560 $rowid = $this->id;
6561 }
6562 if (empty($rowid) && isset($this->rowid)) { // @phan-suppress-current-line PhanUndeclaredProperty
6563 $rowid = $this->rowid; // deprecated @phan-suppress-current-line PhanUndeclaredProperty
6564 }
6565
6566 // To avoid SQL errors. Probably not the better solution though
6567 if (!$this->table_element) {
6568 return 0;
6569 }
6570
6571 $this->array_options = array();
6572
6573 if (!is_array($optionsArray)) {
6574 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6575 if (!isset($extrafields) || !is_object($extrafields)) {
6576 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6577 $extrafields = new ExtraFields($this->db);
6578 }
6579
6580 // Load array of extrafields for elementype = $this->table_element
6581 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6582 $extrafields->fetch_name_optionals_label($this->table_element);
6583 }
6584 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6585 } else {
6586 global $extrafields;
6587 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6588 }
6589
6590 $table_element = $this->table_element;
6591 if ($table_element == 'categorie') {
6592 $table_element = 'categories'; // For compatibility
6593 }
6594
6595 // Request to get complementary values
6596 if (is_array($optionsArray) && count($optionsArray) > 0) {
6597 $sql = "SELECT rowid";
6598 foreach ($optionsArray as $name => $label) {
6599 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6600 $sql .= ", ".$this->db->sanitize($name);
6601 }
6602 // use geo sql fonction to read as text
6603 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || in_array($extrafields->attributes[$this->table_element]['type'][$name], array('point', 'multipts', 'linestrg', 'polygon'))) {
6604 // TODO Add an abstraction method in the database driver
6605 $sql .= ", ST_AsWKT(".$name.") as ".$this->db->sanitize($name);
6606 }
6607 }
6608 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6609 $sql .= " WHERE fk_object = ".((int) $rowid);
6610
6611 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6612 $resql = $this->db->query($sql);
6613 if ($resql) {
6614 $numrows = $this->db->num_rows($resql);
6615 if ($numrows) {
6616 $tab = $this->db->fetch_array($resql);
6617
6618 foreach ($tab as $key => $value) {
6619 // 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)
6620 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6621 // we can add this attribute to object
6622 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6623 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6624 $this->array_options["options_".$key] = $this->db->jdate($value);
6625 } else {
6626 $this->array_options["options_".$key] = $value;
6627 }
6628
6629 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6630 }
6631 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6632 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6633 $this->array_options["options_".$key] = dolDecrypt($value);
6634 }
6635 }
6636 }
6637 } else {
6642 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6643 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6644 $this->array_options['options_' . $key] = null;
6645 }
6646 }
6647 }
6648
6649 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6650 // in the element's extrafields table)
6651 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6652 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6653 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6654 //var_dump($conf->disable_compute);
6655 if (empty($conf->disable_compute)) {
6656 global $objectoffield; // We set a global variable to $objectoffield so
6657 $objectoffield = $this; // we can use it inside computed formula
6658 $this->array_options['options_' . $key] = dol_eval((string) $extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6659 }
6660 }
6661 }
6662 }
6663
6664 $this->db->free($resql);
6665
6666 if ($numrows) {
6667 return $numrows;
6668 } else {
6669 return 0;
6670 }
6671 } else {
6672 $this->errors[] = $this->db->lasterror;
6673 return -1;
6674 }
6675 }
6676 return 0;
6677 }
6678
6685 public function deleteExtraFields()
6686 {
6687 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6688 return 0;
6689 }
6690
6691 $this->db->begin();
6692
6693 $table_element = $this->table_element;
6694 if ($table_element == 'categorie') {
6695 $table_element = 'categories'; // For compatibility
6696 }
6697
6698 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6699
6700 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6701
6702 $resql = $this->db->query($sql_del);
6703 if (!$resql) {
6704 $this->error = $this->db->lasterror();
6705 $this->db->rollback();
6706 return -1;
6707 } else {
6708 $this->db->commit();
6709 return 1;
6710 }
6711 }
6712
6723 public function insertExtraFields($trigger = '', $userused = null)
6724 {
6725 global $langs, $user;
6726
6727 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') || empty($this->array_options)) {
6728 return 0;
6729 }
6730
6731 if (empty($userused)) {
6732 $userused = $user;
6733 }
6734
6735 $error = 0;
6736
6737 // Check parameters
6738 $langs->load('admin');
6739 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6740 $extrafields = new ExtraFields($this->db);
6741 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6742
6743 // Eliminate copied source object extra fields that do not exist in target object
6744 $new_array_options = array();
6745 foreach ($this->array_options as $key => $value) {
6746 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6747 $new_array_options[$key] = $value;
6748 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6749 $new_array_options['options_'.$key] = $value;
6750 }
6751 }
6752
6753 foreach ($new_array_options as $key => $value) {
6754 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6755 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6756 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6757 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6758 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6759 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6760 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6761
6762 // If we clone, we have to clean unique extrafields to prevent duplicates.
6763 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6764 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && !empty($attributeUnique)) {
6765 $new_array_options[$key] = null;
6766 }
6767
6768 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6769 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6770 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6771 $new_array_options[$key] = null;
6772 }
6773
6774 // Similar code than into insertExtraFields
6775 if ($attributeRequired) {
6776 $v = $this->array_options[$key];
6777 if (ExtraFields::isEmptyValue($v, $attributeType)) {
6778 $langs->load("errors");
6779 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6780 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6781 return -1;
6782 }
6783 }
6784
6785 if (!empty($attrfieldcomputed)) {
6786 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6787 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
6788 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
6789 $new_array_options[$key] = $value;
6790 } else {
6791 $new_array_options[$key] = null;
6792 }
6793 }
6794
6795 switch ($attributeType) {
6796 case 'int':
6797 case 'duration':
6798 case 'stars':
6799 if (!is_numeric($value) && $value != '') {
6800 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6801 return -1;
6802 } elseif ($value === '') {
6803 $new_array_options[$key] = null;
6804 }
6805 break;
6806 case 'boolean':
6807 if ($value === '' || $value === false || $value === null) {
6808 $new_array_options[$key] = null;
6809 }
6810 break;
6811 case 'price':
6812 case 'double':
6813 $value = price2num($value);
6814 if (!is_numeric($value) && $value != '') {
6815 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6816 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6817 return -1;
6818 } elseif ($value == '') {
6819 $value = null;
6820 }
6821 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6822 $new_array_options[$key] = $value;
6823 break;
6824 /*case 'select': // Not required, we chose value='0' for undefined values
6825 if ($value=='-1')
6826 {
6827 $this->array_options[$key] = null;
6828 }
6829 break;*/
6830 case 'password':
6831 $algo = '';
6832 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6833 // If there is an encryption choice, we use it to encrypt data before insert
6834 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6835 $algo = reset($tmparrays);
6836 if ($algo != '') {
6837 //global $action; // $action may be 'create', 'update', 'update_extras'...
6838 //var_dump($action);
6839 //var_dump($this->oldcopy);exit;
6840 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
6841 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6842 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6843 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6844 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6845 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6846 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6847 } else {
6848 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6849 }
6850 } else {
6851 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6852 }
6853 } else {
6854 // If value has changed
6855 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6856 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6857 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6858 } else {
6859 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6860 }
6861 } else {
6862 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
6863 }
6864 }
6865 } else {
6866 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6867 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6868 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
6869 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6870 } else {
6871 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6872 }
6873 }
6874 } else {
6875 // No encryption
6876 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6877 }
6878 } else { // Common usage
6879 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6880 }
6881 break;
6882 case 'date':
6883 case 'datetime':
6884 // If data is a string instead of a timestamp, we convert it
6885 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6886 $this->array_options[$key] = strtotime($this->array_options[$key]);
6887 }
6888 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6889 break;
6890 case 'datetimegmt':
6891 // If data is a string instead of a timestamp, we convert it
6892 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6893 $this->array_options[$key] = strtotime($this->array_options[$key]);
6894 }
6895 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
6896 break;
6897 case 'link':
6898 $param_list = array_keys($attributeParam['options']);
6899 // 0 : ObjectName
6900 // 1 : classPath
6901 $InfoFieldList = explode(":", $param_list[0]);
6902 dol_include_once($InfoFieldList[1]);
6903 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
6904 if ($value == '-1') { // -1 is key for no defined in combo list of objects
6905 $new_array_options[$key] = '';
6906 } elseif ($value) {
6907 $object = new $InfoFieldList[0]($this->db);
6908 '@phan-var-force CommonObject $object';
6909
6910 $objectId = 0;
6911
6912 $sqlFetchObject = "SELECT rowid FROM ".$this->db->prefix().$object->table_element;
6913 if (is_numeric($value)) {
6914 $sqlFetchObject .= " WHERE rowid = " . (int) $value;
6915 } else {
6916 $sqlFetchObject .= " WHERE ref = '" . $this->db->escape($value) . "'";
6917 }
6918
6919 $obj = $this->db->getRow($sqlFetchObject);
6920
6921 if ($obj !== false) {
6922 $objectId = $obj->rowid;
6923 $res = 1;
6924 } else {
6925 $res = -1;
6926 }
6927
6928 if ($res > 0) {
6929 $new_array_options[$key] = $objectId;
6930 } else {
6931 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6932 return -1;
6933 }
6934 }
6935 } else {
6936 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6937 }
6938 break;
6939 case 'checkbox':
6940 case 'chkbxlst':
6941 if (is_array($this->array_options[$key])) {
6942 $new_array_options[$key] = implode(',', $this->array_options[$key]);
6943 } else {
6944 $new_array_options[$key] = $this->array_options[$key];
6945 }
6946 break;
6947 }
6948 }
6949
6950 $sqlColumnValues = ['fk_object' => (int) $this->id]; // key-value pairs for the SQL INSERT or UPDATE query
6951
6952 foreach ($new_array_options as $key => $newValue) {
6953 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6954 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6955 if ($attributeType === 'separate') {
6956 // separator extrafields don't have data per object so they don't have a comlumn in the database
6957 continue;
6958 }
6959 $geoDataType = ExtraFields::$geoDataTypes[$attributeType] ?? null;
6960 // Add field of attribute
6961 if (! $geoDataType) {
6962 // not a geodata type
6963 if ($newValue != '') {
6964 $sqlColumnValues[$attributeKey] = "'".$this->db->escape($newValue)."'";
6965 } else {
6966 $sqlColumnValues[$attributeKey] = 'null';
6967 }
6968 continue;
6969 }
6970 if (empty($newValue)) {
6971 $sqlColumnValues[$attributeKey] = 'null';
6972 continue;
6973 }
6974 if (preg_match('/error/i', $newValue)) {
6975 dol_syslog(
6976 'Bad syntax string for '.$geoDataType['shortname'].' '.$newValue.' to generate SQL request',
6977 LOG_WARNING
6978 );
6979 $sqlColumnValues[$attributeKey] = 'null';
6980 continue;
6981 }
6982
6983 // Geodata type: Text must be a WKT string. Examples:
6984 // - point => "POINT(15 20)"
6985 // - multipts => "MULTIPOINT(0 0, 20 20, 60 60)"
6986 // - linestrg => "LINESTRING(0 0, 10 10, 20 25, 50 60)"
6987 // - polygon => "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))"
6988 $sqlColumnValues[$key] = $geoDataType['ST_Function']."('".$this->db->escape($newValue)."')";
6989 }
6990
6991 $this->db->begin();
6992
6993 $table_element = $this->table_element;
6994 if ($table_element == 'categorie') {
6995 $table_element = 'categories'; // For compatibility
6996 }
6997 $extrafieldsTable = $this->db->prefix() . $table_element . '_extrafields';
6998
6999 dol_syslog(get_class($this)."::insertExtraFields delete then insert", LOG_DEBUG);
7000
7001 // if the extrafields row already exists for the object, we update it
7002 if ($this->db->getRow("SELECT 1 FROM {$extrafieldsTable} WHERE fk_object = ".((int) $this->id))) {
7003 array_shift($sqlColumnValues); // drop the 'fk_object' column because its value won't change
7004 if (empty($sqlColumnValues)) {
7005 // No column of the target element to update. This happens when the object was created from
7006 // another element whose extrafields do not exist on this element (e.g. invoice created from a
7007 // shipment: the source line extrafields are copied into array_options but none match facturedet).
7008 // The existing row is already correct, so there is nothing to update: avoid an empty "SET" clause
7009 // that would produce an invalid SQL statement.
7010 $this->db->commit();
7011 return 1;
7012 }
7013 $sqlColumnValueString = implode(
7014 ',',
7019 array_map(function ($key) use ($sqlColumnValues) {
7020 return "{$key} = {$sqlColumnValues[$key]}";
7021 }, array_keys($sqlColumnValues))
7022 );
7023 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7024 } else {
7025 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7026 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7027 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7028 if (isset($sqlColumnValues[$key])) {
7029 // extrafield value already provided: no need to add it
7030 continue;
7031 }
7032 // default value: empty string, except for 'int', 'double' and 'price'
7033 $sqlColumnValues[$key] = "''";
7034 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7035 $sqlColumnValues[$key] = 0;
7036 }
7037 }
7038 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7039 $sqlValues = implode(',', array_values($sqlColumnValues));
7040 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7041 }
7042
7043 $resql = $this->db->query($sql);
7044 if (!$resql) {
7045 $this->error = $this->db->lasterror();
7046 $error++;
7047 }
7048
7049 if (!$error && $trigger) {
7050 // Call trigger
7051 $this->context = array('extrafieldaddupdate' => 1);
7052 $result = $this->call_trigger($trigger, $userused);
7053 if ($result < 0) {
7054 $error++;
7055 }
7056 // End call trigger
7057 }
7058
7059 if ($error) {
7060 $this->db->rollback();
7061 return -1;
7062 } else {
7063 $this->db->commit();
7064 return 1;
7065 }
7066 }
7067
7079 public function insertExtraLanguages($trigger = '', $userused = null)
7080 {
7081 global $langs, $user;
7082
7083 if (empty($userused)) {
7084 $userused = $user;
7085 }
7086
7087 $error = 0;
7088
7089 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7090 return 0; // For avoid conflicts if trigger used
7091 }
7092
7093 if (is_array($this->array_languages)) {
7094 $new_array_languages = $this->array_languages;
7095
7096 foreach ($new_array_languages as $key => $value) {
7097 $attributeKey = $key;
7098 $attributeType = $this->fields[$attributeKey]['type'];
7099 $attributeLabel = $this->fields[$attributeKey]['label'];
7100
7101 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7102 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7103
7104 switch ($attributeType) {
7105 case 'int':
7106 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7107 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7108 return -1;
7109 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7110 $new_array_languages[$key] = null;
7111 }
7112 break;
7113 case 'double':
7114 $value = price2num((string) $value);
7115 if (!is_numeric($value) && $value != '') {
7116 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7117 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7118 return -1;
7119 } elseif ($value == '') {
7120 $new_array_languages[$key] = null;
7121 } else {
7122 $new_array_languages[$key] = $value;
7123 }
7124 break;
7125 /*case 'select': // Not required, we chose value='0' for undefined values
7126 if ($value=='-1')
7127 {
7128 $this->array_options[$key] = null;
7129 }
7130 break;*/
7131 }
7132 }
7133
7134 $this->db->begin();
7135
7136 $table_element = $this->table_element;
7137 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7138 $table_element = 'categories'; // For compatibility
7139 }
7140
7141 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7142
7143 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7144 foreach ($langcodearray as $langcode => $value) {
7145 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7146 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7147 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7148 $this->db->query($sql_del);
7149
7150 if ($value !== '') {
7151 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7152 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7153 $sql .= ")";
7154
7155 $resql = $this->db->query($sql);
7156 if (!$resql) {
7157 $this->error = $this->db->lasterror();
7158 $error++;
7159 break;
7160 }
7161 }
7162 }
7163 }
7164
7165 if (!$error && $trigger) {
7166 // Call trigger
7167 $this->context = array('extralanguagesaddupdate' => 1);
7168 $result = $this->call_trigger($trigger, $userused);
7169 if ($result < 0) {
7170 $error++;
7171 }
7172 // End call trigger
7173 }
7174
7175 if ($error) {
7176 $this->db->rollback();
7177 return -1;
7178 } else {
7179 $this->db->commit();
7180 return 1;
7181 }
7182 } else {
7183 return 0;
7184 }
7185 }
7186
7197 public function updateExtraField($key, $trigger = null, $userused = null)
7198 {
7199 global $langs, $user, $hookmanager;
7200
7201 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7202 return 0;
7203 }
7204
7205 if (empty($userused)) {
7206 $userused = $user;
7207 }
7208
7209 $error = 0;
7210
7211 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7212 // Check parameters
7213 $langs->load('admin');
7214 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7215 $extrafields = new ExtraFields($this->db);
7216 $extrafields->fetch_name_optionals_label($this->table_element);
7217 if (!isset($extrafields->attributes[$this->table_element]['type'][$key])) {
7218 // Extrafield not defined for this object type: nothing to update
7219 return 0;
7220 }
7221 $value = $this->array_options["options_".$key];
7222
7223 $attributeKey = $key;
7224 $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
7225 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
7226 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
7227 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
7228 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7229 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
7230
7231 // Similar code than into insertExtraFields
7232 if ($attributeRequired) {
7233 $mandatorypb = false;
7234 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7235 $mandatorypb = true;
7236 }
7237 if ($this->array_options["options_".$key] === '') {
7238 $mandatorypb = true;
7239 }
7240 if ($mandatorypb) {
7241 $langs->load("errors");
7242 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7243 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7244 return -1;
7245 }
7246 }
7247
7248 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7249 $new_array_options = $this->array_options;
7250
7251 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7252 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7253 if (!empty($attrfieldcomputed)) {
7254 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7255 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7256 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7257
7258 $new_array_options["options_".$key] = $value;
7259
7260 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7261 } else {
7262 $new_array_options["options_".$key] = null;
7263
7264 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7265 }
7266 }
7267
7268 switch ($attributeType) {
7269 case 'int':
7270 if (!is_numeric($value) && $value != '') {
7271 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7272 return -1;
7273 } elseif ($value === '') {
7274 $new_array_options["options_".$key] = null;
7275
7276 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7277 }
7278 break;
7279 case 'price':
7280 case 'double':
7281 $value = price2num($value);
7282 if (!is_numeric($value) && $value != '') {
7283 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7284 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7285 return -1;
7286 } elseif ($value === '') {
7287 $value = null;
7288 }
7289 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7290 $new_array_options["options_".$key] = $value;
7291
7292 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7293 break;
7294 /*case 'select': // Not required, we chose value='0' for undefined values
7295 if ($value=='-1')
7296 {
7297 $new_array_options["options_".$key] = $value;
7298
7299 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7300 }
7301 break;*/
7302 case 'password':
7303 $algo = '';
7304 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7305 // If there is an encryption choice, we use it to encrypt data before insert
7306 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7307 $algo = reset($tmparrays);
7308 if ($algo != '') {
7309 //global $action; // $action may be 'create', 'update', 'update_extras'...
7310 //var_dump($action);
7311 //var_dump($this->oldcopy);exit;
7312 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7313 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
7314 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7315 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.
7316 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7317 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7318 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7319 } else {
7320 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7321 }
7322 } else {
7323 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7324 }
7325 } else {
7326 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7327 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7328 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7329 } else {
7330 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7331 }
7332 } else {
7333 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7334 }
7335 }
7336 } else {
7337 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
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 }
7343 } else {
7344 // No encryption
7345 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7346 }
7347 } else { // Common usage
7348 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7349 }
7350
7351 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7352 break;
7353 case 'date':
7354 case 'datetime':
7355 if (empty($this->array_options["options_".$key])) {
7356 $new_array_options["options_".$key] = null;
7357
7358 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7359 } else {
7360 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7361 }
7362 break;
7363 case 'datetimegmt':
7364 if (empty($this->array_options["options_".$key])) {
7365 $new_array_options["options_".$key] = null;
7366
7367 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7368 } else {
7369 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7370 }
7371 break;
7372 case 'boolean':
7373 if (empty($this->array_options["options_".$key])) {
7374 $new_array_options["options_".$key] = null;
7375
7376 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7377 }
7378 break;
7379 case 'link':
7380 if ($this->array_options["options_".$key] === '') {
7381 $new_array_options["options_".$key] = null;
7382
7383 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7384 }
7385 break;
7386 /*
7387 case 'link':
7388 $param_list = array_keys($attributeParam['options']);
7389 // 0 : ObjectName
7390 // 1 : classPath
7391 $InfoFieldList = explode(":", $param_list[0]);
7392 dol_include_once($InfoFieldList[1]);
7393 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7394 {
7395 if ($value == '-1') // -1 is key for no defined in combo list of objects
7396 {
7397 $new_array_options[$key] = '';
7398 } elseif ($value) {
7399 $object = new $InfoFieldList[0]($this->db);
7400 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7401 else $res = $object->fetch(0, $value); // For compatibility
7402
7403 if ($res > 0) $new_array_options[$key] = $object->id;
7404 else {
7405 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7406 $this->db->rollback();
7407 return -1;
7408 }
7409 }
7410 } else {
7411 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7412 }
7413 break;
7414 */
7415 case 'checkbox':
7416 case 'chkbxlst':
7417 $new_array_options = array();
7418 if (is_array($this->array_options["options_".$key])) {
7419 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7420 } else {
7421 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7422 }
7423
7424 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7425 break;
7426 }
7427
7428 $this->db->begin();
7429
7430 $linealreadyfound = 0;
7431
7432 // 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)
7433 $table_element = $this->table_element;
7434 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7435 $table_element = 'categories'; // For compatibility
7436 }
7437
7438 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7439 $resql = $this->db->query($sql);
7440 if ($resql) {
7441 $tmpobj = $this->db->fetch_object($resql);
7442 if ($tmpobj) {
7443 $linealreadyfound = $tmpobj->nb;
7444 }
7445 }
7446
7447 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7448 if ($linealreadyfound) {
7449 if ($this->array_options["options_".$key] === null) {
7450 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
7451 } else {
7452 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7453 }
7454 $sql .= " WHERE fk_object = ".((int) $this->id);
7455
7456 $resql = $this->db->query($sql);
7457 if (!$resql) {
7458 $error++;
7459 $this->error = $this->db->lasterror();
7460 }
7461 } else {
7462 $result = $this->insertExtraFields('', $user);
7463 if ($result < 0) {
7464 $error++;
7465 }
7466 }
7467
7468 if (!$error) {
7469 $parameters = array('key' => $key);
7470 global $action;
7471 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7472 if ($reshook < 0) {
7473 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7474 }
7475 }
7476
7477 if (!$error && $trigger) {
7478 // Call trigger
7479 $this->context = array('extrafieldupdate' => 1);
7480 $result = $this->call_trigger($trigger, $userused);
7481 if ($result < 0) {
7482 $error++;
7483 }
7484 // End call trigger
7485 }
7486
7487 if ($error) {
7488 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7489 $this->db->rollback();
7490 return -1;
7491 } else {
7492 $this->db->commit();
7493 return 1;
7494 }
7495 } else {
7496 return 0;
7497 }
7498 }
7499
7506 public function getExtraField($key)
7507 {
7508 return $this->array_options['options_'.$key] ?? null;
7509 }
7510
7518 public function setExtraField($key, $value)
7519 {
7520 $this->array_options['options_'.$key] = $value;
7521 }
7522
7533 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7534 {
7535 global $user;
7536
7537 if (empty($userused)) {
7538 $userused = $user;
7539 }
7540
7541 //$error = 0;
7542
7543 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7544 return 0; // For avoid conflicts if trigger used
7545 }
7546
7547 return 0;
7548 }
7549
7550
7565 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7566 {
7567 global $conf, $langs, $form;
7568
7569
7570 // 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...)
7571
7572 if (!is_object($form)) {
7573 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7574 $form = new Form($this->db);
7575 }
7576
7577 if (!empty($this->fields)) {
7578 $val = $this->fields[$key];
7579 }
7580
7581 // Validation tests and output
7582 $fieldValidationErrorMsg = '';
7583 $validationClass = '';
7584 $fieldValidationErrorMsg = $this->getFieldError($key);
7585 if (!empty($fieldValidationErrorMsg)) {
7586 $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
7587 } else {
7588 $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
7589 }
7590
7591 //$valuemultiselectinput = array();
7592 $out = '';
7593 $type = '';
7594 $isDependList = 0;
7595 $param = array();
7596 $param['options'] = array();
7597 $reg = array();
7598 // @phan-suppress-next-line PhanTypeMismatchProperty
7599 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7600 // Because we work on extrafields
7601 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7602 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7603 $type = 'link';
7604 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7605 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7606 $type = 'link';
7607 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7608 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7609 $type = 'link';
7610 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7611 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7612 $type = 'sellist';
7613 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7614 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7615 $type = 'sellist';
7616 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7617 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7618 $type = 'sellist';
7619 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7620 $param['options'] = array($reg[1] => 'N');
7621 $type = 'chkbxlst';
7622 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7623 $param['options'] = array();
7624 $type = 'varchar';
7625 $size = $reg[1];
7626 } elseif (preg_match('/varchar/', $val['type'])) {
7627 $param['options'] = array();
7628 $type = 'varchar';
7629 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7630 $param['options'] = array();
7631 $type = 'stars';
7632 $size = $reg[1];
7633 } else {
7634 $param['options'] = array();
7635 $type = $this->fields[$key]['type'];
7636 }
7637 //var_dump($type); var_dump($param['options']);
7638
7639 // Special case that force options and type ($type can be integer, varchar, ...)
7640 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7641 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7642 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7643 if (empty($this->fields[$key]['multiinput'])) {
7644 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7645 }
7646 }
7647
7648 $label = $this->fields[$key]['label'];
7649 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7650 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7651 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7652 // @phan-suppress-next-line PhanTypeMismatchProperty
7653 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7654 // @phan-suppress-next-line PhanTypeMismatchProperty
7655 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7656 // @phan-suppress-next-line PhanTypeMismatchProperty
7657 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7658 // @phan-suppress-next-line PhanTypeMismatchProperty
7659 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7660 // @phan-suppress-next-line PhanTypeMismatchProperty
7661 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7662
7663 // @phan-suppress-next-line PhanTypeMismatchProperty
7664 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7665 // @phan-suppress-next-line PhanTypeMismatchProperty
7666 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7667 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7668
7669 $objectid = $this->id;
7670
7671 if ($computed) {
7672 if (!preg_match('/^search_/', $keyprefix)) {
7673 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7674 } else {
7675 return '';
7676 }
7677 }
7678
7679 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7680 if (empty($morecss) && !empty($val['css'])) {
7681 $morecss = $val['css'];
7682 } elseif (empty($morecss)) {
7683 if ($type == 'date') {
7684 $morecss = 'minwidth100imp';
7685 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7686 $morecss = 'minwidth200imp';
7687 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7688 $morecss = 'maxwidth75';
7689 } elseif ($type == 'url') {
7690 $morecss = 'minwidth400';
7691 } elseif ($type == 'boolean') {
7692 $morecss = '';
7693 } else {
7694 if (is_numeric($size) && round((float) $size) < 12) {
7695 $morecss = 'minwidth100';
7696 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7697 $morecss = 'minwidth200';
7698 } else {
7699 $morecss = 'minwidth400';
7700 }
7701 }
7702 }
7703
7704 // Add validation state class
7705 if (!empty($validationClass)) {
7706 $morecss .= $validationClass;
7707 }
7708
7709 if (in_array($type, array('date'))) {
7710 $tmp = explode(',', $size);
7711 $newsize = $tmp[0];
7712 $showtime = 0;
7713
7714 // Do not show current date when field not required (see selectDate() method)
7715 if (!$required && $value == '') {
7716 $value = '-1';
7717 }
7718
7719 // TODO Must also support $moreparam
7720 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7721 } elseif (in_array($type, array('datetime'))) {
7722 $tmp = explode(',', $size);
7723 $newsize = $tmp[0];
7724 $showtime = 1;
7725
7726 // Do not show current date when field not required (see selectDate() method)
7727 if (!$required && $value == '') {
7728 $value = '-1';
7729 }
7730
7731 // TODO Must also support $moreparam
7732 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7733 } elseif (in_array($type, array('duration'))) {
7734 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7735 } elseif (in_array($type, array('int', 'integer'))) {
7736 $tmp = explode(',', $size);
7737 $newsize = $tmp[0];
7738 $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' : '').'>';
7739 } elseif (in_array($type, array('real'))) {
7740 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7741 } elseif (preg_match('/varchar/', (string) $type)) {
7742 $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' : '').'>';
7743 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7744 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7745 } elseif (preg_match('/^text/', (string) $type)) {
7746 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7747 if (!empty($param['options'])) {
7748 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7749 //var_dump($param['options']);
7750 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7751 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7752 $out .= "<script>";
7753 $out .= '
7754 function handlemultiinputdisabling(htmlname){
7755 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
7756 multiinput = $("#"+htmlname+"_multiinput");
7757 multiinput.find("option").each(function(){
7758 tmpval = $("#"+htmlname).val();
7759 tmpvalarray = tmpval.split("\n");
7760 valtotest = $(this).val();
7761 if(tmpvalarray.includes(valtotest)){
7762 $(this).prop("disabled",true);
7763 } else {
7764 if($(this).prop("disabled") == true){
7765 console.log(valtotest)
7766 $(this).prop("disabled", false);
7767 }
7768 }
7769 });
7770 }
7771
7772 $(document).ready(function () {
7773 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
7774 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
7775 tmpvalarray = tmpval.split(",");
7776 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
7777 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
7778 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
7779 if(tmpval == ""){
7780 tmpval = valtotest;
7781 } else {
7782 tmpval = tmpval + "\n" + valtotest;
7783 }
7784 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
7785 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7786 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
7787 } else {
7788 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
7789 }
7790 });
7791 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
7792 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7793 });
7794 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7795 })';
7796 $out .= "</script>";
7797 $value = str_replace(',', "\n", $value);
7798 }
7799 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7800 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
7801 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
7802 } else {
7803 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7804 }
7805 } elseif (preg_match('/^html/', (string) $type)) {
7806 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7807 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7808 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
7809 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
7810 } else {
7811 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7812 }
7813 } elseif ($type == 'boolean') {
7814 $checked = '';
7815 if (!empty($value)) {
7816 $checked = ' checked value="1" ';
7817 } else {
7818 $checked = ' value="1" ';
7819 }
7820 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
7821 } elseif ($type == 'price') {
7822 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7823 $value = price($value);
7824 }
7825 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
7826 } elseif ($type == 'stars') {
7827 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7828 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
7829 $i = 1;
7830 while ($i <= $size) {
7831 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
7832 $i++;
7833 }
7834 $out .= '</div>';
7835 $out .= '<script>
7836 jQuery(function($) { /* commonobject.class.php 1 */
7837 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
7838 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
7839 container.find(".star").each(function() {
7840 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7841 });
7842 container.find(".star").on("mouseover", function() {
7843 let selectedStar = $(this).data("value");
7844 container.find(".star").each(function() {
7845 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
7846 });
7847 });
7848 container.on("mouseout", function() {
7849 container.find(".star").each(function() {
7850 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7851 });
7852 });
7853 container.find(".star").off("click").on("click", function() {
7854 selectedStars = $(this).data("value");
7855 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
7856 selectedStars = 0;
7857 }
7858 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
7859 container.find(".star").each(function() {
7860 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7861 });
7862 });
7863 });
7864 </script>';
7865 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7866 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7867 $value = price($value);
7868 }
7869 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
7870 } elseif ($type == 'select') { // combo list
7871 $out = '';
7872 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7873 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7874 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7875 }
7876
7877 $tmpselect = '';
7878 $nbchoice = 0;
7879
7880 foreach ($param['options'] as $keyb => $valb) {
7881 if ((string) $keyb == '') {
7882 continue;
7883 }
7884 if (strpos($valb, "|") !== false) {
7885 list($valb, $parent) = explode('|', $valb);
7886 }
7887 $nbchoice++;
7888 $tmpselect .= '<option value="'.$keyb.'"';
7889 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
7890 if (!empty($parent)) {
7891 $isDependList = 1;
7892 }
7893 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
7894 $tmpselect .= '>'.$langs->trans($valb).'</option>';
7895 }
7896
7897 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
7898 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
7899 $out .= '<option value="0">&nbsp;</option>';
7900 }
7901 $out .= $tmpselect;
7902 $out .= '</select>';
7903 } elseif ($type == 'sellist') {
7904 $out = '';
7905 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7906 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7907 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7908 }
7909
7910 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
7911 if (is_array($param['options'])) {
7912 $tmpparamoptions = array_keys($param['options']);
7913 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
7914
7915 $InfoFieldList = explode(":", $paramoptions[0], 5);
7916 // 0 : tableName
7917 // 1 : label field name
7918 // 2 : key fields name (if different of rowid)
7919 // optional parameters...
7920 // 3 : key field parent (for dependent lists). How this is used ?
7921 // 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".
7922 // 5 : string category type. This replace the filter.
7923 // 6 : ids categories list separated by comma for category root. This replace the filter.
7924 // 7 : sort field
7925
7926 // If there is filter
7927 if (! empty($InfoFieldList[4])) {
7928 $pos = 0;
7929 $parenthesisopen = 0;
7930 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
7931 if (substr($InfoFieldList[4], $pos, 1) == '(') {
7932 $parenthesisopen++;
7933 }
7934 if (substr($InfoFieldList[4], $pos, 1) == ')') {
7935 $parenthesisopen--;
7936 }
7937 $pos++;
7938 }
7939 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
7940 $tmpafter = substr($InfoFieldList[4], $pos + 1);
7941 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
7942 $InfoFieldList[4] = $tmpbefore;
7943 if ($tmpafter !== '') {
7944 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
7945 }
7946 //var_dump($InfoFieldList);
7947
7948 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
7949 $reg = array();
7950 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
7951 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
7952 }
7953
7954 //var_dump($InfoFieldList);
7955 }
7956
7957 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
7958
7959 $parentName = '';
7960 $parentField = '';
7961
7962 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
7963
7964 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7965 if (strpos($InfoFieldList[4], 'extra.') !== false) {
7966 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
7967 } else {
7968 $keyList = $InfoFieldList[2].' as rowid';
7969 }
7970 }
7971 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
7972 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
7973 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
7974 $keyList .= ', main.'.$parentField;
7975 } else {
7976 $keyList .= ', '.$parentField;
7977 }
7978 }
7979
7980 $filter_categorie = false;
7981 if (count($InfoFieldList) > 5) {
7982 if ($InfoFieldList[0] == 'categorie') {
7983 $filter_categorie = true;
7984 }
7985 }
7986
7987 if (!$filter_categorie) {
7988 $fields_label = explode('|', $InfoFieldList[1]);
7989 if (is_array($fields_label)) {
7990 $keyList .= ', ';
7991 $keyList .= implode(', ', $fields_label);
7992 }
7993
7994 $sqlwhere = '';
7995 $sql = "SELECT " . $keyList;
7996 $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
7997
7998 if (!empty($InfoFieldList[4])) {
7999 // can use SELECT request
8000 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8001 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8002 }
8003
8004 // current object id can be use into filter
8005 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8006 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8007 } else {
8008 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8009 }
8010
8011 // We have to join on extrafield table
8012 $errstr = '';
8013 if (strpos($InfoFieldList[4], 'extra') !== false) {
8014 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8015 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8016 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8017 } else {
8018 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8019 }
8020 } else {
8021 $sqlwhere .= ' WHERE 1=1';
8022 }
8023
8024 // Add Usf filter on second line
8025 /*
8026 if ($Usf) {
8027 $errorstr = '';
8028 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8029 if (!$errorstr) {
8030 $sqlwhere .= $sqlusf;
8031 } else {
8032 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8033 }
8034 }
8035 */
8036
8037 // Some tables may have field, some other not. For the moment we disable it.
8038 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8039 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8040 }
8041 $sql .= $sqlwhere;
8042
8043 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8044 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8045 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8046 } else {
8047 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8048 }
8049 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8050 // print $sql;
8051
8052 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8053 $resql = $this->db->query($sql);
8054 if ($resql) {
8055 $out .= '<option value="0">&nbsp;</option>';
8056 $num = $this->db->num_rows($resql);
8057 $i = 0;
8058 while ($i < $num) {
8059 $labeltoshow = '';
8060 $obj = $this->db->fetch_object($resql);
8061
8062 // Several field into label (eq table:code|libelle:rowid)
8063 $notrans = false;
8064 $fields_label = explode('|', $InfoFieldList[1]);
8065 if (count($fields_label) > 1) {
8066 $notrans = true;
8067 foreach ($fields_label as $field_toshow) {
8068 $labeltoshow .= $obj->$field_toshow . ' ';
8069 }
8070 } else {
8071 $labeltoshow = $obj->{$InfoFieldList[1]};
8072 }
8073 $labeltoshow = dol_trunc($labeltoshow, 45);
8074
8075 if ($value == $obj->rowid) {
8076 foreach ($fields_label as $field_toshow) {
8077 $translabel = $langs->trans($obj->$field_toshow);
8078 if ($translabel != $obj->$field_toshow) {
8079 $labeltoshow = dol_trunc($translabel) . ' ';
8080 } else {
8081 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8082 }
8083 }
8084 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8085 } else {
8086 if (!$notrans) {
8087 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8088 if ($translabel != $obj->{$InfoFieldList[1]}) {
8089 $labeltoshow = dol_trunc($translabel, 18);
8090 } else {
8091 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8092 }
8093 }
8094 if (empty($labeltoshow)) {
8095 $labeltoshow = '(not defined)';
8096 }
8097 if ($value == $obj->rowid) {
8098 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8099 }
8100
8101 if (!empty($InfoFieldList[3]) && $parentField) {
8102 $parent = $parentName . ':' . $obj->{$parentField};
8103 $isDependList = 1;
8104 }
8105
8106 $out .= '<option value="' . $obj->rowid . '"';
8107 $out .= ($value == $obj->rowid ? ' selected' : '');
8108 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8109 $out .= '>' . $labeltoshow . '</option>';
8110 }
8111
8112 $i++;
8113 }
8114 $this->db->free($resql);
8115 } else {
8116 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8117 }
8118 } else {
8119 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8120 $categcode = $InfoFieldList[5];
8121 if (is_numeric($categcode)) {
8122 $categcode = Categorie::$MAP_ID_TO_CODE[(int) $InfoFieldList[5]];
8123 }
8124 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8125 $out .= '<option value="0">&nbsp;</option>';
8126 foreach ($data as $data_key => $data_value) {
8127 $out .= '<option value="' . $data_key . '"';
8128 $out .= ($value == $data_key ? ' selected' : '');
8129 $out .= '>' . $data_value . '</option>';
8130 }
8131 }
8132 }
8133 $out .= '</select>';
8134 } elseif ($type == 'checkbox') {
8135 $value_arr = explode(',', $value);
8136 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8137 } elseif ($type == 'radio') {
8138 $out = '';
8139 foreach ($param['options'] as $keyopt => $valopt) {
8140 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8141 $out .= ' value="'.$keyopt.'"';
8142 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8143 $out .= ($value == $keyopt ? 'checked' : '');
8144 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8145 }
8146 } elseif ($type == 'chkbxlst') {
8147 if (is_array($value)) {
8148 $value_arr = $value;
8149 } else {
8150 $value_arr = explode(',', $value);
8151 }
8152
8153 if (is_array($param['options'])) {
8154 $tmpparamoptions = array_keys($param['options']);
8155 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8156
8157 $InfoFieldList = explode(":", $paramoptions[0], 5);
8158 // 0 : tableName
8159 // 1 : label field name
8160 // 2 : key fields name (if different of rowid)
8161 // optional parameters...
8162 // 3 : key field parent (for dependent lists). How this is used ?
8163 // 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".
8164 // 5 : string category type. This replace the filter.
8165 // 6 : ids categories list separated by comma for category root. This replace the filter.
8166 // 7 : sort field
8167
8168 // If there is a filter
8169 if (! empty($InfoFieldList[4])) {
8170 $pos = 0;
8171 $parenthesisopen = 0;
8172 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8173 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8174 $parenthesisopen++;
8175 }
8176 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8177 $parenthesisopen--;
8178 }
8179 $pos++;
8180 }
8181 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8182 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8183 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8184 $InfoFieldList[4] = $tmpbefore;
8185 if ($tmpafter !== '') {
8186 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8187 }
8188
8189 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8190 $reg = array();
8191 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8192 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8193 }
8194
8195 //var_dump($InfoFieldList);
8196 }
8197
8198 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8199
8200 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8201
8202 $parentName = '';
8203 $parentField = '';
8204
8205 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8206
8207 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8208 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8209 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8210 } else {
8211 $keyList = $InfoFieldList[2].' as rowid';
8212 }
8213 }
8214 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8215 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8216 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8217 $keyList .= ', main.'.$parentField;
8218 } else {
8219 $keyList .= ', '.$parentField;
8220 }
8221 }
8222
8223 $filter_categorie = false;
8224 if (count($InfoFieldList) > 5) {
8225 if ($InfoFieldList[0] == 'categorie') {
8226 $filter_categorie = true;
8227 }
8228 }
8229
8230 // Common filter
8231 if (!$filter_categorie) {
8232 $fields_label = explode('|', $InfoFieldList[1]);
8233 if (is_array($fields_label)) {
8234 $keyList .= ', ';
8235 $keyList .= implode(', ', $fields_label);
8236 }
8237
8238 $sqlwhere = '';
8239 $sql = "SELECT " . $keyList;
8240 $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8241
8242 if (!empty($InfoFieldList[4])) {
8243 // can use SELECT request
8244 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8245 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8246 }
8247
8248 // current object id can be use into filter
8249 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8250 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8251 } else {
8252 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8253 }
8254
8255 // We have to join on extrafield table
8256 $errstr = '';
8257 if (strpos($InfoFieldList[4], 'extra') !== false) {
8258 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8259 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8260 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8261 } else {
8262 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8263 }
8264 } else {
8265 $sqlwhere .= ' WHERE 1=1';
8266 }
8267
8268 // Add Usf filter on second line
8269 /*
8270 if ($Usf) {
8271 $errorstr = '';
8272 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8273 if (!$errorstr) {
8274 $sqlwhere .= $sqlusf;
8275 } else {
8276 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8277 }
8278 }
8279 */
8280
8281 // Some tables may have field, some other not. For the moment we disable it.
8282 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8283 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8284 }
8285 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8286 // print $sql;
8287
8288 $sql .= $sqlwhere;
8289
8290 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8291
8292 $resql = $this->db->query($sql);
8293 if ($resql) {
8294 $num = $this->db->num_rows($resql);
8295 $i = 0;
8296
8297 $data = array();
8298
8299 while ($i < $num) {
8300 $labeltoshow = '';
8301 $obj = $this->db->fetch_object($resql);
8302
8303 $notrans = false;
8304 // Several field into label (eq table:code|libelle:rowid)
8305 $fields_label = explode('|', $InfoFieldList[1]);
8306 if (count($fields_label) > 1) {
8307 $notrans = true;
8308 foreach ($fields_label as $field_toshow) {
8309 $labeltoshow .= $obj->$field_toshow . ' ';
8310 }
8311 } else {
8312 $labeltoshow = $obj->{$InfoFieldList[1]};
8313 }
8314 $labeltoshow = dol_trunc($labeltoshow, 45);
8315
8316 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8317 foreach ($fields_label as $field_toshow) {
8318 $translabel = $langs->trans($obj->$field_toshow);
8319 if ($translabel != $obj->$field_toshow) {
8320 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8321 } else {
8322 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8323 }
8324 }
8325
8326 $data[$obj->rowid] = $labeltoshow;
8327 } else {
8328 if (!$notrans) {
8329 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8330 if ($translabel != $obj->{$InfoFieldList[1]}) {
8331 $labeltoshow = dol_trunc($translabel, 18);
8332 } else {
8333 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8334 }
8335 }
8336 if (empty($labeltoshow)) {
8337 $labeltoshow = '(not defined)';
8338 }
8339
8340 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8341 $data[$obj->rowid] = $labeltoshow;
8342 }
8343
8344 if (!empty($InfoFieldList[3]) && $parentField) {
8345 $parent = $parentName . ':' . $obj->{$parentField};
8346 $isDependList = 1;
8347 }
8348
8349 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8350 }
8351
8352 $i++;
8353 }
8354 $this->db->free($resql);
8355
8356 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8357 } else {
8358 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8359 }
8360 } else {
8361 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8362 $categcode = $InfoFieldList[5];
8363 if (is_numeric($categcode)) {
8364 $categcode = Categorie::$MAP_ID_TO_CODE[(int) $InfoFieldList[5]];
8365 }
8366 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8367 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8368 }
8369 }
8370 } elseif ($type == 'link') {
8371 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8372 // Filter can contains some ':' inside.
8373 $param_list = array_keys($param['options']);
8374 $param_list_array = explode(':', $param_list[0], 4);
8375
8376 $showempty = (($required && $default != '') ? 0 : 1);
8377
8378 if (!preg_match('/search_/', $keyprefix)) {
8379 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8380 // @phan-suppress-next-line PhanTypeMismatchProperty
8381 if (!empty($this->fields[$key]['picto'])) {
8382 $morecss .= ' widthcentpercentminusxx';
8383 } else {
8384 $morecss .= ' widthcentpercentminusx';
8385 }
8386 } else {
8387 // @phan-suppress-next-line PhanTypeMismatchProperty
8388 if (!empty($this->fields[$key]['picto'])) {
8389 $morecss .= ' widthcentpercentminusx';
8390 }
8391 }
8392 }
8393
8394 // $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.
8395
8396 // $valparent is a string 'dataobject@module:keyoffieldinfieldsarray' to find the record field to link to.
8397 // $valparent = $this->element.($this->module ? '@'.$this->module : '').':'.$key.$keysuffix;
8398
8399 // $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.
8400
8401 //$objectfield = $valparent;
8402 $objectfield = $val; // Is better than using old method $valparent
8403
8404 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8405 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8406
8407 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8408 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8409 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8410 list($class, $classfile) = explode(':', $param_list[0]);
8411 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8412 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8413 } else {
8414 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8415 }
8416 $paramforthenewlink = '';
8417 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8418 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8419 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8420 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8421 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8422 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8423 $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>';
8424 }
8425 }
8426 } elseif ($type == 'password') {
8427 // If prefix is 'search_', field is used as a filter, we use a common text field.
8428 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8429 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8430 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8431 } else {
8432 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8433 }
8434 } elseif ($type == 'array') {
8435 $newval = $val;
8436 $newval['type'] = 'varchar(256)';
8437
8438 $out = '';
8439 if (!empty($value)) {
8440 foreach ($value as $option) {
8441 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8442 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8443 }
8444 }
8445 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8446
8447 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8448 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8449
8450 if (!empty($conf->use_javascript_ajax)) {
8451 $out .= '
8452 <script nonce="'.getNonce().'">
8453 $(document).ready(function() {
8454 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8455 $("'.dol_escape_js($newInput).'").insertBefore(this);
8456 });
8457
8458 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8459 $(this).parent().remove();
8460 });
8461 });
8462 </script>';
8463 }
8464 }
8465 if (!empty($hidden)) {
8466 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8467 }
8468
8469 if ($isDependList == 1) {
8470 $out .= $this->getJSListDependancies('_common');
8471 }
8472 /* Add comments
8473 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8474 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8475 */
8476
8477 // Display error message for field
8478 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8479 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8480 }
8481
8482 return $out;
8483 }
8484
8498 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8499 {
8500 global $conf, $langs, $form;
8501
8502 // 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...)
8503
8504 if (!is_object($form)) {
8505 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8506 $form = new Form($this->db);
8507 }
8508
8509 //$label = empty($val['label']) ? '' : $val['label'];
8510 $type = empty($val['type']) ? '' : $val['type'];
8511 $size = empty($val['css']) ? '' : $val['css'];
8512 $reg = array();
8513
8514 // Convert var to be able to share same code than showOutputField of extrafields
8515 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8516 $type = 'varchar'; // convert varchar(xx) int varchar
8517 $size = $reg[1];
8518 } elseif (preg_match('/varchar/', $type)) {
8519 $type = 'varchar'; // convert varchar(xx) int varchar
8520 }
8521 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8522 // @phan-suppress-next-line PhanTypeMismatchProperty
8523 if (empty($this->fields[$key]['multiinput'])) {
8524 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8525 }
8526 }
8527 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8528 $type = 'link';
8529 }
8530
8531 $default = empty($val['default']) ? '' : $val['default'];
8532 $computed = empty($val['computed']) ? '' : $val['computed'];
8533 $unique = empty($val['unique']) ? '' : $val['unique'];
8534 $required = empty($val['required']) ? '' : $val['required'];
8535 $param = array();
8536 $param['options'] = array();
8537
8538 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8539 $param['options'] = $val['arrayofkeyval'];
8540 }
8541 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8542 $type = 'link';
8543 $stringforoptions = $reg[1].':'.$reg[2];
8544 // Special case: Force addition of getnomurlparam1 to -1 for users
8545 if ($reg[1] == 'User') {
8546 $stringforoptions .= ':#getnomurlparam1=-1';
8547 }
8548 $param['options'] = array($stringforoptions => $stringforoptions);
8549 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8550 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8551 $type = 'sellist';
8552 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8553 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8554 $type = 'sellist';
8555 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8556 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8557 $type = 'sellist';
8558 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8559 $param['options'] = array($reg[1] => 'N');
8560 $type = 'chkbxlst';
8561 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8562 $param['options'] = array();
8563 $type = 'stars';
8564 $size = $reg[1];
8565 }
8566
8567 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8568 $list = (empty($val['list']) ? '' : $val['list']);
8569 $help = (empty($val['help']) ? '' : $val['help']);
8570 $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)
8571
8572 if ($hidden) {
8573 return '';
8574 }
8575
8576 // If field is a computed field, value must become result of compute
8577 if ($computed) {
8578 // Make the eval of compute string
8579 //var_dump($computed);
8580 $value = dol_eval((string) $computed, 1, 0, '2');
8581 }
8582
8583 if (empty($morecss)) {
8584 if ($type == 'date') {
8585 $morecss = 'minwidth100imp';
8586 } elseif ($type == 'datetime' || $type == 'timestamp') {
8587 $morecss = 'minwidth200imp';
8588 } elseif (in_array($type, array('int', 'double', 'price'))) {
8589 $morecss = 'maxwidth75';
8590 } elseif ($type == 'url') {
8591 $morecss = 'minwidth400';
8592 } elseif ($type == 'boolean') {
8593 $morecss = '';
8594 } else {
8595 if (is_numeric($size) && round((float) $size) < 12) {
8596 $morecss = 'minwidth100';
8597 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8598 $morecss = 'minwidth200';
8599 } else {
8600 $morecss = 'minwidth400';
8601 }
8602 }
8603 }
8604
8605 // Format output value differently according to properties of field
8606 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8607 // @phan-suppress-next-line PhanTypeMismatchProperty
8608 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.
8609 $value = $this->getNomUrl(1, '', 0, '', 1);
8610 }
8611 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8612 $value = $this->getLibStatut(3);
8613 } elseif ($type == 'date') {
8614 if (!empty($value)) {
8615 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8616 } else {
8617 $value = '';
8618 }
8619 } elseif ($type == 'datetime' || $type == 'timestamp') {
8620 if (!empty($value)) {
8621 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8622 } else {
8623 $value = '';
8624 }
8625 } elseif ($type == 'duration') {
8626 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8627 if (!is_null($value) && $value !== '') {
8628 $value = convertSecondToTime((int) $value, 'allhourmin');
8629 }
8630 } elseif ($type == 'double' || $type == 'real') {
8631 if (!is_null($value) && $value !== '') {
8632 $value = price($value);
8633 }
8634 } elseif ($type == 'boolean') {
8635 $checked = '';
8636 if (!empty($value)) {
8637 $checked = ' checked ';
8638 }
8639 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8640 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8641 } else {
8642 $value = yn($value ? 1 : 0);
8643 }
8644 } elseif ($type == 'mail' || $type == 'email') {
8645 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8646 } elseif ($type == 'url') {
8647 $value = dol_print_url((string) $value, '_blank', 32, 1);
8648 } elseif ($type == 'phone') {
8649 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8650 } elseif ($type == 'ip') {
8651 $value = dol_print_ip((string) $value, 0);
8652 } elseif ($type == 'stars') {
8653 $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 : '').'>';
8654 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8655 $i = 1;
8656 while ($i <= $size) {
8657 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8658 $i++;
8659 }
8660 $value .= '</div>';
8661 $value .= '<script>
8662 $(document).ready(function() { /* commonobject.class.php 2 */
8663 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8664 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8665 container.find(".star").each(function() {
8666 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8667 });
8668 container.find(".star").on("mouseover", function() {
8669 let selectedStar = $(this).data("value");
8670 container.find(".star").each(function() {
8671 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8672 });
8673 });
8674 container.on("mouseout", function() {
8675 container.find(".star").each(function() {
8676 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8677 });
8678 });
8679 container.find(".star").off("click").on("click", function() {
8680 selectedStars = $(this).data("value");
8681 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8682 selectedStars = 0;
8683 }
8684 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8685 container.find(".star").each(function() {
8686 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8687 });
8688 $.ajax({
8689 url: "ajax/'.$this->element.'.php",
8690 method: "POST",
8691 data: {
8692 objectId: "'.$this->id.'",
8693 field: "'.$keyprefix.$key.$keysuffix.'",
8694 value: selectedStars,
8695 token: "'.newToken().'"
8696 },
8697 success: function(response) {
8698 var res = JSON.parse(response);
8699 console[res.status === "success" ? "log" : "error"](res.message);
8700 },
8701 error: function(xhr, status, error) {
8702 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8703 }
8704 });
8705 });
8706 });
8707 </script>';
8708 } elseif ($type == 'price') {
8709 if (!is_null($value) && $value !== '') {
8710 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8711 }
8712 } elseif ($type == 'select') {
8713 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8714 if (strpos($value, "|") !== false) {
8715 $value = $langs->trans(explode('|', $value)[0]);
8716 } elseif (! is_numeric($value)) {
8717 $value = $langs->trans($value);
8718 }
8719 } elseif ($type == 'sellist') {
8720 $param_list = array_keys($param['options']);
8721 $InfoFieldList = explode(":", $param_list[0]);
8722
8723 $selectkey = "rowid";
8724 $keyList = 'rowid';
8725
8726 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8727 $selectkey = $InfoFieldList[2];
8728 $keyList = $InfoFieldList[2].' as rowid';
8729 }
8730
8731 $fields_label = explode('|', $InfoFieldList[1]);
8732 if (is_array($fields_label)) {
8733 $keyList .= ', ';
8734 $keyList .= implode(', ', $fields_label);
8735 }
8736
8737 $filter_categorie = false;
8738 if (count($InfoFieldList) > 5) {
8739 if ($InfoFieldList[0] == 'categorie') {
8740 $filter_categorie = true;
8741 }
8742 }
8743
8744 $sql = "SELECT ".$keyList;
8745 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8746 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8747 $sql .= ' as main';
8748 }
8749 if ($selectkey == 'rowid' && empty($value)) {
8750 $sql .= " WHERE ".$selectkey." = 0";
8751 } elseif ($selectkey == 'rowid') {
8752 $sql .= " WHERE ".$selectkey." = ".((int) $value);
8753 } else {
8754 $sql .= " WHERE ".$selectkey." = '".$this->db->escape((string) $value)."'";
8755 }
8756
8757 //$sql.= ' AND entity = '.$conf->entity;
8758
8759 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
8760 $resql = $this->db->query($sql);
8761 if ($resql) {
8762 if (!$filter_categorie) {
8763 $value = ''; // value was used, so now we reset it to use it to build final output
8764 $numrows = $this->db->num_rows($resql);
8765 if ($numrows) {
8766 $obj = $this->db->fetch_object($resql);
8767
8768 // Several field into label (eq table:code|libelle:rowid)
8769 $fields_label = explode('|', $InfoFieldList[1]);
8770
8771 if (is_array($fields_label) && count($fields_label) > 1) {
8772 foreach ($fields_label as $field_toshow) {
8773 $translabel = '';
8774 if (!empty($obj->$field_toshow)) {
8775 $translabel = $langs->trans($obj->$field_toshow);
8776 }
8777 if ($translabel != $field_toshow) {
8778 $value .= dol_trunc($translabel, 18) . ' ';
8779 } else {
8780 $value .= $obj->$field_toshow . ' ';
8781 }
8782 }
8783 } else {
8784 $translabel = '';
8785 if (!empty($obj->{$InfoFieldList[1]})) {
8786 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8787 }
8788 if ($translabel != $obj->{$InfoFieldList[1]}) {
8789 $value = dol_trunc($translabel, 18);
8790 } else {
8791 $value = $obj->{$InfoFieldList[1]};
8792 }
8793 }
8794 }
8795 } else {
8796 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8797
8798 $toprint = array();
8799 $obj = $this->db->fetch_object($resql);
8800 $c = new Categorie($this->db);
8801 $c->fetch($obj->rowid);
8802 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8803 foreach ($ways as $way) {
8804 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8805 }
8806 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8807 }
8808 } else {
8809 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8810 }
8811 } elseif ($type == 'radio') {
8812 $value = $param['options'][(string) $value];
8813 } elseif ($type == 'checkbox') {
8814 $value_arr = explode(',', (string) $value);
8815 $value = '';
8816 if (is_array($value_arr) && count($value_arr) > 0) {
8817 $toprint = array();
8818 foreach ($value_arr as $keyval => $valueval) {
8819 if (!empty($valueval)) {
8820 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
8821 }
8822 }
8823 if (!empty($toprint)) {
8824 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8825 }
8826 }
8827 } elseif ($type == 'chkbxlst') {
8828 $value_arr = (isset($value) ? explode(',', $value) : array());
8829
8830 $param_list = array_keys($param['options']);
8831 $InfoFieldList = explode(":", $param_list[0]);
8832
8833 $selectkey = "rowid";
8834 $keyList = 'rowid';
8835
8836 if (count($InfoFieldList) >= 3) {
8837 $selectkey = $InfoFieldList[2];
8838 $keyList = $InfoFieldList[2].' as rowid';
8839 }
8840
8841 $fields_label = explode('|', $InfoFieldList[1]);
8842 if (is_array($fields_label)) {
8843 $keyList .= ', ';
8844 $keyList .= implode(', ', $fields_label);
8845 }
8846
8847 $filter_categorie = false;
8848 if (count($InfoFieldList) > 5) {
8849 if ($InfoFieldList[0] == 'categorie') {
8850 $filter_categorie = true;
8851 }
8852 }
8853
8854 $sql = "SELECT ".$keyList;
8855 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8856 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8857 $sql .= ' as main';
8858 }
8859 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
8860 // $sql.= ' AND entity = '.$conf->entity;
8861
8862 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
8863 $resql = $this->db->query($sql);
8864 if ($resql) {
8865 if (!$filter_categorie) {
8866 $value = ''; // value was used, so now we reset it to use it to build final output
8867 $toprint = array();
8868 while ($obj = $this->db->fetch_object($resql)) {
8869 // Several field into label (eq table:code|libelle:rowid)
8870 $fields_label = explode('|', $InfoFieldList[1]);
8871 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8872 if (is_array($fields_label) && count($fields_label) > 1) {
8873 foreach ($fields_label as $field_toshow) {
8874 $translabel = '';
8875 if (!empty($obj->$field_toshow)) {
8876 $translabel = $langs->trans($obj->$field_toshow);
8877 }
8878 if ($translabel != $field_toshow) {
8879 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8880 } else {
8881 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
8882 }
8883 }
8884 } else {
8885 $translabel = '';
8886 if (!empty($obj->{$InfoFieldList[1]})) {
8887 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8888 }
8889 if ($translabel != $obj->{$InfoFieldList[1]}) {
8890 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8891 } else {
8892 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
8893 }
8894 }
8895 }
8896 }
8897 } else {
8898 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8899
8900 $toprint = array();
8901 while ($obj = $this->db->fetch_object($resql)) {
8902 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8903 $c = new Categorie($this->db);
8904 $c->fetch($obj->rowid);
8905 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8906 foreach ($ways as $way) {
8907 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8908 }
8909 }
8910 }
8911 }
8912 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8913 } else {
8914 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8915 }
8916 } elseif ($type == 'link') {
8917 $out = '';
8918
8919 // only if something to display (perf)
8920 if ($value) {
8921 $param_list = array_keys($param['options']);
8922 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8923 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
8924
8925 $InfoFieldList = explode(":", $param_list[0]);
8926
8927 $classname = $InfoFieldList[0];
8928 $classpath = $InfoFieldList[1];
8929
8930 // Set $getnomurlparam1 et getnomurlparam2
8931 $getnomurlparam = 3;
8932 $getnomurlparam2 = '';
8933 $regtmp = array();
8934 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
8935 $getnomurlparam = $regtmp[1];
8936 }
8937 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
8938 $getnomurlparam2 = $regtmp[1];
8939 }
8940
8941 if (!empty($classpath)) {
8942 dol_include_once($InfoFieldList[1]);
8943
8944 if ($classname && !class_exists($classname)) {
8945 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
8946 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
8947 $element_prop = getElementProperties($classname);
8948 if ($element_prop) {
8949 $classname = $element_prop['classname'];
8950 }
8951 }
8952
8953
8954 if ($classname && class_exists($classname)) {
8955 $object = new $classname($this->db);
8956 '@phan-var-force CommonObject $object';
8957 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
8958 '@phan-var-force Product $object';
8959 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
8960 } else {
8961 $result = $object->fetch($value);
8962 }
8963 if ($result > 0) {
8964 if ($object->element === 'product') {
8965 '@phan-var-force Product $object';
8966 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
8967 if (isset($val['get_name_url_params'])) {
8968 $get_name_url_params = explode(':', $val['get_name_url_params']);
8969 if (!empty($get_name_url_params)) {
8970 $param_num_max = count($get_name_url_param_arr) - 1;
8971 foreach ($get_name_url_params as $param_num => $param_value) {
8972 if ($param_num > $param_num_max) {
8973 break;
8974 }
8975 $get_name_url_param_arr[$param_num] = $param_value;
8976 }
8977 }
8978 }
8979
8983 $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]);
8984 } else {
8985 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
8986 }
8987 } else {
8988 $value = '';
8989 }
8990 }
8991 } else {
8992 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
8993 return 'Error bad setup of extrafield';
8994 }
8995 } else {
8996 $value = '';
8997 }
8998 } elseif ($type == 'password') {
8999 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
9000 //$value = preg_replace('/./i', '*', $value);
9001 } elseif ($type == 'array') {
9002 if (is_array($value)) {
9003 $value = implode('<br>', $value);
9004 } else {
9005 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
9006 return 'Error unexpected result from code evaluation';
9007 }
9008 } else { // text|html|varchar
9009 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
9010 $value = str_replace(',', "\n", $value);
9011 }
9012 $value = dol_htmlentitiesbr((string) $value);
9013 }
9014
9015 //print $type.'-'.$size.'-'.$value;
9016 $out = $value;
9017
9018 return is_null($out) ? '' : $out;
9019 }
9020
9027 public function clearFieldError($fieldKey)
9028 {
9029 $this->error = '';
9030 unset($this->validateFieldsErrors[$fieldKey]);
9031 }
9032
9040 public function setFieldError($fieldKey, $msg = '')
9041 {
9042 global $langs;
9043 if (empty($msg)) {
9044 $msg = $langs->trans("UnknownError");
9045 }
9046
9047 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9048 }
9049
9056 public function getFieldError($fieldKey)
9057 {
9058 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9059 return $this->validateFieldsErrors[$fieldKey];
9060 }
9061 return '';
9062 }
9063
9072 public function validateField($fields, $fieldKey, $fieldValue)
9073 {
9074 global $langs;
9075
9076 if (!class_exists('Validate')) {
9077 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9078 }
9079
9080 $this->clearFieldError($fieldKey);
9081
9082 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9083 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9084 return false;
9085 }
9086
9087 $val = $fields[$fieldKey];
9088
9089 $param = array();
9090 $param['options'] = array();
9091 $type = $val['type'];
9092
9093 $required = false;
9094 if (isset($val['notnull']) && $val['notnull'] === 1) {
9095 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9096 $required = true;
9097 }
9098
9099 $maxSize = 0;
9100 $minSize = 0;
9101
9102 //
9103 // PREPARE Elements
9104 //
9105 $reg = array();
9106
9107 // Convert var to be able to share same code than showOutputField of extrafields
9108 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9109 $type = 'varchar'; // convert varchar(xx) int varchar
9110 $maxSize = (int) $reg[1];
9111 } elseif (preg_match('/varchar/', $type)) {
9112 $type = 'varchar'; // convert varchar(xx) int varchar
9113 }
9114
9115 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9116 $type = 'select';
9117 }
9118
9119 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9120 $type = 'link';
9121 }
9122
9123 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9124 $param['options'] = $val['arrayofkeyval'];
9125 }
9126
9127 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9128 $type = 'link';
9129 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9130 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9131 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9132 $type = 'sellist';
9133 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9134 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9135 $type = 'sellist';
9136 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9137 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9138 $type = 'sellist';
9139 }
9140
9141 //
9142 // TEST Value
9143 //
9144
9145 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9146 $validate = new Validate($this->db, $langs);
9147
9148
9149 // little trick : to perform tests with good performances sort tests by quick to low
9150
9151 //
9152 // COMMON TESTS
9153 //
9154
9155 // Required test and empty value
9156 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9157 $this->setFieldError($fieldKey, $validate->error);
9158 return false;
9159 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9160 // if no value sent and the field is not mandatory, no need to perform tests
9161 return true;
9162 }
9163
9164 // MAX Size test
9165 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9166 $this->setFieldError($fieldKey, $validate->error);
9167 return false;
9168 }
9169
9170 // MIN Size test
9171 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9172 $this->setFieldError($fieldKey, $validate->error);
9173 return false;
9174 }
9175
9176 //
9177 // TESTS for TYPE
9178 //
9179
9180 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9181 if (!$validate->isTimestamp($fieldValue)) {
9182 $this->setFieldError($fieldKey, $validate->error);
9183 return false;
9184 } else {
9185 return true;
9186 }
9187 } elseif ($type == 'duration') {
9188 if (!$validate->isDuration($fieldValue)) {
9189 $this->setFieldError($fieldKey, $validate->error);
9190 return false;
9191 } else {
9192 return true;
9193 }
9194 } elseif (in_array($type, array('double', 'real', 'price'))) {
9195 // is numeric
9196 if (!$validate->isNumeric($fieldValue)) {
9197 $this->setFieldError($fieldKey, $validate->error);
9198 return false;
9199 } else {
9200 return true;
9201 }
9202 } elseif ($type == 'boolean') {
9203 if (!$validate->isBool($fieldValue)) {
9204 $this->setFieldError($fieldKey, $validate->error);
9205 return false;
9206 } else {
9207 return true;
9208 }
9209 } elseif ($type == 'mail' || $type == 'email') {
9210 if (!$validate->isEmail($fieldValue)) {
9211 $this->setFieldError($fieldKey, $validate->error);
9212 return false;
9213 }
9214 } elseif ($type == 'url') {
9215 if (!$validate->isUrl($fieldValue)) {
9216 $this->setFieldError($fieldKey, $validate->error);
9217 return false;
9218 } else {
9219 return true;
9220 }
9221 } elseif ($type == 'phone') {
9222 if (!$validate->isPhone($fieldValue)) {
9223 $this->setFieldError($fieldKey, $validate->error);
9224 return false;
9225 } else {
9226 return true;
9227 }
9228 } elseif ($type == 'select' || $type == 'radio') {
9229 if (!isset($param['options'][$fieldValue])) {
9230 $this->error = $langs->trans('RequireValidValue');
9231 return false;
9232 } else {
9233 return true;
9234 }
9235 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9236 $param_list = array_keys($param['options']);
9237 $InfoFieldList = explode(":", $param_list[0]);
9238 $value_arr = explode(',', $fieldValue);
9239 $value_arr = array_map(array($this->db, 'escape'), $value_arr);
9240
9241 $selectkey = "rowid";
9242 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9243 $selectkey = $InfoFieldList[2];
9244 }
9245
9246 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9247 $this->setFieldError($fieldKey, $validate->error);
9248 return false;
9249 } else {
9250 return true;
9251 }
9252 } elseif ($type == 'link') {
9253 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9254 $InfoFieldList = explode(":", $param_list[0]);
9255 $classname = $InfoFieldList[0];
9256 $classpath = $InfoFieldList[1];
9257 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9258 $lastIsFetchableError = $validate->error;
9259
9260 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9261 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9262 return true;
9263 }
9264
9265 $this->setFieldError($fieldKey, $lastIsFetchableError);
9266 return false;
9267 } else {
9268 return true;
9269 }
9270 }
9271
9272 // if no test failed all is ok
9273 return true;
9274 }
9275
9289 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9290 {
9291 global $db, $conf, $langs, $action, $form, $hookmanager;
9292
9293 if (!is_object($form)) {
9294 $form = new Form($db);
9295 }
9296 if (!is_object($extrafields)) {
9297 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9298 return 'Bad parameter extrafields for showOptionals';
9299 }
9300 if (!is_array($extrafields->attributes[$this->table_element])) {
9301 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9302 }
9303
9304 $out = '';
9305
9306 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9307 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9308
9309 if (empty($reshook)) {
9310 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) {
9311 $out .= "\n";
9312 $out .= '<!-- commonobject:showOptionals --> ';
9313 $out .= "\n";
9314
9315 $nbofextrafieldsshown = 0;
9316 $e = 0; // var to manage the modulo (odd/even)
9317
9318 $lastseparatorkeyfound = '';
9319 $extrafields_collapse_num = '';
9320 $extrafields_collapse_num_old = '';
9321 $i = 0;
9322
9323 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9324 $i++;
9325
9326 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9327 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9328 continue;
9329 }
9330
9331 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9332 $enabled = 1;
9333 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9334 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9335 }
9336 if (empty($enabled)) {
9337 continue;
9338 }
9339
9340 $visibility = 1;
9341 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9342 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9343 }
9344
9345 $perms = 1;
9346 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9347 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9348 }
9349
9350 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9351 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9352 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9353 // We need to make sure, that the values of hidden extrafields are also part of $_POST.
9354 // Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9355 // TODO: We should not have this hidden field, and action='update' should be done only if field was POSTED by form.
9356 $ef_name = 'options_' . $key;
9357 $ef_value = isset($this->array_options[$ef_name]) ? $this->array_options[$ef_name] : '';
9358 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . dolPrintHTMLForAttribute($ef_value) . '" />' . "\n";
9359 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9360 } elseif ($mode == 'view' && empty($visibility)) {
9361 continue;
9362 }
9363 if (empty($perms)) {
9364 continue;
9365 }
9366
9367 // Load language if required
9368 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9369 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9370 }
9371
9372 $colspan = 0;
9373 $value = null;
9374 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9375 if (array_key_exists('cols', $params)) {
9376 $colspan = $params['cols'];
9377 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9378 $reg = array();
9379 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9380 $colspan = $reg[1];
9381 } else {
9382 $colspan = $params['colspan'];
9383 }
9384 }
9385 }
9386 $colspan = intval($colspan);
9387
9388 switch ($mode) {
9389 case "view":
9390 $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
9391 break;
9392 case "create":
9393 case "edit":
9394 // We get the value of property found with GETPOST so it takes into account:
9395 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9396 $check = 'alphanohtml';
9397 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9398 $check = 'restricthtml';
9399 }
9400 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9401 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9402 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9403 if (is_array($getposttemp)) {
9404 // $getposttemp is an array but following code expects a comma separated string
9405 $value = implode(",", $getposttemp);
9406 } else {
9407 $value = $getposttemp;
9408 }
9409 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9410 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9411 } else {
9412 $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.
9413 }
9414 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9415 break;
9416 }
9417
9418 $nbofextrafieldsshown++;
9419
9420 // Output value of the current field
9421 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9422 $extrafields_collapse_num = $key;
9423 /*
9424 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9425 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9426 $extrafield_param_list = array_keys($extrafield_param['options']);
9427
9428 if (count($extrafield_param_list) > 0) {
9429 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9430
9431 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9432 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9433 $extrafields_collapse_num = $key;
9434 }
9435 }
9436 }
9437 */
9438
9439 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9440 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9441
9442 $lastseparatorkeyfound = $key;
9443 } else {
9444 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9445
9446 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9447 $csstyle = '';
9448 if (is_array($params) && count($params) > 0) {
9449 if (array_key_exists('class', $params)) {
9450 $class .= $params['class'].' ';
9451 }
9452 if (array_key_exists('style', $params)) {
9453 $csstyle = $params['style'];
9454 }
9455 }
9456
9457 // add html5 elements
9458 $domData = ' data-element="extrafield"';
9459 $domData .= ' data-targetelement="'.$this->element.'"';
9460 $domData .= ' data-targetid="'.$this->id.'"';
9461
9462 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9463 if ($display_type == 'card') {
9464 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9465 $colspan = 0;
9466 }
9467
9468 if ($action == 'selectlines') {
9469 $colspan++;
9470 }
9471 }
9472
9473 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9474 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9475 $force_values_on_change_company = (
9476 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9477 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9478 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9479 );
9480 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9481 $value = $this->array_options['options_'.$key] ?? $value;
9482 }
9483
9484 // Convert date into timestamp format (value in memory must be a timestamp)
9485 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9486 $datenotinstring = null;
9487 if (array_key_exists('options_'.$key, $this->array_options)) {
9488 $datenotinstring = $this->array_options['options_'.$key];
9489 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9490 $datenotinstring = $this->db->jdate($datenotinstring);
9491 }
9492 }
9493 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9494 $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;
9495 }
9496 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9497 $datenotinstring = null;
9498 if (array_key_exists('options_'.$key, $this->array_options)) {
9499 $datenotinstring = $this->array_options['options_'.$key];
9500 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9501 $datenotinstring = $this->db->jdate($datenotinstring);
9502 }
9503 }
9504 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9505 $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;
9506 }
9507 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9508 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9509 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9510 $value = price2num($value);
9511 } elseif (isset($this->array_options['options_'.$key])) {
9512 $value = $this->array_options['options_'.$key];
9513 }
9514 }
9515
9516 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9517 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9518 if ($action == 'create' || $mode == 'create') {
9519 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9520 }
9521 }
9522
9523 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9524 if ($action == 'create') {
9525 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9526 }
9527 }
9528
9529
9530 $labeltoshow = $langs->trans($label);
9531 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9532 if ($display_type == 'card') {
9533 $out .= '<tr '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="field_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9534 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9535 $out .= '<td></td>';
9536 }
9537 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9538 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9539 $out .= ' tdtop';
9540 }
9541 } elseif ($display_type == 'line') {
9542 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9543 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9544 }
9545 //$out .= "titlefield";
9546 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9547 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9548 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9549 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9550 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9551 $out .= ' fieldrequired';
9552 }
9553 }
9554 $out .= '">';
9555 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9556 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9557 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9558 } else {
9559 $out .= $labeltoshow;
9560 }
9561 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9562 $out .= '&nbsp;<span style="color: red">*</span>';
9563 }
9564 } else {
9565 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9566 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9567 } else {
9568 $out .= $labeltoshow;
9569 }
9570 }
9571
9572 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9573
9574 // Second column
9575 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9576 if ($display_type == 'card') {
9577 // 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
9578 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9579 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9580 $out .= '>';
9581 } elseif ($display_type == 'line') {
9582 $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].'">';
9583 }
9584
9585 switch ($mode) {
9586 case "view":
9587 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9588 break;
9589 case "create":
9590 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9591 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9592 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9593 }
9594 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9595 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9596 break;
9597 case "edit":
9598 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9599 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9600 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9601 }
9602 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9603 break;
9604 }
9605
9606 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9607 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9608 $e++;
9609 }
9610 }
9611 $out .= "\n";
9612 // Add code to manage list depending on others
9613 if (!empty($conf->use_javascript_ajax)) {
9614 $out .= $this->getJSListDependancies();
9615 }
9616
9617 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9618
9619 if (empty($nbofextrafieldsshown)) {
9620 $out = '';
9621 }
9622 }
9623 }
9624
9625 $out .= $hookmanager->resPrint;
9626
9627 return $out;
9628 }
9629
9634 public function getJSListDependancies($type = '_extra')
9635 {
9636 $out = '
9637 <script nonce="'.getNonce().'">
9638 jQuery(document).ready(function() {
9639 function showOptions'.$type.'(child_list, parent_list, orig_select)
9640 {
9641 var val = $("select[name=\""+parent_list+"\"]").val();
9642 var parentVal = parent_list + ":" + val;
9643 if(typeof val == "string"){
9644 if(val != "") {
9645 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9646 $("select[name=\""+child_list+"\"] option[parent]").remove();
9647 $("select[name=\""+child_list+"\"]").append(options);
9648 } else {
9649 var options = orig_select.find("option[parent]").clone();
9650 $("select[name=\""+child_list+"\"] option[parent]").remove();
9651 $("select[name=\""+child_list+"\"]").append(options);
9652 }
9653 } else if(val > 0) {
9654 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9655 $("select[name=\""+child_list+"\"] option[parent]").remove();
9656 $("select[name=\""+child_list+"\"]").append(options);
9657 } else {
9658 var options = orig_select.find("option[parent]").clone();
9659 $("select[name=\""+child_list+"\"] option[parent]").remove();
9660 $("select[name=\""+child_list+"\"]").append(options);
9661 }
9662 }
9663 function setListDependencies'.$type.'() {
9664 jQuery("select option[parent]").parent().each(function() {
9665 var orig_select = {};
9666 var child_list = $(this).attr("name");
9667 orig_select[child_list] = $(this).clone();
9668 var parent = $(this).find("option[parent]:first").attr("parent");
9669 var infos = parent.split(":");
9670 var parent_list = infos[0];
9671
9672 //Hide daughters lists
9673 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9674 $("#"+child_list).hide();
9675 //Show mother lists
9676 } else if ($("#"+parent_list).val() != 0){
9677 $("#"+parent_list).show();
9678 }
9679 //Show the child list if the parent list value is selected
9680 $("select[name=\""+parent_list+"\"]").click(function() {
9681 if ($(this).val() != 0){
9682 $("#"+child_list).show()
9683 }
9684 });
9685
9686 //When we change parent list
9687 $("select[name=\""+parent_list+"\"]").change(function() {
9688 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9689 //Select the value 0 on child list after a change on the parent list
9690 $("#"+child_list).val(0).trigger("change");
9691 //Hide child lists if the parent value is set to 0
9692 if ($(this).val() == 0){
9693 $("#"+child_list).hide();
9694 }
9695 });
9696 });
9697 }
9698
9699 setListDependencies'.$type.'();
9700 });
9701 </script>'."\n";
9702 return $out;
9703 }
9704
9710 public function getRights()
9711 {
9712 global $user;
9713
9714 $module = empty($this->module) ? '' : $this->module;
9715 $element = $this->element;
9716
9717 if ($element == 'facturerec') {
9718 $element = 'facture';
9719 } elseif ($element == 'invoice_supplier_rec') {
9720 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9721 } elseif ($module && $user->hasRight($module, $element)) {
9722 // for modules built with ModuleBuilder
9723 return $user->hasRight($module, $element);
9724 }
9725
9726 return isset($user->rights->$element) ? $user->rights->$element : null;
9727 }
9728
9741 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9742 {
9743 global $hookmanager;
9744
9745 $parameters = array(
9746 'origin_id' => $origin_id,
9747 'dest_id' => $dest_id,
9748 'tables' => $tables,
9749 );
9750 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
9751 if ($reshook) {
9752 return true; // replacement code
9753 } elseif ($reshook < 0) {
9754 return $ignoreerrors === 1; // failure
9755 } // reshook = 0 => execute normal code
9756
9757 foreach ($tables as $table) {
9758 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
9759
9760 if (!$dbs->query($sql)) {
9761 if ($ignoreerrors) {
9762 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.
9763 }
9764 //$this->errors = $db->lasterror();
9765 return false;
9766 }
9767 }
9768
9769 return true;
9770 }
9771
9784 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9785 {
9786 foreach ($tables as $table) {
9787 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
9788
9789 if (!$dbs->query($sql)) {
9790 if ($ignoreerrors) {
9791 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.
9792 }
9793 //$this->errors = $db->lasterror();
9794 return false;
9795 }
9796 }
9797
9798 return true;
9799 }
9800
9813 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
9814 {
9815 global $conf;
9816
9817 $buyPrice = 0;
9818
9819 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
9820 // When ForceBuyingPriceIfNull is set
9821 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
9822 } else {
9823 // Get cost price for margin calculation
9824 if (!empty($fk_product) && $fk_product > 0) {
9825 $result = 0;
9826 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
9827 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9828 $product = new Product($this->db);
9829 $result = $product->fetch($fk_product);
9830 if ($result <= 0) {
9831 $this->errors[] = 'ErrorProductIdDoesNotExists';
9832 return -1;
9833 }
9834 if ($product->cost_price > 0) {
9835 $buyPrice = $product->cost_price;
9836 } elseif ($product->pmp > 0) {
9837 $buyPrice = $product->pmp;
9838 }
9839 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
9840 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9841 $product = new Product($this->db);
9842 $result = $product->fetch($fk_product);
9843 if ($result <= 0) {
9844 $this->errors[] = 'ErrorProductIdDoesNotExists';
9845 return -1;
9846 }
9847 if ($product->pmp > 0) {
9848 $buyPrice = $product->pmp;
9849 }
9850 }
9851
9852 if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
9853 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
9854 $productFournisseur = new ProductFournisseur($this->db);
9855 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
9856 $buyPrice = $productFournisseur->fourn_unitprice;
9857 } elseif ($result < 0) {
9858 $this->errors[] = $productFournisseur->error;
9859 return -2;
9860 }
9861 }
9862 }
9863 }
9864
9865 return (float) $buyPrice;
9866 }
9867
9875 public function getDataToShowPhoto($modulepart, $imagesize)
9876 {
9877 // See getDataToShowPhoto() implemented by Product for example.
9878 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
9879 }
9880
9881
9882 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
9902 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')
9903 {
9904 // phpcs:enable
9905 global $user, $langs;
9906
9907 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
9908 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
9909
9910 $sortfield = 'position_name';
9911 $sortorder = 'asc';
9912
9913 $dir = $sdir.'/';
9914 $pdir = '/';
9915
9916 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9917 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9918
9919 // For backward compatibility
9920 if ($modulepart == 'product') {
9921 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
9922 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9923 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9924 }
9925 }
9926 if ($modulepart == 'category') {
9927 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9928 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9929 }
9930
9931 // Defined relative dir to DOL_DATA_ROOT
9932 $relativedir = '';
9933 if ($dir) {
9934 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
9935 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
9936 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
9937 }
9938
9939 $dirthumb = $dir.'thumbs/';
9940 $pdirthumb = $pdir.'thumbs/';
9941
9942 $return = '<!-- Photo -->'."\n";
9943 $nbphoto = 0;
9944
9945 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
9946
9947 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
9948 {
9949 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
9950 $filearray=array_merge($filearray, $filearrayold);
9951 }*/
9952
9953 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
9954 '@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';
9955
9956 if (count($filearray)) {
9957 if ($sortfield && $sortorder) {
9958 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
9959 }
9960
9961 foreach ($filearray as $key => $val) {
9962 $photo = '';
9963 $file = $val['name'];
9964
9965 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
9966 if (image_format_supported($file) >= 0) {
9967 $nbphoto++;
9968 $photo = $file;
9969 $viewfilename = $file;
9970
9971 if ($size == 1 || $size == 'small') { // Format vignette
9972 // Find name of thumb file
9973 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
9974 if (!dol_is_file($dirthumb.$photo_vignette)) {
9975 // The thumb does not exists, so we will use the original file
9976 $dirthumb = $dir;
9977 $pdirthumb = $pdir;
9978 $photo_vignette = basename($file);
9979 }
9980
9981 // Get filesize of original file
9982 $imgarray = dol_getImageSize($dir.$photo);
9983
9984 if ($nbbyrow > 0) {
9985 if ($nbphoto == 1) {
9986 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
9987 }
9988
9989 if ($nbphoto % $nbbyrow == 1) {
9990 $return .= '<tr class="center valignmiddle" style="border: 1px">';
9991 }
9992 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
9993 } elseif ($nbbyrow < 0) {
9994 $return .= '<div class="inline-block">'."\n";
9995 }
9996
9997 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
9998 if (empty($nolink)) {
9999 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
10000 if ($urladvanced) {
10001 $return .= '<a href="'.$urladvanced.'">';
10002 } else {
10003 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
10004 }
10005 }
10006
10007 // Show image (width height=$maxHeight)
10008 // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
10009 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
10010 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
10011 if ($overwritetitle) {
10012 if (is_numeric($overwritetitle)) {
10013 $alt = '';
10014 } else {
10015 $alt = $overwritetitle;
10016 }
10017 }
10018 if (empty($cache) && !empty($val['label'])) {
10019 // label is md5 of file
10020 // use it in url to say we want to cache this version of the file
10021 $cache = $val['label'];
10022 }
10023 if ($usesharelink) {
10024 if (array_key_exists('share', $val) && $val['share']) {
10025 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10026 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10027 $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).'">';
10028 } else {
10029 $return .= '<!-- Show original file -->';
10030 $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).'">';
10031 }
10032 } else {
10033 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10034 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10035 }
10036 } else {
10037 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10038 $return .= '<!-- Show thumb -->';
10039 $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).'">';
10040 } else {
10041 $return .= '<!-- Show original file -->';
10042 $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).'">';
10043 }
10044 }
10045
10046 if (empty($nolink)) {
10047 $return .= '</a>';
10048 }
10049
10050 if ($showfilename) {
10051 $return .= '<br>'.$viewfilename;
10052 }
10053 if ($showaction) {
10054 $return .= '<br>';
10055 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10056 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10057 $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>';
10058 }
10059 // Special case for product
10060 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10061 // Link to resize
10062 $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; ';
10063
10064 // Link to delete
10065 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10066 $return .= img_delete().'</a>';
10067 }
10068 }
10069 $return .= "\n";
10070
10071 if ($nbbyrow > 0) {
10072 $return .= '</td>';
10073 if (($nbphoto % $nbbyrow) == 0) {
10074 $return .= '</tr>';
10075 }
10076 } elseif ($nbbyrow < 0) {
10077 $return .= '</div>'."\n";
10078 }
10079 }
10080
10081 if (empty($size)) { // Format origine
10082 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10083
10084 if ($showfilename) {
10085 $return .= '<br>'.$viewfilename;
10086 }
10087 if ($showaction) {
10088 // Special case for product
10089 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10090 // Link to resize
10091 $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; ';
10092
10093 // Link to delete
10094 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10095 $return .= img_delete().'</a>';
10096 }
10097 }
10098 }
10099
10100 // On continue ou on arrete de boucler ?
10101 if ($nbmax && $nbphoto >= $nbmax) {
10102 break;
10103 }
10104 }
10105 }
10106
10107 if ($size == 1 || $size == 'small') {
10108 if ($nbbyrow > 0) {
10109 // Ferme tableau
10110 while ($nbphoto % $nbbyrow) {
10111 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10112 $nbphoto++;
10113 }
10114
10115 if ($nbphoto) {
10116 $return .= '</table>';
10117 }
10118 }
10119 }
10120 }
10121
10122 $this->nbphoto = $nbphoto;
10123
10124 return $return;
10125 }
10126
10127
10134 protected function isArray($info)
10135 {
10136 if (is_array($info)) {
10137 if (isset($info['type']) && $info['type'] == 'array') {
10138 return true;
10139 } else {
10140 return false;
10141 }
10142 }
10143 return false;
10144 }
10145
10152 public function isDate($info)
10153 {
10154 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10155 return true;
10156 }
10157 return false;
10158 }
10159
10166 public function isDuration($info)
10167 {
10168 if (is_array($info)) {
10169 if (isset($info['type']) && ($info['type'] == 'duration')) {
10170 return true;
10171 } else {
10172 return false;
10173 }
10174 } else {
10175 return false;
10176 }
10177 }
10178
10185 public function isInt($info)
10186 {
10187 if (is_array($info)) {
10188 if (isset($info['type']) && (preg_match('/^(?:tiny|small|medium|big)?int|int$/i', $info['type']))) {
10189 return true;
10190 } else {
10191 return false;
10192 }
10193 } else {
10194 return false;
10195 }
10196 }
10197
10204 public function isFloat($info)
10205 {
10206 if (is_array($info)) {
10207 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10208 return true;
10209 } else {
10210 return false;
10211 }
10212 }
10213 return false;
10214 }
10215
10222 public function isText($info)
10223 {
10224 if (is_array($info)) {
10225 if (isset($info['type']) && $info['type'] == 'text') {
10226 return true;
10227 } else {
10228 return false;
10229 }
10230 }
10231 return false;
10232 }
10233
10240 protected function canBeNull($info)
10241 {
10242 if (is_array($info)) {
10243 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10244 return true;
10245 } else {
10246 return false;
10247 }
10248 }
10249 return true;
10250 }
10251
10258 protected function isForcedToNullIfZero($info)
10259 {
10260 if (is_array($info)) {
10261 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10262 return true;
10263 } else {
10264 return false;
10265 }
10266 }
10267 return false;
10268 }
10269
10276 protected function isIndex($info)
10277 {
10278 if (is_array($info)) {
10279 if (array_key_exists('index', $info) && $info['index'] == true) {
10280 return true;
10281 } else {
10282 return false;
10283 }
10284 }
10285 return false;
10286 }
10287
10288
10297 protected function setSaveQuery()
10298 {
10299 global $conf;
10300
10301 $queryarray = array();
10302 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10303 // Depending on field type ('datetime', ...)
10304 if ($this->isDate($info)) {
10305 if (empty($this->{$field})) {
10306 $queryarray[$field] = null;
10307 } else {
10308 $queryarray[$field] = $this->db->idate($this->{$field});
10309 }
10310 } elseif ($this->isDuration($info)) {
10311 // $this->{$field} may be null, '', 0, '0', 123, '123'
10312 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10313 if (!isset($this->{$field})) {
10314 if (!empty($info['default'])) {
10315 $queryarray[$field] = $info['default'];
10316 } else {
10317 $queryarray[$field] = 0;
10318 }
10319 } else {
10320 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10321 }
10322 } else {
10323 $queryarray[$field] = null;
10324 }
10325 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10326 if ($field == 'entity' && is_null($this->{$field})) {
10327 $queryarray[$field] = ((int) $conf->entity);
10328 } else {
10329 // $this->{$field} may be null, '', 0, '0', 123, '123'
10330 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10331 if (!isset($this->{$field})) {
10332 $queryarray[$field] = 0;
10333 } elseif ($this->isInt($info)) {
10334 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10335 } elseif ($this->isFloat($info)) {
10336 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10337 }
10338 } else {
10339 $queryarray[$field] = null;
10340 }
10341 }
10342 } else {
10343 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10344 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10345 $queryarray[$field] = $this->{$field};
10346 }
10347
10348 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10349 unset($queryarray[$field]);
10350 }
10351 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10352 $queryarray[$field] = null; // May force 0 to null
10353 }
10354 }
10355
10356 return $queryarray;
10357 }
10358
10365 public function setVarsFromFetchObj(&$obj)
10366 {
10367 global $db;
10368
10369 foreach ($this->fields as $field => $info) {
10370 if ($this->isDate($info)) {
10371 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') {
10372 $this->$field = '';
10373 } else {
10374 $this->$field = $db->jdate($obj->$field);
10375 }
10376 } elseif ($this->isInt($info)) {
10377 if ($field == 'rowid') {
10378 $this->id = (int) $obj->$field;
10379 } else {
10380 if ($this->isForcedToNullIfZero($info)) {
10381 if (empty($obj->$field)) {
10382 $this->$field = null;
10383 } else {
10384 $this->$field = (int) $obj->$field;
10385 }
10386 } else {
10387 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10388 $this->$field = (int) $obj->$field;
10389 } else {
10390 $this->$field = null;
10391 }
10392 }
10393 }
10394 } elseif ($this->isFloat($info)) {
10395 if ($this->isForcedToNullIfZero($info)) {
10396 if (empty($obj->$field)) {
10397 $this->$field = null;
10398 } else {
10399 $this->$field = (float) $obj->$field;
10400 }
10401 } else {
10402 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10403 $this->$field = (float) $obj->$field;
10404 } else {
10405 $this->$field = null;
10406 }
10407 }
10408 } else {
10409 $this->$field = isset($obj->$field) ? $obj->$field : null;
10410 }
10411 }
10412
10413 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10414 if (!isset($this->fields['ref']) && isset($this->id)) {
10415 $this->ref = (string) $this->id;
10416 }
10417 }
10418
10423 public function emtpyObjectVars()
10424 {
10425 foreach ($this->fields as $field => $arr) {
10426 $this->$field = null;
10427 }
10428 }
10429
10437 public function getFieldList($alias = '', $excludefields = array())
10438 {
10439 $keys = array_keys($this->fields);
10440 if (!empty($alias)) {
10441 $keys_with_alias = array();
10442 foreach ($keys as $fieldname) {
10443 if (!empty($excludefields)) {
10444 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10445 continue;
10446 }
10447 }
10448 $keys_with_alias[] = $alias . '.' . $fieldname;
10449 }
10450 return implode(', ', $keys_with_alias);
10451 } else {
10452 return implode(', ', $keys);
10453 }
10454 }
10455
10463 protected function quote($value, $fieldsentry)
10464 {
10465 if (is_null($value)) {
10466 return 'NULL';
10467 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10468 return price2num((string) $value);
10469 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10470 return (int) $value;
10471 } elseif ($fieldsentry['type'] == 'boolean') {
10472 if ($value) {
10473 return 'true';
10474 } else {
10475 return 'false';
10476 }
10477 } else {
10478 return "'".$this->db->escape($value)."'";
10479 }
10480 }
10481
10482
10490 public function createCommon(User $user, $notrigger = 0)
10491 {
10492 global $langs;
10493
10494 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10495
10496 $error = 0;
10497
10498 $now = dol_now();
10499
10500 $fieldvalues = $this->setSaveQuery();
10501
10502 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10503
10504 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10505 $fieldvalues['date_creation'] = $this->db->idate($now);
10506 $this->date_creation = $this->db->idate($now);
10507 }
10508 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10509 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10510 $fieldvalues['fk_user_creat'] = $user->id;
10511 $this->fk_user_creat = $user->id;
10512 }
10513 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10514 $fieldvalues['user_creation_id'] = $user->id;
10515 $this->user_creation_id = $user->id;
10516 }
10517 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10518 // @phan-suppress-next-line PhanUndeclaredProperty
10519 $tmparray = dol_hash($this->pass, '0', 0, 1);
10520 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10521 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10522 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10523 }
10524 }
10525 if (array_key_exists('ref', $fieldvalues)) {
10526 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10527 }
10528
10529 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10530
10531 $keys = array();
10532 $values = array(); // Array to store string forged for SQL syntax
10533 foreach ($fieldvalues as $k => $v) {
10534 $keys[$k] = $k;
10535 $value = $this->fields[$k];
10536 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10537 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10538 }
10539
10540 // Clean and check mandatory
10541 foreach ($keys as $key) {
10542 if (!isset($this->fields[$key])) {
10543 continue;
10544 }
10545 $key_fields = $this->fields[$key];
10546
10547 // If field is an implicit foreign key field (so type = 'integer:...')
10548 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10549 $values[$key] = '';
10550 }
10551 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10552 $values[$key] = '';
10553 }
10554
10555 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10556 $error++;
10557 $langs->load("errors");
10558 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10559 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10560 }
10561
10562 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10563 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && !is_null($key_fields['default'])) {
10564 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10565 }
10566
10567 // If field is an implicit foreign key field (so type = 'integer:...')
10568 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10569 if (isset($key_fields['default'])) {
10570 $values[$key] = ((int) $key_fields['default']);
10571 } else {
10572 $values[$key] = 'null';
10573 }
10574 }
10575 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10576 $values[$key] = 'null';
10577 }
10578 }
10579
10580 if ($error) {
10581 return -1;
10582 }
10583
10584 $this->db->begin();
10585
10586 if (!$error) {
10587 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10588 $sql .= " (".implode(", ", $keys).')';
10589 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10590
10591 $res = $this->db->query($sql);
10592 if (!$res) {
10593 $error++;
10594 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10595 $this->errors[] = "ErrorRefAlreadyExists";
10596 } else {
10597 $this->errors[] = $this->db->lasterror();
10598 }
10599 }
10600 }
10601
10602 if (!$error) {
10603 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10604 }
10605
10606 // If we have a field ref with a default value of (PROV)
10607 if (!$error) {
10608 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10609 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)') {
10610 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10611 $resqlupdate = $this->db->query($sql);
10612
10613 if ($resqlupdate === false) {
10614 $error++;
10615 $this->errors[] = $this->db->lasterror();
10616 } else {
10617 $this->ref = '(PROV'.$this->id.')';
10618 }
10619 }
10620 }
10621
10622 // Create extrafields
10623 if (!$error) {
10624 $result = $this->insertExtraFields();
10625 if ($result < 0) {
10626 $error++;
10627 }
10628 }
10629
10630 // Create lines
10631 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10632 foreach ($this->lines as $line) {
10633 $keyforparent = $this->fk_element;
10634 $line->$keyforparent = $this->id;
10635
10636 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10637 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10638 if (!is_object($line)) {
10639 $line = (object) $line;
10640 }
10641
10642 $result = 0;
10643 if (method_exists($line, 'insert')) {
10644 $result = $line->insert($user, 1);
10645 } elseif (method_exists($line, 'create')) {
10646 $result = $line->create($user, 1);
10647 }
10648 if ($result < 0) {
10649 $this->error = $line->error;
10650 $this->db->rollback();
10651 return -1;
10652 }
10653 }
10654 }
10655
10656 // Triggers
10657 if (!$error && !$notrigger) {
10658 // Call triggers
10659 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10660 if ($result < 0) {
10661 $error++;
10662 }
10663 // End call triggers
10664 }
10665
10666 // Commit or rollback
10667 if ($error) {
10668 $this->db->rollback();
10669 return -1;
10670 } else {
10671 $this->db->commit();
10672 return $this->id;
10673 }
10674 }
10675
10676
10686 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
10687 {
10688 if (empty($id) && empty($ref) && empty($morewhere)) {
10689 return -1;
10690 }
10691
10692 $fieldlist = $this->getFieldList('t');
10693 if (empty($fieldlist)) {
10694 return 0;
10695 }
10696
10697 $sql = "SELECT ".$fieldlist;
10698 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
10699
10700 if (!empty($id)) {
10701 $sql .= ' WHERE t.rowid = '.((int) $id);
10702 } elseif (!empty($ref)) {
10703 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
10704 } else {
10705 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
10706 }
10707 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
10708 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
10709 }
10710 if ($morewhere) {
10711 $sql .= $morewhere;
10712 }
10713 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
10714
10715 $res = $this->db->query($sql);
10716 if ($res) {
10717 $obj = $this->db->fetch_object($res);
10718 if ($obj) {
10719 $this->setVarsFromFetchObj($obj);
10720
10721 // Retrieve all extrafield
10722 // fetch optionals attributes and labels
10723 if (empty($noextrafields)) {
10724 $result = $this->fetch_optionals();
10725 if ($result < 0) {
10726 $this->error = $this->db->lasterror();
10727 $this->errors[] = $this->error;
10728 return -4;
10729 }
10730 }
10731
10732 return $this->id;
10733 } else {
10734 return 0;
10735 }
10736 } else {
10737 $this->error = $this->db->lasterror();
10738 $this->errors[] = $this->error;
10739 return -1;
10740 }
10741 }
10742
10750 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
10751 {
10752 $objectlineclassname = get_class($this).'Line';
10753 if (!class_exists($objectlineclassname)) {
10754 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
10755 return -1;
10756 }
10757
10758 $objectline = new $objectlineclassname($this->db);
10759 '@phan-var-force CommonObjectLine $objectline';
10760
10761 $sql = "SELECT ".$objectline->getFieldList('l');
10762 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
10763 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
10764 if ($morewhere) {
10765 $sql .= $morewhere;
10766 }
10767 if (isset($objectline->fields['position'])) {
10768 $sql .= $this->db->order('position', 'ASC');
10769 }
10770
10771 $resql = $this->db->query($sql);
10772 if ($resql) {
10773 $num_rows = $this->db->num_rows($resql);
10774 $i = 0;
10775 $this->lines = array();
10776 while ($i < $num_rows) {
10777 $obj = $this->db->fetch_object($resql);
10778 if ($obj) {
10779 $newline = new $objectlineclassname($this->db);
10780 '@phan-var-force CommonObjectLine $newline';
10781 $newline->setVarsFromFetchObj($obj);
10782
10783 // Load also extrafields for the line
10784 if (empty($noextrafields)) {
10785 $newline->fetch_optionals();
10786 }
10787
10788 $this->lines[] = $newline;
10789 }
10790 $i++;
10791 }
10792
10793 return 1;
10794 } else {
10795 $this->error = $this->db->lasterror();
10796 $this->errors[] = $this->error;
10797 return -1;
10798 }
10799 }
10800
10808 public function updateCommon(User $user, $notrigger = 0)
10809 {
10810 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
10811
10812 $error = 0;
10813
10814 $now = dol_now();
10815
10816 // $this->oldcopy should have been set by the caller of update
10817 //if (empty($this->oldcopy)) {
10818 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
10819 // $this->oldcopy = dol_clone($this, 2);
10820 //}
10821
10822 $fieldvalues = $this->setSaveQuery();
10823
10824 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10825
10826 if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) {
10827 $fieldvalues['date_modification'] = $this->db->idate($now);
10828 }
10829 if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) {
10830 $fieldvalues['fk_user_modif'] = $user->id;
10831 }
10832 if (array_key_exists('user_modification_id', $fieldvalues) && !($fieldvalues['user_modification_id'] > 0)) {
10833 $fieldvalues['user_modification_id'] = $user->id;
10834 }
10835 // @phan-suppress-next-line PhanUndeclaredProperty
10836 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
10837 // @phan-suppress-next-line PhanUndeclaredProperty
10838 $tmparray = dol_hash($this->pass, '0', 0, 1);
10839 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10840 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10841 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10842 }
10843 }
10844 if (array_key_exists('ref', $fieldvalues)) {
10845 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10846 }
10847
10848 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
10849
10850 // Add quotes and escape on fields with type string
10851 $keys = array();
10852 $values = array();
10853 $tmp = array();
10854 foreach ($fieldvalues as $k => $v) {
10855 $keys[$k] = $k;
10856 $value = $this->fields[$k];
10857 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10858 $values[$k] = $this->quote($v, $value);
10859 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
10860 // Clean values for text with selectbox
10861 $v = preg_replace('/\s/', ',', $v);
10862 $v = preg_replace('/,+/', ',', $v);
10863 }
10864 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10865 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
10866 }
10867
10868 // Clean and check mandatory fields
10869 foreach ($keys as $key) {
10870 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
10871 $values[$key] = ''; // This is an implicit foreign key field
10872 }
10873 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
10874 $values[$key] = ''; // This is an explicit foreign key field
10875 }
10876
10877 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
10878 /*
10879 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
10880 {
10881 $error++;
10882 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
10883 }*/
10884 }
10885
10886 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
10887
10888 $this->db->begin();
10889
10890 if (!$error) {
10891 $res = $this->db->query($sql);
10892 if (!$res) {
10893 $error++;
10894 $this->errors[] = $this->db->lasterror();
10895 }
10896 }
10897
10898 // Update extrafield
10899 if (!$error) {
10900 $result = $this->insertExtraFields(); // This delete and reinsert extrafields
10901 if ($result < 0) {
10902 $error++;
10903 }
10904 }
10905
10906 // Triggers
10907 if (!$error && !$notrigger) {
10908 // Call triggers
10909 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
10910 if ($result < 0) {
10911 $error++;
10912 } //Do also here what you must do to rollback action if trigger fail
10913 // End call triggers
10914 }
10915
10916 // Commit or rollback
10917 if ($error) {
10918 $this->db->rollback();
10919 return -1;
10920 } else {
10921 $this->db->commit();
10922 return $this->id;
10923 }
10924 }
10925
10934 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
10935 {
10936 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
10937
10938 $error = 0;
10939
10940 $this->db->begin();
10941
10942 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
10943 foreach ($this->childtables as $table) {
10944 $sql = "DELETE FROM ".$this->db->prefix().$table." WHERE ".$this->fk_element." = ".((int) $this->id);
10945 $resql = $this->db->query($sql);
10946 if (!$resql) {
10947 $this->error = $this->db->lasterror();
10948 $this->errors[] = $this->error;
10949 $this->db->rollback();
10950 return -1;
10951 }
10952 }
10953 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
10954 $objectisused = $this->isObjectUsed($this->id);
10955 if (!empty($objectisused)) {
10956 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
10957 $this->error = 'ErrorRecordHasChildren';
10958 $this->errors[] = $this->error;
10959 $this->db->rollback();
10960 return 0;
10961 }
10962 }
10963
10964 // Delete cascade first
10965 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
10966 foreach ($this->childtablesoncascade as $tabletodelete) {
10967 $deleteFromObject = explode(':', $tabletodelete, 4);
10968 if (count($deleteFromObject) >= 2) {
10969 $className = str_replace('@', '', $deleteFromObject[0]);
10970 $filePath = $deleteFromObject[1];
10971 $columnName = $deleteFromObject[2];
10972 $filter = '';
10973 if (!empty($deleteFromObject[3])) {
10974 $filter = $deleteFromObject[3];
10975 }
10976
10977 if (dol_include_once($filePath)) {
10978 $childObject = new $className($this->db);
10979 if (method_exists($childObject, 'deleteByParentField')) {
10980 '@phan-var-force CommonObject $childObject';
10981 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
10982 if ($result < 0) {
10983 $error++;
10984 $this->errors[] = $childObject->error;
10985 break;
10986 }
10987 } else {
10988 $error++;
10989 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
10990 break;
10991 }
10992 } else {
10993 $error++;
10994 $this->errors[] = 'Cannot include child class file '.$filePath;
10995 break;
10996 }
10997 } else {
10998 // Delete record in child table
10999 $sql = "DELETE FROM ".$this->db->prefix().$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
11000
11001 $resql = $this->db->query($sql);
11002 if (!$resql) {
11003 $error++;
11004 $this->error = $this->db->lasterror();
11005 $this->errors[] = $this->error;
11006 break;
11007 }
11008 }
11009 }
11010 }
11011
11012 if (!$error) {
11013 if (!$notrigger) {
11014 // Call triggers
11015 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11016 if ($result < 0) {
11017 $error++;
11018 } // Do also here what you must do to rollback action if trigger fail
11019 // End call triggers
11020 }
11021 }
11022
11023 // Delete llx_ecm_files
11024 if (!$error) {
11025 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11026 if (!$res) {
11027 $error++;
11028 }
11029 }
11030
11031 // Delete linked object
11032 $res = $this->deleteObjectLinked();
11033 if ($res < 0) {
11034 $error++;
11035 }
11036
11037 if (!$error && !empty($this->isextrafieldmanaged)) {
11038 $result = $this->deleteExtraFields();
11039 if ($result < 0) {
11040 $error++;
11041 }
11042 }
11043
11044 if (!$error) {
11045 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11046
11047 $resql = $this->db->query($sql);
11048 if (!$resql) {
11049 $error++;
11050 $this->errors[] = $this->db->lasterror();
11051 }
11052 }
11053
11054 // Commit or rollback
11055 if ($error) {
11056 $this->db->rollback();
11057 return -1;
11058 } else {
11059 $this->db->commit();
11060 return 1;
11061 }
11062 }
11063
11075 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11076 {
11077 global $user;
11078
11079 $error = 0;
11080 $deleted = 0;
11081
11082 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11083
11084 if (!empty($parentId) && !empty($parentField)) {
11085 if (empty($this->table_element)) {
11086 $this->error = 'Property table_element for object is not defined';
11087 $this->errors[] = $this->error;
11088 $error++;
11089 return -1;
11090 }
11091 if (!method_exists($this, 'fetch')) {
11092 $this->error = 'Method fetch for object is not defined';
11093 $this->errors[] = $this->error;
11094 $error++;
11095 return -1;
11096 }
11097 if (!method_exists($this, 'delete')) {
11098 $this->error = 'Method delete for object is not defined';
11099 $this->errors[] = $this->error;
11100 $error++;
11101 return -1;
11102 }
11103
11104 $this->db->begin();
11105
11106 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11107 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11108
11109 // Manage filter
11110 $errormessage = '';
11111 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11112 if ($errormessage) {
11113 $this->errors[] = $errormessage;
11114 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11115 return -1;
11116 }
11117
11118 $resql = $this->db->query($sql);
11119 if (!$resql) {
11120 $this->errors[] = $this->db->lasterror();
11121 $error++;
11122 } else {
11123 while ($obj = $this->db->fetch_object($resql)) {
11124 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11125 if ($result < 0) {
11126 $error++;
11127 $this->errors[] = $this->error;
11128 } else {
11129 $result = $this->delete($user); // @phpstan-ignore-line
11130 if ($result < 0) {
11131 $error++;
11132 $this->errors[] = $this->error;
11133 } else {
11134 $deleted++;
11135 }
11136 }
11137 }
11138 }
11139
11140 if (empty($error)) {
11141 $this->db->commit();
11142 return $deleted;
11143 } else {
11144 $this->error = implode(', ', $this->errors);
11145 $this->db->rollback();
11146 return $error * -1;
11147 }
11148 }
11149
11150 return $deleted;
11151 }
11152
11161 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11162 {
11163 $error = 0;
11164
11165 $tmpforobjectclass = get_class($this);
11166 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11167
11168 $this->db->begin();
11169
11170 // Call trigger
11171 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11172 if ($result < 0) {
11173 $error++;
11174 }
11175 // End call triggers
11176
11177 if (empty($error)) {
11178 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11179 $sql .= " WHERE rowid = ".((int) $idline);
11180
11181 $resql = $this->db->query($sql);
11182 if (!$resql) {
11183 $this->error = "Error ".$this->db->lasterror();
11184 $error++;
11185 }
11186 }
11187
11188 if (empty($error)) {
11189 // Remove extrafields
11190 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11191 '@phan-var-force CommonObjectLine $tmpobjectline';
11192 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11193 $tmpobjectline->id = $idline;
11194 $result = $tmpobjectline->deleteExtraFields();
11195 if ($result < 0) {
11196 $error++;
11197 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11198 }
11199 }
11200 }
11201
11202 if (empty($error)) {
11203 $this->db->commit();
11204 return 1;
11205 } else {
11206 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11207 $this->db->rollback();
11208 return -1;
11209 }
11210 }
11211
11212
11222 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11223 {
11224 $error = 0;
11225
11226 $this->db->begin();
11227
11228 $statusfield = 'status';
11229 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11230 $statusfield = 'fk_statut';
11231 }
11232
11233 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11234 $sql .= " SET ".$statusfield." = ".((int) $status);
11235 $sql .= " WHERE rowid = ".((int) $this->id);
11236
11237 if ($this->db->query($sql)) {
11238 if (!$error) {
11239 $this->oldcopy = clone $this;
11240 }
11241
11242 if (!$error && !$notrigger) {
11243 // Call trigger
11244 $result = $this->call_trigger($triggercode, $user);
11245 if ($result < 0) {
11246 $error++;
11247 }
11248 }
11249
11250 if (!$error) {
11251 $this->status = $status;
11252 if (property_exists($this, 'statut')) { // For backward compatibility
11253 $this->statut = $status;
11254 }
11255 $this->db->commit();
11256 return 1;
11257 } else {
11258 $this->db->rollback();
11259 return -1;
11260 }
11261 } else {
11262 $this->error = $this->db->error();
11263 $this->db->rollback();
11264 return -1;
11265 }
11266 }
11267
11274 public function initAsSpecimenCommon()
11275 {
11276 global $user;
11277
11278 $this->id = 0;
11279 $this->specimen = 1;
11280 $fields = array(
11281 'label' => 'This is label',
11282 'ref' => 'ABCD1234',
11283 'description' => 'This is a description',
11284 'qty' => 123.12,
11285 'note_public' => 'Public note',
11286 'note_private' => 'Private note',
11287 'date_creation' => (dol_now() - 3600 * 48),
11288 'date_modification' => (dol_now() - 3600 * 24),
11289 'fk_user_creat' => $user->id,
11290 'fk_user_modif' => $user->id,
11291 'date' => dol_now(),
11292 );
11293 foreach ($fields as $key => $value) {
11294 if (array_key_exists($key, $this->fields)) {
11295 $this->{$key} = $value; // @phpstan-ignore-line
11296 }
11297 }
11298
11299 // Force values to default values when known
11300 if (property_exists($this, 'fields')) {
11301 foreach ($this->fields as $key => $value) {
11302 // If fields are already set, do nothing
11303 if (array_key_exists($key, $fields)) {
11304 continue;
11305 }
11306
11307 if (!empty($value['default'])) {
11308 $this->$key = $value['default'];
11309 }
11310 }
11311 }
11312
11313 return 1;
11314 }
11315
11316
11317 /* Part for comments */
11318
11324 public function fetchComments()
11325 {
11326 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11327
11328 $comment = new Comment($this->db);
11329 $result = $comment->fetchAllFor($this->element, $this->id);
11330 if ($result < 0) {
11331 $this->errors = array_merge($this->errors, $comment->errors);
11332 return -1;
11333 } else {
11334 $this->comments = $comment->comments;
11335 }
11336 return count($this->comments);
11337 }
11338
11344 public function getNbComments()
11345 {
11346 return count($this->comments);
11347 }
11348
11355 public function trimParameters($parameters)
11356 {
11357 if (!is_array($parameters)) {
11358 return;
11359 }
11360 foreach ($parameters as $parameter) {
11361 if (isset($this->$parameter)) {
11362 $this->$parameter = trim($this->$parameter);
11363 }
11364 }
11365 }
11366
11367 /* Part for categories/tags */
11368
11379 public function getCategoriesCommon($type_categ)
11380 {
11381 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11382
11383 // Get current categories
11384 $c = new Categorie($this->db);
11385 $existing = $c->containing($this->id, $type_categ, 'id');
11386
11387 return $existing;
11388 }
11389
11402 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11403 {
11404 // Handle single category
11405 if (!is_array($categories)) {
11406 $categories = array($categories);
11407 }
11408
11409 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11410
11411 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11412
11413 if (empty($type_categ)) {
11414 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11415 return -1;
11416 }
11417
11418 // Get current categories
11419 $c = new Categorie($this->db);
11420 $existing = $c->containing($this->id, $type_categ, 'id');
11421 if ($remove_existing) {
11422 // Diff
11423 if (is_array($existing)) {
11424 $to_del = array_diff($existing, $categories);
11425 $to_add = array_diff($categories, $existing);
11426 } else {
11427 $to_del = array(); // Nothing to delete
11428 $to_add = $categories;
11429 }
11430 } else {
11431 $to_del = array(); // Nothing to delete
11432 $to_add = array_diff($categories, $existing);
11433 }
11434
11435 $error = 0;
11436 $ok = 0;
11437
11438 // Process
11439 foreach ($to_del as $del) {
11440 if ($c->fetch($del) > 0) {
11441 $result = $c->del_type($this, $type_categ);
11442 if ($result < 0) {
11443 $error++;
11444 $this->error = $c->error;
11445 $this->errors = $c->errors;
11446 break;
11447 } else {
11448 $ok += $result;
11449 }
11450 }
11451 }
11452 foreach ($to_add as $add) {
11453 if ($c->fetch($add) > 0) {
11454 $result = $c->add_type($this, $type_categ);
11455 if ($result < 0) {
11456 $error++;
11457 $this->error = $c->error;
11458 $this->errors = $c->errors;
11459 break;
11460 } else {
11461 $ok += $result;
11462 }
11463 }
11464 }
11465
11466 return $error ? (-1 * $error) : $ok;
11467 }
11468
11477 public function cloneCategories($fromId, $toId, $type = '')
11478 {
11479 $this->db->begin();
11480
11481 if (empty($type)) {
11482 $type = $this->table_element;
11483 }
11484
11485 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11486 $categorystatic = new Categorie($this->db);
11487
11488 $sql = "INSERT INTO ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type])." (fk_categorie, fk_product)";
11489 $sql .= " SELECT fk_categorie, $toId FROM ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11490 $sql .= " WHERE fk_product = ".((int) $fromId);
11491
11492 if (!$this->db->query($sql)) {
11493 $this->error = $this->db->lasterror();
11494 $this->db->rollback();
11495 return -1;
11496 }
11497
11498 $this->db->commit();
11499 return 1;
11500 }
11501
11508 public function deleteEcmFiles($mode = 0)
11509 {
11510 global $conf;
11511
11512 $this->db->begin();
11513
11514 // Delete in database with mode 0
11515 if ($mode == 0) {
11516 switch ($this->element) {
11517 case 'propal':
11518 $element = 'propale';
11519 break;
11520 case 'product':
11521 $element = 'produit';
11522 break;
11523 case 'order_supplier':
11524 $element = 'fournisseur/commande';
11525 break;
11526 case 'invoice_supplier':
11527 // Special cases that need to use get_exdir to get real dir of object
11528 // In future, all object should use this to define path of documents.
11529 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11530 break;
11531 case 'shipping':
11532 $element = 'expedition/sending';
11533 break;
11534 case 'task':
11535 case 'project_task':
11536 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11537
11538 $project_result = $this->fetchProject();
11539 if ($project_result >= 0) {
11540 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11541 }
11542 // no break
11543 case 'contrat':
11544 $element = 'contract';
11545 break;
11546 default:
11547 $element = $this->element;
11548 }
11549 '@phan-var-force string $element';
11550
11551 // Delete ecm_files_extrafields with mode 0 (using name)
11552 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11553 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11554 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11555 $sql .= ")";
11556
11557 if (!$this->db->query($sql)) {
11558 $this->error = $this->db->lasterror();
11559 $this->db->rollback();
11560 return false;
11561 }
11562
11563 // Delete ecm_files with mode 0 (using name)
11564 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11565 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11566 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11567
11568 if (!$this->db->query($sql)) {
11569 $this->error = $this->db->lasterror();
11570 $this->db->rollback();
11571 return false;
11572 }
11573 }
11574
11575 // Delete in database with mode 1
11576 if ($mode == 1) {
11577 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11578 $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).")";
11579 $resql = $this->db->query($sql);
11580 if (!$resql) {
11581 $this->error = $this->db->lasterror();
11582 $this->db->rollback();
11583 return false;
11584 }
11585
11586 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11587 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11588 $resql = $this->db->query($sql);
11589 if (!$resql) {
11590 $this->error = $this->db->lasterror();
11591 $this->db->rollback();
11592 return false;
11593 }
11594 }
11595
11596 $this->db->commit();
11597 return true;
11598 }
11599}
$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.