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 $sqlColumnValueString = implode(
7005 ',',
7010 array_map(function ($key) use ($sqlColumnValues) {
7011 return "{$key} = {$sqlColumnValues[$key]}";
7012 }, array_keys($sqlColumnValues))
7013 );
7014 $sql = "UPDATE {$extrafieldsTable} SET {$sqlColumnValueString} WHERE fk_object = ".((int) $this->id);
7015 } else {
7016 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7017 $extrafieldsRequiredOnOtherEntities = $extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] ?? array();
7018 foreach ($extrafieldsRequiredOnOtherEntities as $key => $extrafieldType) {
7019 if (isset($sqlColumnValues[$key])) {
7020 // extrafield value already provided: no need to add it
7021 continue;
7022 }
7023 // default value: empty string, except for 'int', 'double' and 'price'
7024 $sqlColumnValues[$key] = "''";
7025 if (in_array($extrafieldType, array('int', 'double', 'price'))) {
7026 $sqlColumnValues[$key] = 0;
7027 }
7028 }
7029 $sqlColumns = implode(',', array_keys($sqlColumnValues));
7030 $sqlValues = implode(',', array_values($sqlColumnValues));
7031 $sql = "INSERT INTO {$extrafieldsTable} ({$sqlColumns}) VALUES ({$sqlValues})";
7032 }
7033
7034 $resql = $this->db->query($sql);
7035 if (!$resql) {
7036 $this->error = $this->db->lasterror();
7037 $error++;
7038 }
7039
7040 if (!$error && $trigger) {
7041 // Call trigger
7042 $this->context = array('extrafieldaddupdate' => 1);
7043 $result = $this->call_trigger($trigger, $userused);
7044 if ($result < 0) {
7045 $error++;
7046 }
7047 // End call trigger
7048 }
7049
7050 if ($error) {
7051 $this->db->rollback();
7052 return -1;
7053 } else {
7054 $this->db->commit();
7055 return 1;
7056 }
7057 }
7058
7070 public function insertExtraLanguages($trigger = '', $userused = null)
7071 {
7072 global $langs, $user;
7073
7074 if (empty($userused)) {
7075 $userused = $user;
7076 }
7077
7078 $error = 0;
7079
7080 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7081 return 0; // For avoid conflicts if trigger used
7082 }
7083
7084 if (is_array($this->array_languages)) {
7085 $new_array_languages = $this->array_languages;
7086
7087 foreach ($new_array_languages as $key => $value) {
7088 $attributeKey = $key;
7089 $attributeType = $this->fields[$attributeKey]['type'];
7090 $attributeLabel = $this->fields[$attributeKey]['label'];
7091
7092 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7093 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7094
7095 switch ($attributeType) {
7096 case 'int':
7097 if (is_array($value) || (!is_numeric($value) && $value != '')) {
7098 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7099 return -1;
7100 } elseif ($value == '') { // @phan-suppress-current-line PhanTypeComparisonFromArray
7101 $new_array_languages[$key] = null;
7102 }
7103 break;
7104 case 'double':
7105 $value = price2num((string) $value);
7106 if (!is_numeric($value) && $value != '') {
7107 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7108 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
7109 return -1;
7110 } elseif ($value == '') {
7111 $new_array_languages[$key] = null;
7112 } else {
7113 $new_array_languages[$key] = $value;
7114 }
7115 break;
7116 /*case 'select': // Not required, we chose value='0' for undefined values
7117 if ($value=='-1')
7118 {
7119 $this->array_options[$key] = null;
7120 }
7121 break;*/
7122 }
7123 }
7124
7125 $this->db->begin();
7126
7127 $table_element = $this->table_element;
7128 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7129 $table_element = 'categories'; // For compatibility
7130 }
7131
7132 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
7133
7134 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
7135 foreach ($langcodearray as $langcode => $value) {
7136 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
7137 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
7138 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
7139 $this->db->query($sql_del);
7140
7141 if ($value !== '') {
7142 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
7143 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
7144 $sql .= ")";
7145
7146 $resql = $this->db->query($sql);
7147 if (!$resql) {
7148 $this->error = $this->db->lasterror();
7149 $error++;
7150 break;
7151 }
7152 }
7153 }
7154 }
7155
7156 if (!$error && $trigger) {
7157 // Call trigger
7158 $this->context = array('extralanguagesaddupdate' => 1);
7159 $result = $this->call_trigger($trigger, $userused);
7160 if ($result < 0) {
7161 $error++;
7162 }
7163 // End call trigger
7164 }
7165
7166 if ($error) {
7167 $this->db->rollback();
7168 return -1;
7169 } else {
7170 $this->db->commit();
7171 return 1;
7172 }
7173 } else {
7174 return 0;
7175 }
7176 }
7177
7188 public function updateExtraField($key, $trigger = null, $userused = null)
7189 {
7190 global $langs, $user, $hookmanager;
7191
7192 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7193 return 0;
7194 }
7195
7196 if (empty($userused)) {
7197 $userused = $user;
7198 }
7199
7200 $error = 0;
7201
7202 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
7203 // Check parameters
7204 $langs->load('admin');
7205 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
7206 $extrafields = new ExtraFields($this->db);
7207 $extrafields->fetch_name_optionals_label($this->table_element);
7208
7209 $value = $this->array_options["options_".$key];
7210
7211 $attributeKey = $key;
7212 $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
7213 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
7214 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
7215 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
7216 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7217 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
7218
7219 // Similar code than into insertExtraFields
7220 if ($attributeRequired) {
7221 $mandatorypb = false;
7222 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7223 $mandatorypb = true;
7224 }
7225 if ($this->array_options["options_".$key] === '') {
7226 $mandatorypb = true;
7227 }
7228 if ($mandatorypb) {
7229 $langs->load("errors");
7230 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7231 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7232 return -1;
7233 }
7234 }
7235
7236 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7237 $new_array_options = $this->array_options;
7238
7239 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7240 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7241 if (!empty($attrfieldcomputed)) {
7242 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7243 $value = dol_eval((string) $attrfieldcomputed, 1, 0, '2');
7244 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7245
7246 $new_array_options["options_".$key] = $value;
7247
7248 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7249 } else {
7250 $new_array_options["options_".$key] = null;
7251
7252 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7253 }
7254 }
7255
7256 switch ($attributeType) {
7257 case 'int':
7258 if (!is_numeric($value) && $value != '') {
7259 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7260 return -1;
7261 } elseif ($value === '') {
7262 $new_array_options["options_".$key] = null;
7263
7264 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7265 }
7266 break;
7267 case 'price':
7268 case 'double':
7269 $value = price2num($value);
7270 if (!is_numeric($value) && $value != '') {
7271 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7272 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7273 return -1;
7274 } elseif ($value === '') {
7275 $value = null;
7276 }
7277 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7278 $new_array_options["options_".$key] = $value;
7279
7280 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7281 break;
7282 /*case 'select': // Not required, we chose value='0' for undefined values
7283 if ($value=='-1')
7284 {
7285 $new_array_options["options_".$key] = $value;
7286
7287 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7288 }
7289 break;*/
7290 case 'password':
7291 $algo = '';
7292 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7293 // If there is an encryption choice, we use it to encrypt data before insert
7294 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7295 $algo = reset($tmparrays);
7296 if ($algo != '') {
7297 //global $action; // $action may be 'create', 'update', 'update_extras'...
7298 //var_dump($action);
7299 //var_dump($this->oldcopy);exit;
7300 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7301 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
7302 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7303 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.
7304 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7305 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7306 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7307 } else {
7308 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7309 }
7310 } else {
7311 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7312 }
7313 } else {
7314 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7315 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7316 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7317 } else {
7318 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7319 }
7320 } else {
7321 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7322 }
7323 }
7324 } else {
7325 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7326 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7327 } else {
7328 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7329 }
7330 }
7331 } else {
7332 // No encryption
7333 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7334 }
7335 } else { // Common usage
7336 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7337 }
7338
7339 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7340 break;
7341 case 'date':
7342 case 'datetime':
7343 if (empty($this->array_options["options_".$key])) {
7344 $new_array_options["options_".$key] = null;
7345
7346 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7347 } else {
7348 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7349 }
7350 break;
7351 case 'datetimegmt':
7352 if (empty($this->array_options["options_".$key])) {
7353 $new_array_options["options_".$key] = null;
7354
7355 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7356 } else {
7357 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7358 }
7359 break;
7360 case 'boolean':
7361 if (empty($this->array_options["options_".$key])) {
7362 $new_array_options["options_".$key] = null;
7363
7364 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7365 }
7366 break;
7367 case 'link':
7368 if ($this->array_options["options_".$key] === '') {
7369 $new_array_options["options_".$key] = null;
7370
7371 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7372 }
7373 break;
7374 /*
7375 case 'link':
7376 $param_list = array_keys($attributeParam['options']);
7377 // 0 : ObjectName
7378 // 1 : classPath
7379 $InfoFieldList = explode(":", $param_list[0]);
7380 dol_include_once($InfoFieldList[1]);
7381 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7382 {
7383 if ($value == '-1') // -1 is key for no defined in combo list of objects
7384 {
7385 $new_array_options[$key] = '';
7386 } elseif ($value) {
7387 $object = new $InfoFieldList[0]($this->db);
7388 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7389 else $res = $object->fetch(0, $value); // For compatibility
7390
7391 if ($res > 0) $new_array_options[$key] = $object->id;
7392 else {
7393 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7394 $this->db->rollback();
7395 return -1;
7396 }
7397 }
7398 } else {
7399 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7400 }
7401 break;
7402 */
7403 case 'checkbox':
7404 case 'chkbxlst':
7405 $new_array_options = array();
7406 if (is_array($this->array_options["options_".$key])) {
7407 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7408 } else {
7409 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7410 }
7411
7412 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7413 break;
7414 }
7415
7416 $this->db->begin();
7417
7418 $linealreadyfound = 0;
7419
7420 // 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)
7421 $table_element = $this->table_element;
7422 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7423 $table_element = 'categories'; // For compatibility
7424 }
7425
7426 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7427 $resql = $this->db->query($sql);
7428 if ($resql) {
7429 $tmpobj = $this->db->fetch_object($resql);
7430 if ($tmpobj) {
7431 $linealreadyfound = $tmpobj->nb;
7432 }
7433 }
7434
7435 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7436 if ($linealreadyfound) {
7437 if ($this->array_options["options_".$key] === null) {
7438 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
7439 } else {
7440 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7441 }
7442 $sql .= " WHERE fk_object = ".((int) $this->id);
7443
7444 $resql = $this->db->query($sql);
7445 if (!$resql) {
7446 $error++;
7447 $this->error = $this->db->lasterror();
7448 }
7449 } else {
7450 $result = $this->insertExtraFields('', $user);
7451 if ($result < 0) {
7452 $error++;
7453 }
7454 }
7455
7456 if (!$error) {
7457 $parameters = array('key' => $key);
7458 global $action;
7459 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7460 if ($reshook < 0) {
7461 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7462 }
7463 }
7464
7465 if (!$error && $trigger) {
7466 // Call trigger
7467 $this->context = array('extrafieldupdate' => 1);
7468 $result = $this->call_trigger($trigger, $userused);
7469 if ($result < 0) {
7470 $error++;
7471 }
7472 // End call trigger
7473 }
7474
7475 if ($error) {
7476 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7477 $this->db->rollback();
7478 return -1;
7479 } else {
7480 $this->db->commit();
7481 return 1;
7482 }
7483 } else {
7484 return 0;
7485 }
7486 }
7487
7494 public function getExtraField($key)
7495 {
7496 return $this->array_options['options_'.$key] ?? null;
7497 }
7498
7506 public function setExtraField($key, $value)
7507 {
7508 $this->array_options['options_'.$key] = $value;
7509 }
7510
7521 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7522 {
7523 global $user;
7524
7525 if (empty($userused)) {
7526 $userused = $user;
7527 }
7528
7529 //$error = 0;
7530
7531 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7532 return 0; // For avoid conflicts if trigger used
7533 }
7534
7535 return 0;
7536 }
7537
7538
7553 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7554 {
7555 global $conf, $langs, $form;
7556
7557
7558 // 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...)
7559
7560 if (!is_object($form)) {
7561 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7562 $form = new Form($this->db);
7563 }
7564
7565 if (!empty($this->fields)) {
7566 $val = $this->fields[$key];
7567 }
7568
7569 // Validation tests and output
7570 $fieldValidationErrorMsg = '';
7571 $validationClass = '';
7572 $fieldValidationErrorMsg = $this->getFieldError($key);
7573 if (!empty($fieldValidationErrorMsg)) {
7574 $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
7575 } else {
7576 $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
7577 }
7578
7579 //$valuemultiselectinput = array();
7580 $out = '';
7581 $type = '';
7582 $isDependList = 0;
7583 $param = array();
7584 $param['options'] = array();
7585 $reg = array();
7586 // @phan-suppress-next-line PhanTypeMismatchProperty
7587 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7588 // Because we work on extrafields
7589 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7590 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7591 $type = 'link';
7592 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7593 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7594 $type = 'link';
7595 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7596 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7597 $type = 'link';
7598 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7599 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7600 $type = 'sellist';
7601 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7602 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7603 $type = 'sellist';
7604 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7605 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7606 $type = 'sellist';
7607 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7608 $param['options'] = array($reg[1] => 'N');
7609 $type = 'chkbxlst';
7610 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7611 $param['options'] = array();
7612 $type = 'varchar';
7613 $size = $reg[1];
7614 } elseif (preg_match('/varchar/', $val['type'])) {
7615 $param['options'] = array();
7616 $type = 'varchar';
7617 } elseif (preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
7618 $param['options'] = array();
7619 $type = 'stars';
7620 $size = $reg[1];
7621 } else {
7622 $param['options'] = array();
7623 $type = $this->fields[$key]['type'];
7624 }
7625 //var_dump($type); var_dump($param['options']);
7626
7627 // Special case that force options and type ($type can be integer, varchar, ...)
7628 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7629 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7630 // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty
7631 if (empty($this->fields[$key]['multiinput'])) {
7632 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7633 }
7634 }
7635
7636 $label = $this->fields[$key]['label'];
7637 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7638 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7639 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7640 // @phan-suppress-next-line PhanTypeMismatchProperty
7641 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7642 // @phan-suppress-next-line PhanTypeMismatchProperty
7643 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7644 // @phan-suppress-next-line PhanTypeMismatchProperty
7645 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7646 // @phan-suppress-next-line PhanTypeMismatchProperty
7647 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7648 // @phan-suppress-next-line PhanTypeMismatchProperty
7649 $placeholder = (!empty($this->fields[$key]['placeholder']) ? $this->fields[$key]['placeholder'] : 0);
7650
7651 // @phan-suppress-next-line PhanTypeMismatchProperty
7652 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7653 // @phan-suppress-next-line PhanTypeMismatchProperty
7654 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7655 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7656
7657 $objectid = $this->id;
7658
7659 if ($computed) {
7660 if (!preg_match('/^search_/', $keyprefix)) {
7661 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7662 } else {
7663 return '';
7664 }
7665 }
7666
7667 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7668 if (empty($morecss) && !empty($val['css'])) {
7669 $morecss = $val['css'];
7670 } elseif (empty($morecss)) {
7671 if ($type == 'date') {
7672 $morecss = 'minwidth100imp';
7673 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7674 $morecss = 'minwidth200imp';
7675 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7676 $morecss = 'maxwidth75';
7677 } elseif ($type == 'url') {
7678 $morecss = 'minwidth400';
7679 } elseif ($type == 'boolean') {
7680 $morecss = '';
7681 } else {
7682 if (is_numeric($size) && round((float) $size) < 12) {
7683 $morecss = 'minwidth100';
7684 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7685 $morecss = 'minwidth200';
7686 } else {
7687 $morecss = 'minwidth400';
7688 }
7689 }
7690 }
7691
7692 // Add validation state class
7693 if (!empty($validationClass)) {
7694 $morecss .= $validationClass;
7695 }
7696
7697 if (in_array($type, array('date'))) {
7698 $tmp = explode(',', $size);
7699 $newsize = $tmp[0];
7700 $showtime = 0;
7701
7702 // Do not show current date when field not required (see selectDate() method)
7703 if (!$required && $value == '') {
7704 $value = '-1';
7705 }
7706
7707 // TODO Must also support $moreparam
7708 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7709 } elseif (in_array($type, array('datetime'))) {
7710 $tmp = explode(',', $size);
7711 $newsize = $tmp[0];
7712 $showtime = 1;
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, '', '', '', 1, '', '', 'tzuserrel');
7721 } elseif (in_array($type, array('duration'))) {
7722 $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7723 } elseif (in_array($type, array('int', 'integer'))) {
7724 $tmp = explode(',', $size);
7725 $newsize = $tmp[0];
7726 $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' : '').'>';
7727 } elseif (in_array($type, array('real'))) {
7728 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7729 } elseif (preg_match('/varchar/', (string) $type)) {
7730 $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' : '').'>';
7731 } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7732 $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7733 } elseif (preg_match('/^text/', (string) $type)) {
7734 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7735 if (!empty($param['options'])) {
7736 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
7737 //var_dump($param['options']);
7738 $out .= $form->selectarray($keyprefix.$key.$keysuffix."_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone".$morecss);
7739 $out .= '<input id="'.$keyprefix.$key.$keysuffix.'_multiinputadd" type="button" class="button" value="'.$langs->trans("Add").'">';
7740 $out .= "<script>";
7741 $out .= '
7742 function handlemultiinputdisabling(htmlname){
7743 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
7744 multiinput = $("#"+htmlname+"_multiinput");
7745 multiinput.find("option").each(function(){
7746 tmpval = $("#"+htmlname).val();
7747 tmpvalarray = tmpval.split("\n");
7748 valtotest = $(this).val();
7749 if(tmpvalarray.includes(valtotest)){
7750 $(this).prop("disabled",true);
7751 } else {
7752 if($(this).prop("disabled") == true){
7753 console.log(valtotest)
7754 $(this).prop("disabled", false);
7755 }
7756 }
7757 });
7758 }
7759
7760 $(document).ready(function () {
7761 $("#'.$keyprefix.$key.$keysuffix.'_multiinputadd").on("click",function() {
7762 tmpval = $("#'.$keyprefix.$key.$keysuffix.'").val();
7763 tmpvalarray = tmpval.split(",");
7764 valtotest = $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val();
7765 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
7766 console.log("We add the selected value to the text area '.$keyprefix.$key.$keysuffix.'");
7767 if(tmpval == ""){
7768 tmpval = valtotest;
7769 } else {
7770 tmpval = tmpval + "\n" + valtotest;
7771 }
7772 $("#'.$keyprefix.$key.$keysuffix.'").val(tmpval);
7773 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7774 $("#'.$keyprefix.$key.$keysuffix.'_multiinput").val(-1);
7775 } else {
7776 console.log("We add nothing the text area '.$keyprefix.$key.$keysuffix.'");
7777 }
7778 });
7779 $("#'.$keyprefix.$key.$keysuffix.'").on("change",function(){
7780 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7781 });
7782 handlemultiinputdisabling("'.$keyprefix.$key.$keysuffix.'");
7783 })';
7784 $out .= "</script>";
7785 $value = str_replace(',', "\n", $value);
7786 }
7787 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7788 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
7789 $out .= (string) $doleditor->Create(1, '', true, '', '', '', $morecss);
7790 } else {
7791 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7792 }
7793 } elseif (preg_match('/^html/', (string) $type)) {
7794 if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
7795 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
7796 $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
7797 $out = (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
7798 } else {
7799 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
7800 }
7801 } elseif ($type == 'boolean') {
7802 $checked = '';
7803 if (!empty($value)) {
7804 $checked = ' checked value="1" ';
7805 } else {
7806 $checked = ' value="1" ';
7807 }
7808 $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
7809 } elseif ($type == 'price') {
7810 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7811 $value = price($value);
7812 }
7813 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
7814 } elseif ($type == 'stars') {
7815 $out = '<input type="hidden" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
7816 $out .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.'_selection">';
7817 $i = 1;
7818 while ($i <= $size) {
7819 $out .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
7820 $i++;
7821 }
7822 $out .= '</div>';
7823 $out .= '<script>
7824 jQuery(function($) { /* commonobject.class.php 1 */
7825 let container = $("#'.$keyprefix.$key.$keysuffix.'_selection");
7826 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.'").val()) || 0;
7827 container.find(".star").each(function() {
7828 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7829 });
7830 container.find(".star").on("mouseover", function() {
7831 let selectedStar = $(this).data("value");
7832 container.find(".star").each(function() {
7833 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
7834 });
7835 });
7836 container.on("mouseout", function() {
7837 container.find(".star").each(function() {
7838 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7839 });
7840 });
7841 container.find(".star").off("click").on("click", function() {
7842 selectedStars = $(this).data("value");
7843 if (selectedStars === 1 && $("#'.$keyprefix.$key.$keysuffix.'").val() == 1) {
7844 selectedStars = 0;
7845 }
7846 $("#'.$keyprefix.$key.$keysuffix.'").val(selectedStars);
7847 container.find(".star").each(function() {
7848 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
7849 });
7850 });
7851 });
7852 </script>';
7853 } elseif (preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7854 if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
7855 $value = price($value);
7856 }
7857 $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
7858 } elseif ($type == 'select') { // combo list
7859 $out = '';
7860 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7861 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7862 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7863 }
7864
7865 $tmpselect = '';
7866 $nbchoice = 0;
7867
7868 foreach ($param['options'] as $keyb => $valb) {
7869 if ((string) $keyb == '') {
7870 continue;
7871 }
7872 if (strpos($valb, "|") !== false) {
7873 list($valb, $parent) = explode('|', $valb);
7874 }
7875 $nbchoice++;
7876 $tmpselect .= '<option value="'.$keyb.'"';
7877 $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
7878 if (!empty($parent)) {
7879 $isDependList = 1;
7880 }
7881 $tmpselect .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
7882 $tmpselect .= '>'.$langs->trans($valb).'</option>';
7883 }
7884
7885 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
7886 if ((!isset($this->fields[$key]['default'])) || empty($this->fields[$key]['notnull']) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
7887 $out .= '<option value="0">&nbsp;</option>';
7888 }
7889 $out .= $tmpselect;
7890 $out .= '</select>';
7891 } elseif ($type == 'sellist') {
7892 $out = '';
7893 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7894 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7895 $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7896 }
7897
7898 $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
7899 if (is_array($param['options'])) {
7900 $tmpparamoptions = array_keys($param['options']);
7901 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
7902
7903 $InfoFieldList = explode(":", $paramoptions[0], 5);
7904 // 0 : tableName
7905 // 1 : label field name
7906 // 2 : key fields name (if different of rowid)
7907 // optional parameters...
7908 // 3 : key field parent (for dependent lists). How this is used ?
7909 // 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".
7910 // 5 : string category type. This replace the filter.
7911 // 6 : ids categories list separated by comma for category root. This replace the filter.
7912 // 7 : sort field
7913
7914 // If there is filter
7915 if (! empty($InfoFieldList[4])) {
7916 $pos = 0;
7917 $parenthesisopen = 0;
7918 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
7919 if (substr($InfoFieldList[4], $pos, 1) == '(') {
7920 $parenthesisopen++;
7921 }
7922 if (substr($InfoFieldList[4], $pos, 1) == ')') {
7923 $parenthesisopen--;
7924 }
7925 $pos++;
7926 }
7927 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
7928 $tmpafter = substr($InfoFieldList[4], $pos + 1);
7929 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
7930 $InfoFieldList[4] = $tmpbefore;
7931 if ($tmpafter !== '') {
7932 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
7933 }
7934 //var_dump($InfoFieldList);
7935
7936 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
7937 $reg = array();
7938 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
7939 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
7940 }
7941
7942 //var_dump($InfoFieldList);
7943 }
7944
7945 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
7946
7947 $parentName = '';
7948 $parentField = '';
7949
7950 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
7951
7952 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7953 if (strpos($InfoFieldList[4], 'extra.') !== false) {
7954 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
7955 } else {
7956 $keyList = $InfoFieldList[2].' as rowid';
7957 }
7958 }
7959 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
7960 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
7961 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
7962 $keyList .= ', main.'.$parentField;
7963 } else {
7964 $keyList .= ', '.$parentField;
7965 }
7966 }
7967
7968 $filter_categorie = false;
7969 if (count($InfoFieldList) > 5) {
7970 if ($InfoFieldList[0] == 'categorie') {
7971 $filter_categorie = true;
7972 }
7973 }
7974
7975 if (!$filter_categorie) {
7976 $fields_label = explode('|', $InfoFieldList[1]);
7977 if (is_array($fields_label)) {
7978 $keyList .= ', ';
7979 $keyList .= implode(', ', $fields_label);
7980 }
7981
7982 $sqlwhere = '';
7983 $sql = "SELECT " . $keyList;
7984 $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
7985
7986 if (!empty($InfoFieldList[4])) {
7987 // can use SELECT request
7988 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
7989 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
7990 }
7991
7992 // current object id can be use into filter
7993 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
7994 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
7995 } else {
7996 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
7997 }
7998
7999 // We have to join on extrafield table
8000 $errstr = '';
8001 if (strpos($InfoFieldList[4], 'extra') !== false) {
8002 $sql .= " as main, " . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . "_extrafields as extra";
8003 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8004 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8005 } else {
8006 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8007 }
8008 } else {
8009 $sqlwhere .= ' WHERE 1=1';
8010 }
8011
8012 // Add Usf filter on second line
8013 /*
8014 if ($Usf) {
8015 $errorstr = '';
8016 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8017 if (!$errorstr) {
8018 $sqlwhere .= $sqlusf;
8019 } else {
8020 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8021 }
8022 }
8023 */
8024
8025 // Some tables may have field, some other not. For the moment we disable it.
8026 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8027 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8028 }
8029 $sql .= $sqlwhere;
8030
8031 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8032 if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8033 $sql .= " ORDER BY ".$this->db->escape($InfoFieldList[7]);
8034 } else {
8035 $sql .= " ORDER BY ".$this->db->sanitize(implode(', ', $fields_label));
8036 }
8037 $sql .= ' LIMIT ' . getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', 1000);
8038 // print $sql;
8039
8040 dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8041 $resql = $this->db->query($sql);
8042 if ($resql) {
8043 $out .= '<option value="0">&nbsp;</option>';
8044 $num = $this->db->num_rows($resql);
8045 $i = 0;
8046 while ($i < $num) {
8047 $labeltoshow = '';
8048 $obj = $this->db->fetch_object($resql);
8049
8050 // Several field into label (eq table:code|libelle:rowid)
8051 $notrans = false;
8052 $fields_label = explode('|', $InfoFieldList[1]);
8053 if (count($fields_label) > 1) {
8054 $notrans = true;
8055 foreach ($fields_label as $field_toshow) {
8056 $labeltoshow .= $obj->$field_toshow . ' ';
8057 }
8058 } else {
8059 $labeltoshow = $obj->{$InfoFieldList[1]};
8060 }
8061 $labeltoshow = dol_trunc($labeltoshow, 45);
8062
8063 if ($value == $obj->rowid) {
8064 foreach ($fields_label as $field_toshow) {
8065 $translabel = $langs->trans($obj->$field_toshow);
8066 if ($translabel != $obj->$field_toshow) {
8067 $labeltoshow = dol_trunc($translabel) . ' ';
8068 } else {
8069 $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8070 }
8071 }
8072 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8073 } else {
8074 if (!$notrans) {
8075 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8076 if ($translabel != $obj->{$InfoFieldList[1]}) {
8077 $labeltoshow = dol_trunc($translabel, 18);
8078 } else {
8079 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8080 }
8081 }
8082 if (empty($labeltoshow)) {
8083 $labeltoshow = '(not defined)';
8084 }
8085 if ($value == $obj->rowid) {
8086 $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8087 }
8088
8089 if (!empty($InfoFieldList[3]) && $parentField) {
8090 $parent = $parentName . ':' . $obj->{$parentField};
8091 $isDependList = 1;
8092 }
8093
8094 $out .= '<option value="' . $obj->rowid . '"';
8095 $out .= ($value == $obj->rowid ? ' selected' : '');
8096 $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8097 $out .= '>' . $labeltoshow . '</option>';
8098 }
8099
8100 $i++;
8101 }
8102 $this->db->free($resql);
8103 } else {
8104 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8105 }
8106 } else {
8107 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8108 $categcode = $InfoFieldList[5];
8109 if (is_numeric($categcode)) {
8110 $categcode = Categorie::$MAP_ID_TO_CODE[(int) $InfoFieldList[5]];
8111 }
8112 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8113 $out .= '<option value="0">&nbsp;</option>';
8114 foreach ($data as $data_key => $data_value) {
8115 $out .= '<option value="' . $data_key . '"';
8116 $out .= ($value == $data_key ? ' selected' : '');
8117 $out .= '>' . $data_value . '</option>';
8118 }
8119 }
8120 }
8121 $out .= '</select>';
8122 } elseif ($type == 'checkbox') {
8123 $value_arr = explode(',', $value);
8124 $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8125 } elseif ($type == 'radio') {
8126 $out = '';
8127 foreach ($param['options'] as $keyopt => $valopt) {
8128 $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
8129 $out .= ' value="'.$keyopt.'"';
8130 $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
8131 $out .= ($value == $keyopt ? 'checked' : '');
8132 $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
8133 }
8134 } elseif ($type == 'chkbxlst') {
8135 if (is_array($value)) {
8136 $value_arr = $value;
8137 } else {
8138 $value_arr = explode(',', $value);
8139 }
8140
8141 if (is_array($param['options'])) {
8142 $tmpparamoptions = array_keys($param['options']);
8143 $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]);
8144
8145 $InfoFieldList = explode(":", $paramoptions[0], 5);
8146 // 0 : tableName
8147 // 1 : label field name
8148 // 2 : key fields name (if different of rowid)
8149 // optional parameters...
8150 // 3 : key field parent (for dependent lists). How this is used ?
8151 // 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".
8152 // 5 : string category type. This replace the filter.
8153 // 6 : ids categories list separated by comma for category root. This replace the filter.
8154 // 7 : sort field
8155
8156 // If there is a filter
8157 if (! empty($InfoFieldList[4])) {
8158 $pos = 0;
8159 $parenthesisopen = 0;
8160 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8161 if (substr($InfoFieldList[4], $pos, 1) == '(') {
8162 $parenthesisopen++;
8163 }
8164 if (substr($InfoFieldList[4], $pos, 1) == ')') {
8165 $parenthesisopen--;
8166 }
8167 $pos++;
8168 }
8169 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8170 $tmpafter = substr($InfoFieldList[4], $pos + 1);
8171 //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8172 $InfoFieldList[4] = $tmpbefore;
8173 if ($tmpafter !== '') {
8174 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8175 }
8176
8177 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
8178 $reg = array();
8179 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
8180 $InfoFieldList[4] = '('.$reg[1].':'.$reg[2].':'.$reg[3].')';
8181 }
8182
8183 //var_dump($InfoFieldList);
8184 }
8185
8186 //$Usf = empty($paramoptions[1]) ? '' :$paramoptions[1];
8187
8188 '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8189
8190 $parentName = '';
8191 $parentField = '';
8192
8193 $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
8194
8195 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8196 if (strpos($InfoFieldList[4], 'extra.') !== false) {
8197 $keyList = 'main.'.$InfoFieldList[2].' as rowid';
8198 } else {
8199 $keyList = $InfoFieldList[2].' as rowid';
8200 }
8201 }
8202 if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8203 list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8204 if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
8205 $keyList .= ', main.'.$parentField;
8206 } else {
8207 $keyList .= ', '.$parentField;
8208 }
8209 }
8210
8211 $filter_categorie = false;
8212 if (count($InfoFieldList) > 5) {
8213 if ($InfoFieldList[0] == 'categorie') {
8214 $filter_categorie = true;
8215 }
8216 }
8217
8218 // Common filter
8219 if (!$filter_categorie) {
8220 $fields_label = explode('|', $InfoFieldList[1]);
8221 if (is_array($fields_label)) {
8222 $keyList .= ', ';
8223 $keyList .= implode(', ', $fields_label);
8224 }
8225
8226 $sqlwhere = '';
8227 $sql = "SELECT " . $keyList;
8228 $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8229
8230 if (!empty($InfoFieldList[4])) {
8231 // can use SELECT request
8232 if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8233 $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8234 }
8235
8236 // current object id can be use into filter
8237 if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8238 $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
8239 } else {
8240 $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8241 }
8242
8243 // We have to join on extrafield table
8244 $errstr = '';
8245 if (strpos($InfoFieldList[4], 'extra') !== false) {
8246 $sql .= ' as main, ' . $this->db->sanitize($this->db->prefix() . $InfoFieldList[0]) . '_extrafields as extra';
8247 $sqlwhere .= " WHERE extra.fk_object = main." . $this->db->sanitize($InfoFieldList[2]);
8248 $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8249 } else {
8250 $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8251 }
8252 } else {
8253 $sqlwhere .= ' WHERE 1=1';
8254 }
8255
8256 // Add Usf filter on second line
8257 /*
8258 if ($Usf) {
8259 $errorstr = '';
8260 $sqlusf .= forgeSQLFromUniversalSearchCriteria($Usf, $errorstr);
8261 if (!$errorstr) {
8262 $sqlwhere .= $sqlusf;
8263 } else {
8264 $sqlwhere .= " AND invalid_usf_filter_of_extrafield";
8265 }
8266 }
8267 */
8268
8269 // Some tables may have field, some other not. For the moment we disable it.
8270 if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8271 $sqlwhere .= " AND entity = " . ((int) $conf->entity);
8272 }
8273 // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8274 // print $sql;
8275
8276 $sql .= $sqlwhere;
8277
8278 dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8279
8280 $resql = $this->db->query($sql);
8281 if ($resql) {
8282 $num = $this->db->num_rows($resql);
8283 $i = 0;
8284
8285 $data = array();
8286
8287 while ($i < $num) {
8288 $labeltoshow = '';
8289 $obj = $this->db->fetch_object($resql);
8290
8291 $notrans = false;
8292 // Several field into label (eq table:code|libelle:rowid)
8293 $fields_label = explode('|', $InfoFieldList[1]);
8294 if (count($fields_label) > 1) {
8295 $notrans = true;
8296 foreach ($fields_label as $field_toshow) {
8297 $labeltoshow .= $obj->$field_toshow . ' ';
8298 }
8299 } else {
8300 $labeltoshow = $obj->{$InfoFieldList[1]};
8301 }
8302 $labeltoshow = dol_trunc($labeltoshow, 45);
8303
8304 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8305 foreach ($fields_label as $field_toshow) {
8306 $translabel = $langs->trans($obj->$field_toshow);
8307 if ($translabel != $obj->$field_toshow) {
8308 $labeltoshow = dol_trunc($translabel, 18) . ' ';
8309 } else {
8310 $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8311 }
8312 }
8313
8314 $data[$obj->rowid] = $labeltoshow;
8315 } else {
8316 if (!$notrans) {
8317 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8318 if ($translabel != $obj->{$InfoFieldList[1]}) {
8319 $labeltoshow = dol_trunc($translabel, 18);
8320 } else {
8321 $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8322 }
8323 }
8324 if (empty($labeltoshow)) {
8325 $labeltoshow = '(not defined)';
8326 }
8327
8328 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8329 $data[$obj->rowid] = $labeltoshow;
8330 }
8331
8332 if (!empty($InfoFieldList[3]) && $parentField) {
8333 $parent = $parentName . ':' . $obj->{$parentField};
8334 $isDependList = 1;
8335 }
8336
8337 $data[$obj->rowid] = $labeltoshow; // Warning: $obj->rowid is an alias and can be an int, but also a string ref.
8338 }
8339
8340 $i++;
8341 }
8342 $this->db->free($resql);
8343
8344 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8345 } else {
8346 print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8347 }
8348 } else {
8349 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
8350 $categcode = $InfoFieldList[5];
8351 if (is_numeric($categcode)) {
8352 $categcode = Categorie::$MAP_ID_TO_CODE[(int) $InfoFieldList[5]];
8353 }
8354 $data = $form->select_all_categories($categcode, '', 'parent', 64, $InfoFieldList[6], 1, 1);
8355 $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8356 }
8357 }
8358 } elseif ($type == 'link') {
8359 // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8360 // Filter can contains some ':' inside.
8361 $param_list = array_keys($param['options']);
8362 $param_list_array = explode(':', $param_list[0], 4);
8363
8364 $showempty = (($required && $default != '') ? 0 : 1);
8365
8366 if (!preg_match('/search_/', $keyprefix)) {
8367 if (!empty($param_list_array[2])) { // If the entry into $fields is set to add a create button
8368 // @phan-suppress-next-line PhanTypeMismatchProperty
8369 if (!empty($this->fields[$key]['picto'])) {
8370 $morecss .= ' widthcentpercentminusxx';
8371 } else {
8372 $morecss .= ' widthcentpercentminusx';
8373 }
8374 } else {
8375 // @phan-suppress-next-line PhanTypeMismatchProperty
8376 if (!empty($this->fields[$key]['picto'])) {
8377 $morecss .= ' widthcentpercentminusx';
8378 }
8379 }
8380 }
8381
8382 // $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.
8383
8384 // $valparent is a string 'dataobject@module:keyoffieldinfieldsarray' to find the record field to link to.
8385 // $valparent = $this->element.($this->module ? '@'.$this->module : '').':'.$key.$keysuffix;
8386
8387 // $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.
8388
8389 //$objectfield = $valparent;
8390 $objectfield = $val; // Is better than using old method $valparent
8391
8392 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
8393 $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, (int) $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8394
8395 if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
8396 if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
8397 && empty($val['disabled']) && empty($nonewbutton)) { // and to avoid to show the button if the field is protected by a "disabled".
8398 list($class, $classfile) = explode(':', $param_list[0]);
8399 if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
8400 $url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
8401 } else {
8402 $url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
8403 }
8404 $paramforthenewlink = '';
8405 $paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
8406 $paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOSTINT('id') : '');
8407 $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin='.GETPOST('origin', 'aZ09') : '');
8408 $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid='.GETPOSTINT('originid') : '');
8409 $paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
8410 // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8411 $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>';
8412 }
8413 }
8414 } elseif ($type == 'password') {
8415 // If prefix is 'search_', field is used as a filter, we use a common text field.
8416 if ($keyprefix.$key.$keysuffix == 'pass_crypted') {
8417 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="pass" id="pass" value="" '.($moreparam ? $moreparam : '').'>';
8418 $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8419 } else {
8420 $out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
8421 }
8422 } elseif ($type == 'array') {
8423 $newval = $val;
8424 $newval['type'] = 'varchar(256)';
8425
8426 $out = '';
8427 if (!empty($value)) {
8428 foreach ($value as $option) {
8429 $out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8430 $out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
8431 }
8432 }
8433 $out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8434
8435 $newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8436 $newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
8437
8438 if (!empty($conf->use_javascript_ajax)) {
8439 $out .= '
8440 <script nonce="'.getNonce().'">
8441 $(document).ready(function() {
8442 $("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
8443 $("'.dol_escape_js($newInput).'").insertBefore(this);
8444 });
8445
8446 $(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
8447 $(this).parent().remove();
8448 });
8449 });
8450 </script>';
8451 }
8452 }
8453 if (!empty($hidden)) {
8454 $out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
8455 }
8456
8457 if ($isDependList == 1) {
8458 $out .= $this->getJSListDependancies('_common');
8459 }
8460 /* Add comments
8461 if ($type == 'date') $out.=' (YYYY-MM-DD)';
8462 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8463 */
8464
8465 // Display error message for field
8466 if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8467 $out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
8468 }
8469
8470 return $out;
8471 }
8472
8486 public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
8487 {
8488 global $conf, $langs, $form;
8489
8490 // 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...)
8491
8492 if (!is_object($form)) {
8493 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
8494 $form = new Form($this->db);
8495 }
8496
8497 //$label = empty($val['label']) ? '' : $val['label'];
8498 $type = empty($val['type']) ? '' : $val['type'];
8499 $size = empty($val['css']) ? '' : $val['css'];
8500 $reg = array();
8501
8502 // Convert var to be able to share same code than showOutputField of extrafields
8503 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
8504 $type = 'varchar'; // convert varchar(xx) int varchar
8505 $size = $reg[1];
8506 } elseif (preg_match('/varchar/', $type)) {
8507 $type = 'varchar'; // convert varchar(xx) int varchar
8508 }
8509 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8510 // @phan-suppress-next-line PhanTypeMismatchProperty
8511 if (empty($this->fields[$key]['multiinput'])) {
8512 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8513 }
8514 }
8515 if (isset($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8516 $type = 'link';
8517 }
8518
8519 $default = empty($val['default']) ? '' : $val['default'];
8520 $computed = empty($val['computed']) ? '' : $val['computed'];
8521 $unique = empty($val['unique']) ? '' : $val['unique'];
8522 $required = empty($val['required']) ? '' : $val['required'];
8523 $param = array();
8524 $param['options'] = array();
8525
8526 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8527 $param['options'] = $val['arrayofkeyval'];
8528 }
8529 if (isset($val['type']) && preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) { // ex: integer:User:user/class/user.class.php
8530 $type = 'link';
8531 $stringforoptions = $reg[1].':'.$reg[2];
8532 // Special case: Force addition of getnomurlparam1 to -1 for users
8533 if ($reg[1] == 'User') {
8534 $stringforoptions .= ':#getnomurlparam1=-1';
8535 }
8536 $param['options'] = array($stringforoptions => $stringforoptions);
8537 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8538 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
8539 $type = 'sellist';
8540 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8541 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
8542 $type = 'sellist';
8543 } elseif (isset($val['type']) && preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8544 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
8545 $type = 'sellist';
8546 } elseif (isset($val['type']) && preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8547 $param['options'] = array($reg[1] => 'N');
8548 $type = 'chkbxlst';
8549 } elseif (isset($val['type']) && preg_match('/stars\‍((\d+)\‍)/', $val['type'], $reg)) {
8550 $param['options'] = array();
8551 $type = 'stars';
8552 $size = $reg[1];
8553 }
8554
8555 $langfile = empty($val['langfile']) ? '' : $val['langfile'];
8556 $list = (empty($val['list']) ? '' : $val['list']);
8557 $help = (empty($val['help']) ? '' : $val['help']);
8558 $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)
8559
8560 if ($hidden) {
8561 return '';
8562 }
8563
8564 // If field is a computed field, value must become result of compute
8565 if ($computed) {
8566 // Make the eval of compute string
8567 //var_dump($computed);
8568 $value = dol_eval((string) $computed, 1, 0, '2');
8569 }
8570
8571 if (empty($morecss)) {
8572 if ($type == 'date') {
8573 $morecss = 'minwidth100imp';
8574 } elseif ($type == 'datetime' || $type == 'timestamp') {
8575 $morecss = 'minwidth200imp';
8576 } elseif (in_array($type, array('int', 'double', 'price'))) {
8577 $morecss = 'maxwidth75';
8578 } elseif ($type == 'url') {
8579 $morecss = 'minwidth400';
8580 } elseif ($type == 'boolean') {
8581 $morecss = '';
8582 } else {
8583 if (is_numeric($size) && round((float) $size) < 12) {
8584 $morecss = 'minwidth100';
8585 } elseif (is_numeric($size) && round((float) $size) <= 48) {
8586 $morecss = 'minwidth200';
8587 } else {
8588 $morecss = 'minwidth400';
8589 }
8590 }
8591 }
8592
8593 // Format output value differently according to properties of field
8594 if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
8595 // @phan-suppress-next-line PhanTypeMismatchProperty
8596 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.
8597 $value = $this->getNomUrl(1, '', 0, '', 1);
8598 }
8599 } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
8600 $value = $this->getLibStatut(3);
8601 } elseif ($type == 'date') {
8602 if (!empty($value)) {
8603 $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
8604 } else {
8605 $value = '';
8606 }
8607 } elseif ($type == 'datetime' || $type == 'timestamp') {
8608 if (!empty($value)) {
8609 $value = dol_print_date($value, 'dayhour', 'tzuserrel');
8610 } else {
8611 $value = '';
8612 }
8613 } elseif ($type == 'duration') {
8614 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8615 if (!is_null($value) && $value !== '') {
8616 $value = convertSecondToTime((int) $value, 'allhourmin');
8617 }
8618 } elseif ($type == 'double' || $type == 'real') {
8619 if (!is_null($value) && $value !== '') {
8620 $value = price($value);
8621 }
8622 } elseif ($type == 'boolean') {
8623 $checked = '';
8624 if (!empty($value)) {
8625 $checked = ' checked ';
8626 }
8627 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
8628 $value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
8629 } else {
8630 $value = yn($value ? 1 : 0);
8631 }
8632 } elseif ($type == 'mail' || $type == 'email') {
8633 $value = dol_print_email((string) $value, 0, 0, 0, 64, 1, 1);
8634 } elseif ($type == 'url') {
8635 $value = dol_print_url((string) $value, '_blank', 32, 1);
8636 } elseif ($type == 'phone') {
8637 $value = dol_print_phone((string) $value, '', 0, 0, '', '&nbsp;', 'phone');
8638 } elseif ($type == 'ip') {
8639 $value = dol_print_ip((string) $value, 0);
8640 } elseif ($type == 'stars') {
8641 $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 : '').'>';
8642 $value .= '<div class="star-selection" id="'.$keyprefix.$key.$keysuffix.$this->id.'_selection">';
8643 $i = 1;
8644 while ($i <= $size) {
8645 $value .= '<span class="star" data-value="'.$i.'">'.img_picto('', 'fontawesome_star_fas').'</span>';
8646 $i++;
8647 }
8648 $value .= '</div>';
8649 $value .= '<script>
8650 $(document).ready(function() { /* commonobject.class.php 2 */
8651 let container = $("#'.$keyprefix.$key.$keysuffix.$this->id.'_selection");
8652 let selectedStars = parseInt($("#'.$keyprefix.$key.$keysuffix.$this->id.'").val()) || 0;
8653 container.find(".star").each(function() {
8654 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8655 });
8656 container.find(".star").on("mouseover", function() {
8657 let selectedStar = $(this).data("value");
8658 container.find(".star").each(function() {
8659 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
8660 });
8661 });
8662 container.on("mouseout", function() {
8663 container.find(".star").each(function() {
8664 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8665 });
8666 });
8667 container.find(".star").off("click").on("click", function() {
8668 selectedStars = $(this).data("value");
8669 if (selectedStars == 1 && $("#'.$keyprefix.$key.$keysuffix.$this->id.'").val() == 1) {
8670 selectedStars = 0;
8671 }
8672 container.find("#'.$keyprefix.$key.$keysuffix.$this->id.'").val(selectedStars);
8673 container.find(".star").each(function() {
8674 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
8675 });
8676 $.ajax({
8677 url: "ajax/'.$this->element.'.php",
8678 method: "POST",
8679 data: {
8680 objectId: "'.$this->id.'",
8681 field: "'.$keyprefix.$key.$keysuffix.'",
8682 value: selectedStars,
8683 token: "'.newToken().'"
8684 },
8685 success: function(response) {
8686 var res = JSON.parse(response);
8687 console[res.status === "success" ? "log" : "error"](res.message);
8688 },
8689 error: function(xhr, status, error) {
8690 console.log("Ajax request failed while updating '.$keyprefix.$key.$keysuffix.':", error);
8691 }
8692 });
8693 });
8694 });
8695 </script>';
8696 } elseif ($type == 'price') {
8697 if (!is_null($value) && $value !== '') {
8698 $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
8699 }
8700 } elseif ($type == 'select') {
8701 $value = isset($param['options'][(string) $value]) ? $param['options'][(string) $value] : '';
8702 if (strpos($value, "|") !== false) {
8703 $value = $langs->trans(explode('|', $value)[0]);
8704 } elseif (! is_numeric($value)) {
8705 $value = $langs->trans($value);
8706 }
8707 } elseif ($type == 'sellist') {
8708 $param_list = array_keys($param['options']);
8709 $InfoFieldList = explode(":", $param_list[0]);
8710
8711 $selectkey = "rowid";
8712 $keyList = 'rowid';
8713
8714 if (count($InfoFieldList) > 2 && !empty($InfoFieldList[2])) {
8715 $selectkey = $InfoFieldList[2];
8716 $keyList = $InfoFieldList[2].' as rowid';
8717 }
8718
8719 $fields_label = explode('|', $InfoFieldList[1]);
8720 if (is_array($fields_label)) {
8721 $keyList .= ', ';
8722 $keyList .= implode(', ', $fields_label);
8723 }
8724
8725 $filter_categorie = false;
8726 if (count($InfoFieldList) > 5) {
8727 if ($InfoFieldList[0] == 'categorie') {
8728 $filter_categorie = true;
8729 }
8730 }
8731
8732 $sql = "SELECT ".$keyList;
8733 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8734 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8735 $sql .= ' as main';
8736 }
8737 if ($selectkey == 'rowid' && empty($value)) {
8738 $sql .= " WHERE ".$selectkey." = 0";
8739 } elseif ($selectkey == 'rowid') {
8740 $sql .= " WHERE ".$selectkey." = ".((int) $value);
8741 } else {
8742 $sql .= " WHERE ".$selectkey." = '".$this->db->escape((string) $value)."'";
8743 }
8744
8745 //$sql.= ' AND entity = '.$conf->entity;
8746
8747 dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
8748 $resql = $this->db->query($sql);
8749 if ($resql) {
8750 if (!$filter_categorie) {
8751 $value = ''; // value was used, so now we reset it to use it to build final output
8752 $numrows = $this->db->num_rows($resql);
8753 if ($numrows) {
8754 $obj = $this->db->fetch_object($resql);
8755
8756 // Several field into label (eq table:code|libelle:rowid)
8757 $fields_label = explode('|', $InfoFieldList[1]);
8758
8759 if (is_array($fields_label) && count($fields_label) > 1) {
8760 foreach ($fields_label as $field_toshow) {
8761 $translabel = '';
8762 if (!empty($obj->$field_toshow)) {
8763 $translabel = $langs->trans($obj->$field_toshow);
8764 }
8765 if ($translabel != $field_toshow) {
8766 $value .= dol_trunc($translabel, 18) . ' ';
8767 } else {
8768 $value .= $obj->$field_toshow . ' ';
8769 }
8770 }
8771 } else {
8772 $translabel = '';
8773 if (!empty($obj->{$InfoFieldList[1]})) {
8774 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8775 }
8776 if ($translabel != $obj->{$InfoFieldList[1]}) {
8777 $value = dol_trunc($translabel, 18);
8778 } else {
8779 $value = $obj->{$InfoFieldList[1]};
8780 }
8781 }
8782 }
8783 } else {
8784 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8785
8786 $toprint = array();
8787 $obj = $this->db->fetch_object($resql);
8788 $c = new Categorie($this->db);
8789 $c->fetch($obj->rowid);
8790 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8791 foreach ($ways as $way) {
8792 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8793 }
8794 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8795 }
8796 } else {
8797 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8798 }
8799 } elseif ($type == 'radio') {
8800 $value = $param['options'][(string) $value];
8801 } elseif ($type == 'checkbox') {
8802 $value_arr = explode(',', (string) $value);
8803 $value = '';
8804 if (is_array($value_arr) && count($value_arr) > 0) {
8805 $toprint = array();
8806 foreach ($value_arr as $keyval => $valueval) {
8807 if (!empty($valueval)) {
8808 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
8809 }
8810 }
8811 if (!empty($toprint)) {
8812 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8813 }
8814 }
8815 } elseif ($type == 'chkbxlst') {
8816 $value_arr = (isset($value) ? explode(',', $value) : array());
8817
8818 $param_list = array_keys($param['options']);
8819 $InfoFieldList = explode(":", $param_list[0]);
8820
8821 $selectkey = "rowid";
8822 $keyList = 'rowid';
8823
8824 if (count($InfoFieldList) >= 3) {
8825 $selectkey = $InfoFieldList[2];
8826 $keyList = $InfoFieldList[2].' as rowid';
8827 }
8828
8829 $fields_label = explode('|', $InfoFieldList[1]);
8830 if (is_array($fields_label)) {
8831 $keyList .= ', ';
8832 $keyList .= implode(', ', $fields_label);
8833 }
8834
8835 $filter_categorie = false;
8836 if (count($InfoFieldList) > 5) {
8837 if ($InfoFieldList[0] == 'categorie') {
8838 $filter_categorie = true;
8839 }
8840 }
8841
8842 $sql = "SELECT ".$keyList;
8843 $sql .= ' FROM '.$this->db->prefix().$InfoFieldList[0];
8844 if (isset($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra') !== false) {
8845 $sql .= ' as main';
8846 }
8847 // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
8848 // $sql.= ' AND entity = '.$conf->entity;
8849
8850 dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
8851 $resql = $this->db->query($sql);
8852 if ($resql) {
8853 if (!$filter_categorie) {
8854 $value = ''; // value was used, so now we reset it to use it to build final output
8855 $toprint = array();
8856 while ($obj = $this->db->fetch_object($resql)) {
8857 // Several field into label (eq table:code|libelle:rowid)
8858 $fields_label = explode('|', $InfoFieldList[1]);
8859 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8860 if (is_array($fields_label) && count($fields_label) > 1) {
8861 foreach ($fields_label as $field_toshow) {
8862 $translabel = '';
8863 if (!empty($obj->$field_toshow)) {
8864 $translabel = $langs->trans($obj->$field_toshow);
8865 }
8866 if ($translabel != $field_toshow) {
8867 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8868 } else {
8869 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
8870 }
8871 }
8872 } else {
8873 $translabel = '';
8874 if (!empty($obj->{$InfoFieldList[1]})) {
8875 $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8876 }
8877 if ($translabel != $obj->{$InfoFieldList[1]}) {
8878 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8879 } else {
8880 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
8881 }
8882 }
8883 }
8884 }
8885 } else {
8886 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8887
8888 $toprint = array();
8889 while ($obj = $this->db->fetch_object($resql)) {
8890 if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8891 $c = new Categorie($this->db);
8892 $c->fetch($obj->rowid);
8893 $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8894 foreach ($ways as $way) {
8895 $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8896 }
8897 }
8898 }
8899 }
8900 $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
8901 } else {
8902 dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
8903 }
8904 } elseif ($type == 'link') {
8905 $out = '';
8906
8907 // only if something to display (perf)
8908 if ($value) {
8909 $param_list = array_keys($param['options']);
8910 // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8911 // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
8912
8913 $InfoFieldList = explode(":", $param_list[0]);
8914
8915 $classname = $InfoFieldList[0];
8916 $classpath = $InfoFieldList[1];
8917
8918 // Set $getnomurlparam1 et getnomurlparam2
8919 $getnomurlparam = 3;
8920 $getnomurlparam2 = '';
8921 $regtmp = array();
8922 if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
8923 $getnomurlparam = $regtmp[1];
8924 }
8925 if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
8926 $getnomurlparam2 = $regtmp[1];
8927 }
8928
8929 if (!empty($classpath)) {
8930 dol_include_once($InfoFieldList[1]);
8931
8932 if ($classname && !class_exists($classname)) {
8933 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
8934 // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management
8935 $element_prop = getElementProperties($classname);
8936 if ($element_prop) {
8937 $classname = $element_prop['classname'];
8938 }
8939 }
8940
8941
8942 if ($classname && class_exists($classname)) {
8943 $object = new $classname($this->db);
8944 '@phan-var-force CommonObject $object';
8945 if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong
8946 '@phan-var-force Product $object';
8947 $result = $object->fetch((int) $value, '', '', '', 0, 1, 1);
8948 } else {
8949 $result = $object->fetch($value);
8950 }
8951 if ($result > 0) {
8952 if ($object->element === 'product') {
8953 '@phan-var-force Product $object';
8954 $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
8955 if (isset($val['get_name_url_params'])) {
8956 $get_name_url_params = explode(':', $val['get_name_url_params']);
8957 if (!empty($get_name_url_params)) {
8958 $param_num_max = count($get_name_url_param_arr) - 1;
8959 foreach ($get_name_url_params as $param_num => $param_value) {
8960 if ($param_num > $param_num_max) {
8961 break;
8962 }
8963 $get_name_url_param_arr[$param_num] = $param_value;
8964 }
8965 }
8966 }
8967
8971 $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]);
8972 } else {
8973 $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
8974 }
8975 } else {
8976 $value = '';
8977 }
8978 }
8979 } else {
8980 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
8981 return 'Error bad setup of extrafield';
8982 }
8983 } else {
8984 $value = '';
8985 }
8986 } elseif ($type == 'password') {
8987 $value = '<span class="opacitymedium">'.$langs->trans("Encrypted").'</span>';
8988 //$value = preg_replace('/./i', '*', $value);
8989 } elseif ($type == 'array') {
8990 if (is_array($value)) {
8991 $value = implode('<br>', $value);
8992 } else {
8993 dol_syslog(__METHOD__.' Expected array from dol_eval, but got '.gettype($value), LOG_ERR);
8994 return 'Error unexpected result from code evaluation';
8995 }
8996 } else { // text|html|varchar
8997 if (!empty($value) && preg_match('/^text/', (string) $type) && !preg_match('/search_/', $keyprefix) && !empty($param['options'])) {
8998 $value = str_replace(',', "\n", $value);
8999 }
9000 $value = dol_htmlentitiesbr((string) $value);
9001 }
9002
9003 //print $type.'-'.$size.'-'.$value;
9004 $out = $value;
9005
9006 return is_null($out) ? '' : $out;
9007 }
9008
9015 public function clearFieldError($fieldKey)
9016 {
9017 $this->error = '';
9018 unset($this->validateFieldsErrors[$fieldKey]);
9019 }
9020
9028 public function setFieldError($fieldKey, $msg = '')
9029 {
9030 global $langs;
9031 if (empty($msg)) {
9032 $msg = $langs->trans("UnknownError");
9033 }
9034
9035 $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
9036 }
9037
9044 public function getFieldError($fieldKey)
9045 {
9046 if (!empty($this->validateFieldsErrors[$fieldKey])) {
9047 return $this->validateFieldsErrors[$fieldKey];
9048 }
9049 return '';
9050 }
9051
9060 public function validateField($fields, $fieldKey, $fieldValue)
9061 {
9062 global $langs;
9063
9064 if (!class_exists('Validate')) {
9065 require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
9066 }
9067
9068 $this->clearFieldError($fieldKey);
9069
9070 if (!array_key_exists($fieldKey, $fields) || !is_array($fields[$fieldKey])) {
9071 $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
9072 return false;
9073 }
9074
9075 $val = $fields[$fieldKey];
9076
9077 $param = array();
9078 $param['options'] = array();
9079 $type = $val['type'];
9080
9081 $required = false;
9082 if (isset($val['notnull']) && $val['notnull'] === 1) {
9083 // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
9084 $required = true;
9085 }
9086
9087 $maxSize = 0;
9088 $minSize = 0;
9089
9090 //
9091 // PREPARE Elements
9092 //
9093 $reg = array();
9094
9095 // Convert var to be able to share same code than showOutputField of extrafields
9096 if (preg_match('/varchar\‍((\d+)\‍)/', $type, $reg)) {
9097 $type = 'varchar'; // convert varchar(xx) int varchar
9098 $maxSize = (int) $reg[1];
9099 } elseif (preg_match('/varchar/', $type)) {
9100 $type = 'varchar'; // convert varchar(xx) int varchar
9101 }
9102
9103 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9104 $type = 'select';
9105 }
9106
9107 if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9108 $type = 'link';
9109 }
9110
9111 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
9112 $param['options'] = $val['arrayofkeyval'];
9113 }
9114
9115 if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
9116 $type = 'link';
9117 $param['options'] = array($reg[1].':'.$reg[2] => $reg[1].':'.$reg[2]);
9118 } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
9119 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
9120 $type = 'sellist';
9121 } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
9122 $param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
9123 $type = 'sellist';
9124 } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
9125 $param['options'] = array($reg[1].':'.$reg[2] => 'N');
9126 $type = 'sellist';
9127 }
9128
9129 //
9130 // TEST Value
9131 //
9132
9133 // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
9134 $validate = new Validate($this->db, $langs);
9135
9136
9137 // little trick : to perform tests with good performances sort tests by quick to low
9138
9139 //
9140 // COMMON TESTS
9141 //
9142
9143 // Required test and empty value
9144 if ($required && !$validate->isNotEmptyString($fieldValue)) {
9145 $this->setFieldError($fieldKey, $validate->error);
9146 return false;
9147 } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
9148 // if no value sent and the field is not mandatory, no need to perform tests
9149 return true;
9150 }
9151
9152 // MAX Size test
9153 if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
9154 $this->setFieldError($fieldKey, $validate->error);
9155 return false;
9156 }
9157
9158 // MIN Size test
9159 if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
9160 $this->setFieldError($fieldKey, $validate->error);
9161 return false;
9162 }
9163
9164 //
9165 // TESTS for TYPE
9166 //
9167
9168 if (in_array($type, array('date', 'datetime', 'timestamp'))) {
9169 if (!$validate->isTimestamp($fieldValue)) {
9170 $this->setFieldError($fieldKey, $validate->error);
9171 return false;
9172 } else {
9173 return true;
9174 }
9175 } elseif ($type == 'duration') {
9176 if (!$validate->isDuration($fieldValue)) {
9177 $this->setFieldError($fieldKey, $validate->error);
9178 return false;
9179 } else {
9180 return true;
9181 }
9182 } elseif (in_array($type, array('double', 'real', 'price'))) {
9183 // is numeric
9184 if (!$validate->isNumeric($fieldValue)) {
9185 $this->setFieldError($fieldKey, $validate->error);
9186 return false;
9187 } else {
9188 return true;
9189 }
9190 } elseif ($type == 'boolean') {
9191 if (!$validate->isBool($fieldValue)) {
9192 $this->setFieldError($fieldKey, $validate->error);
9193 return false;
9194 } else {
9195 return true;
9196 }
9197 } elseif ($type == 'mail' || $type == 'email') {
9198 if (!$validate->isEmail($fieldValue)) {
9199 $this->setFieldError($fieldKey, $validate->error);
9200 return false;
9201 }
9202 } elseif ($type == 'url') {
9203 if (!$validate->isUrl($fieldValue)) {
9204 $this->setFieldError($fieldKey, $validate->error);
9205 return false;
9206 } else {
9207 return true;
9208 }
9209 } elseif ($type == 'phone') {
9210 if (!$validate->isPhone($fieldValue)) {
9211 $this->setFieldError($fieldKey, $validate->error);
9212 return false;
9213 } else {
9214 return true;
9215 }
9216 } elseif ($type == 'select' || $type == 'radio') {
9217 if (!isset($param['options'][$fieldValue])) {
9218 $this->error = $langs->trans('RequireValidValue');
9219 return false;
9220 } else {
9221 return true;
9222 }
9223 } elseif ($type == 'sellist' || $type == 'chkbxlst') {
9224 $param_list = array_keys($param['options']);
9225 $InfoFieldList = explode(":", $param_list[0]);
9226 $value_arr = explode(',', $fieldValue);
9227 $value_arr = array_map(array($this->db, 'escape'), $value_arr);
9228
9229 $selectkey = "rowid";
9230 if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
9231 $selectkey = $InfoFieldList[2];
9232 }
9233
9234 if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
9235 $this->setFieldError($fieldKey, $validate->error);
9236 return false;
9237 } else {
9238 return true;
9239 }
9240 } elseif ($type == 'link') {
9241 $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
9242 $InfoFieldList = explode(":", $param_list[0]);
9243 $classname = $InfoFieldList[0];
9244 $classpath = $InfoFieldList[1];
9245 if (!$validate->isFetchable((int) $fieldValue, $classname, $classpath)) {
9246 $lastIsFetchableError = $validate->error;
9247
9248 // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
9249 if ($validate->isFetchableElement((int) $fieldValue, $classname)) {
9250 return true;
9251 }
9252
9253 $this->setFieldError($fieldKey, $lastIsFetchableError);
9254 return false;
9255 } else {
9256 return true;
9257 }
9258 }
9259
9260 // if no test failed all is ok
9261 return true;
9262 }
9263
9277 public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
9278 {
9279 global $db, $conf, $langs, $action, $form, $hookmanager;
9280
9281 if (!is_object($form)) {
9282 $form = new Form($db);
9283 }
9284 if (!is_object($extrafields)) {
9285 dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
9286 return 'Bad parameter extrafields for showOptionals';
9287 }
9288 if (!is_array($extrafields->attributes[$this->table_element])) {
9289 dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
9290 }
9291
9292 $out = '';
9293
9294 $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
9295 $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
9296
9297 if (empty($reshook)) {
9298 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) {
9299 $out .= "\n";
9300 $out .= '<!-- commonobject:showOptionals --> ';
9301 $out .= "\n";
9302
9303 $nbofextrafieldsshown = 0;
9304 $e = 0; // var to manage the modulo (odd/even)
9305
9306 $lastseparatorkeyfound = '';
9307 $extrafields_collapse_num = '';
9308 $extrafields_collapse_num_old = '';
9309 $i = 0;
9310
9311 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
9312 $i++;
9313
9314 // Show only the key field in params @phan-suppress-next-line PhanTypeArraySuspiciousNullable
9315 if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
9316 continue;
9317 }
9318
9319 // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
9320 $enabled = 1;
9321 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
9322 $enabled = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
9323 }
9324 if (empty($enabled)) {
9325 continue;
9326 }
9327
9328 $visibility = 1;
9329 if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
9330 $visibility = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
9331 }
9332
9333 $perms = 1;
9334 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
9335 $perms = (int) dol_eval((string) $extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
9336 }
9337
9338 if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
9339 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
9340 } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
9341 // We need to make sure, that the values of hidden extrafields are also part of $_POST. Otherwise, they would be empty after an update of the object. See also getOptionalsFromPost
9342 $ef_name = 'options_' . $key;
9343 $ef_value = $this->array_options[$ef_name];
9344 $out .= '<input type="hidden" name="' . $ef_name . '" id="' . $ef_name . '" value="' . $ef_value . '" />' . "\n";
9345 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
9346 } elseif ($mode == 'view' && empty($visibility)) {
9347 continue;
9348 }
9349 if (empty($perms)) {
9350 continue;
9351 }
9352
9353 // Load language if required
9354 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
9355 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
9356 }
9357
9358 $colspan = 0;
9359 $value = null;
9360 if (is_array($params) && count($params) > 0 && $display_type == 'card') {
9361 if (array_key_exists('cols', $params)) {
9362 $colspan = $params['cols'];
9363 } elseif (array_key_exists('colspan', $params)) { // For backward compatibility. Use cols instead now.
9364 $reg = array();
9365 if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
9366 $colspan = $reg[1];
9367 } else {
9368 $colspan = $params['colspan'];
9369 }
9370 }
9371 }
9372 $colspan = intval($colspan);
9373
9374 switch ($mode) {
9375 case "view":
9376 $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
9377 break;
9378 case "create":
9379 case "edit":
9380 // We get the value of property found with GETPOST so it takes into account:
9381 // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
9382 $check = 'alphanohtml';
9383 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
9384 $check = 'restricthtml';
9385 }
9386 $getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
9387 // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
9388 if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
9389 if (is_array($getposttemp)) {
9390 // $getposttemp is an array but following code expects a comma separated string
9391 $value = implode(",", $getposttemp);
9392 } else {
9393 $value = $getposttemp;
9394 }
9395 } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
9396 $value = (!empty($this->array_options["options_".$key]) || (isset($this->array_options["options_".$key]) && $this->array_options["options_".$key] === '0')) ? $this->array_options["options_".$key] : '';
9397 } else {
9398 $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.
9399 }
9400 //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
9401 break;
9402 }
9403
9404 $nbofextrafieldsshown++;
9405
9406 // Output value of the current field
9407 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
9408 $extrafields_collapse_num = $key;
9409 /*
9410 $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
9411 if (!empty($extrafield_param) && is_array($extrafield_param)) {
9412 $extrafield_param_list = array_keys($extrafield_param['options']);
9413
9414 if (count($extrafield_param_list) > 0) {
9415 $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
9416
9417 if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
9418 //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
9419 $extrafields_collapse_num = $key;
9420 }
9421 }
9422 }
9423 */
9424
9425 // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
9426 $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
9427
9428 $lastseparatorkeyfound = $key;
9429 } else {
9430 $collapse_group = $extrafields_collapse_num.(!empty($this->id) ? '_'.$this->id : '');
9431
9432 $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
9433 $csstyle = '';
9434 if (is_array($params) && count($params) > 0) {
9435 if (array_key_exists('class', $params)) {
9436 $class .= $params['class'].' ';
9437 }
9438 if (array_key_exists('style', $params)) {
9439 $csstyle = $params['style'];
9440 }
9441 }
9442
9443 // add html5 elements
9444 $domData = ' data-element="extrafield"';
9445 $domData .= ' data-targetelement="'.$this->element.'"';
9446 $domData .= ' data-targetid="'.$this->id.'"';
9447
9448 $html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
9449 if ($display_type == 'card') {
9450 if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
9451 $colspan = 0;
9452 }
9453
9454 if ($action == 'selectlines') {
9455 $colspan++;
9456 }
9457 }
9458
9459 // Expected behavior : if THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set, when we change company,
9460 // Then we use the extrafields of the object (they are filled in the card when constant THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_XXX is set)
9461 $force_values_on_change_company = (
9462 ($this->element == 'facture' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_INVOICE'))
9463 || ($this->element == 'commande' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_ORDER'))
9464 || ($this->element == 'order_supplier' && getDolGlobalInt('THIRDPARTY_PROPAGATE_EXTRAFIELDS_TO_SUPPLIER_ORDER'))
9465 );
9466 if ($force_values_on_change_company && GETPOSTINT('changecompany')) {
9467 $value = $this->array_options['options_'.$key] ?? $value;
9468 }
9469
9470 // Convert date into timestamp format (value in memory must be a timestamp)
9471 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
9472 $datenotinstring = null;
9473 if (array_key_exists('options_'.$key, $this->array_options)) {
9474 $datenotinstring = $this->array_options['options_'.$key];
9475 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9476 $datenotinstring = $this->db->jdate($datenotinstring);
9477 }
9478 }
9479 $datekey = $keyprefix.'options_'.$key.$keysuffix;
9480 $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;
9481 }
9482 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
9483 $datenotinstring = null;
9484 if (array_key_exists('options_'.$key, $this->array_options)) {
9485 $datenotinstring = $this->array_options['options_'.$key];
9486 if (!is_numeric($this->array_options['options_'.$key])) { // For backward compatibility
9487 $datenotinstring = $this->db->jdate($datenotinstring);
9488 }
9489 }
9490 $timekey = $keyprefix.'options_'.$key.$keysuffix;
9491 $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;
9492 }
9493 // Convert float submitted string into real php numeric (value in memory must be a php numeric)
9494 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
9495 if (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) {
9496 $value = price2num($value);
9497 } elseif (isset($this->array_options['options_'.$key])) {
9498 $value = $this->array_options['options_'.$key];
9499 }
9500 }
9501
9502 // HTML, text, select, integer and varchar: take into account default value in database if in create mode
9503 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
9504 if ($action == 'create' || $mode == 'create') {
9505 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
9506 }
9507 }
9508
9509 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('checkbox'))) {
9510 if ($action == 'create') {
9511 $value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : explode(',', $extrafields->attributes[$this->table_element]['default'][$key]);
9512 }
9513 }
9514
9515
9516 $labeltoshow = $langs->trans($label);
9517 $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
9518 if ($display_type == 'card') {
9519 $out .= '<tr '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="field_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9520 if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
9521 $out .= '<td></td>';
9522 }
9523 $out .= '<td class="'.(empty($params['tdclass']) ? 'titlefieldmax45' : $params['tdclass']).' wordbreak';
9524 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
9525 $out .= ' tdtop';
9526 }
9527 } elseif ($display_type == 'line') {
9528 $out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="fieldline_options_'.$key.' '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$collapse_group.'" '.$domData.' >';
9529 $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
9530 }
9531 //$out .= "titlefield";
9532 //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
9533 // BUG #11554 : For public page, use red dot for required fields, instead of bold label
9534 $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
9535 if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
9536 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9537 $out .= ' fieldrequired';
9538 }
9539 }
9540 $out .= '">';
9541 if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
9542 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9543 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9544 } else {
9545 $out .= $labeltoshow;
9546 }
9547 if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
9548 $out .= '&nbsp;<span style="color: red">*</span>';
9549 }
9550 } else {
9551 if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
9552 $out .= $form->textwithpicto($labeltoshow, $helptoshow);
9553 } else {
9554 $out .= $labeltoshow;
9555 }
9556 }
9557
9558 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9559
9560 // Second column
9561 $html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
9562 if ($display_type == 'card') {
9563 // 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
9564 $out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="valuefieldcreate '.$this->element.'_extras_'.$key;
9565 $out .= '" '.($colspan ? ' colspan="'.$colspan.'"' : '');
9566 $out .= '>';
9567 } elseif ($display_type == 'line') {
9568 $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].'">';
9569 }
9570
9571 switch ($mode) {
9572 case "view":
9573 $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
9574 break;
9575 case "create":
9576 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9577 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9578 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9579 }
9580 //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
9581 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9582 break;
9583 case "edit":
9584 $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICTO', 'email,phone,ip,password'));
9585 if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
9586 $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
9587 }
9588 $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', '', $this, $this->table_element);
9589 break;
9590 }
9591
9592 $out .= ($display_type == 'card' ? '</td>' : '</div>');
9593 $out .= ($display_type == 'card' ? '</tr>'."\n" : '</div>');
9594 $e++;
9595 }
9596 }
9597 $out .= "\n";
9598 // Add code to manage list depending on others
9599 if (!empty($conf->use_javascript_ajax)) {
9600 $out .= $this->getJSListDependancies();
9601 }
9602
9603 $out .= '<!-- commonobject:showOptionals end --> '."\n";
9604
9605 if (empty($nbofextrafieldsshown)) {
9606 $out = '';
9607 }
9608 }
9609 }
9610
9611 $out .= $hookmanager->resPrint;
9612
9613 return $out;
9614 }
9615
9620 public function getJSListDependancies($type = '_extra')
9621 {
9622 $out = '
9623 <script nonce="'.getNonce().'">
9624 jQuery(document).ready(function() {
9625 function showOptions'.$type.'(child_list, parent_list, orig_select)
9626 {
9627 var val = $("select[name=\""+parent_list+"\"]").val();
9628 var parentVal = parent_list + ":" + val;
9629 if(typeof val == "string"){
9630 if(val != "") {
9631 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9632 $("select[name=\""+child_list+"\"] option[parent]").remove();
9633 $("select[name=\""+child_list+"\"]").append(options);
9634 } else {
9635 var options = orig_select.find("option[parent]").clone();
9636 $("select[name=\""+child_list+"\"] option[parent]").remove();
9637 $("select[name=\""+child_list+"\"]").append(options);
9638 }
9639 } else if(val > 0) {
9640 var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
9641 $("select[name=\""+child_list+"\"] option[parent]").remove();
9642 $("select[name=\""+child_list+"\"]").append(options);
9643 } else {
9644 var options = orig_select.find("option[parent]").clone();
9645 $("select[name=\""+child_list+"\"] option[parent]").remove();
9646 $("select[name=\""+child_list+"\"]").append(options);
9647 }
9648 }
9649 function setListDependencies'.$type.'() {
9650 jQuery("select option[parent]").parent().each(function() {
9651 var orig_select = {};
9652 var child_list = $(this).attr("name");
9653 orig_select[child_list] = $(this).clone();
9654 var parent = $(this).find("option[parent]:first").attr("parent");
9655 var infos = parent.split(":");
9656 var parent_list = infos[0];
9657
9658 //Hide daughters lists
9659 if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
9660 $("#"+child_list).hide();
9661 //Show mother lists
9662 } else if ($("#"+parent_list).val() != 0){
9663 $("#"+parent_list).show();
9664 }
9665 //Show the child list if the parent list value is selected
9666 $("select[name=\""+parent_list+"\"]").click(function() {
9667 if ($(this).val() != 0){
9668 $("#"+child_list).show()
9669 }
9670 });
9671
9672 //When we change parent list
9673 $("select[name=\""+parent_list+"\"]").change(function() {
9674 showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
9675 //Select the value 0 on child list after a change on the parent list
9676 $("#"+child_list).val(0).trigger("change");
9677 //Hide child lists if the parent value is set to 0
9678 if ($(this).val() == 0){
9679 $("#"+child_list).hide();
9680 }
9681 });
9682 });
9683 }
9684
9685 setListDependencies'.$type.'();
9686 });
9687 </script>'."\n";
9688 return $out;
9689 }
9690
9696 public function getRights()
9697 {
9698 global $user;
9699
9700 $module = empty($this->module) ? '' : $this->module;
9701 $element = $this->element;
9702
9703 if ($element == 'facturerec') {
9704 $element = 'facture';
9705 } elseif ($element == 'invoice_supplier_rec') {
9706 return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
9707 } elseif ($module && $user->hasRight($module, $element)) {
9708 // for modules built with ModuleBuilder
9709 return $user->hasRight($module, $element);
9710 }
9711
9712 return $user->rights->$element;
9713 }
9714
9727 public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9728 {
9729 global $hookmanager;
9730
9731 $parameters = array(
9732 'origin_id' => $origin_id,
9733 'dest_id' => $dest_id,
9734 'tables' => $tables,
9735 );
9736 $reshook = $hookmanager->executeHooks('commonReplaceThirdparty', $parameters);
9737 if ($reshook) {
9738 return true; // replacement code
9739 } elseif ($reshook < 0) {
9740 return $ignoreerrors === 1; // failure
9741 } // reshook = 0 => execute normal code
9742
9743 foreach ($tables as $table) {
9744 $sql = 'UPDATE '.$dbs->prefix().$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
9745
9746 if (!$dbs->query($sql)) {
9747 if ($ignoreerrors) {
9748 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.
9749 }
9750 //$this->errors = $db->lasterror();
9751 return false;
9752 }
9753 }
9754
9755 return true;
9756 }
9757
9770 public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
9771 {
9772 foreach ($tables as $table) {
9773 $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
9774
9775 if (!$dbs->query($sql)) {
9776 if ($ignoreerrors) {
9777 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.
9778 }
9779 //$this->errors = $db->lasterror();
9780 return false;
9781 }
9782 }
9783
9784 return true;
9785 }
9786
9799 public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
9800 {
9801 global $conf;
9802
9803 $buyPrice = 0;
9804
9805 if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
9806 // When ForceBuyingPriceIfNull is set
9807 $buyPrice = $unitPrice * (1 - $discountPercent / 100);
9808 } else {
9809 // Get cost price for margin calculation
9810 if (!empty($fk_product) && $fk_product > 0) {
9811 $result = 0;
9812 if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
9813 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9814 $product = new Product($this->db);
9815 $result = $product->fetch($fk_product);
9816 if ($result <= 0) {
9817 $this->errors[] = 'ErrorProductIdDoesNotExists';
9818 return -1;
9819 }
9820 if ($product->cost_price > 0) {
9821 $buyPrice = $product->cost_price;
9822 } elseif ($product->pmp > 0) {
9823 $buyPrice = $product->pmp;
9824 }
9825 } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
9826 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
9827 $product = new Product($this->db);
9828 $result = $product->fetch($fk_product);
9829 if ($result <= 0) {
9830 $this->errors[] = 'ErrorProductIdDoesNotExists';
9831 return -1;
9832 }
9833 if ($product->pmp > 0) {
9834 $buyPrice = $product->pmp;
9835 }
9836 }
9837
9838 if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
9839 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
9840 $productFournisseur = new ProductFournisseur($this->db);
9841 if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
9842 $buyPrice = $productFournisseur->fourn_unitprice;
9843 } elseif ($result < 0) {
9844 $this->errors[] = $productFournisseur->error;
9845 return -2;
9846 }
9847 }
9848 }
9849 }
9850
9851 return (float) $buyPrice;
9852 }
9853
9861 public function getDataToShowPhoto($modulepart, $imagesize)
9862 {
9863 // See getDataToShowPhoto() implemented by Product for example.
9864 return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
9865 }
9866
9867
9868 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
9888 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')
9889 {
9890 // phpcs:enable
9891 global $user, $langs;
9892
9893 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
9894 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
9895
9896 $sortfield = 'position_name';
9897 $sortorder = 'asc';
9898
9899 $dir = $sdir.'/';
9900 $pdir = '/';
9901
9902 $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9903 $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9904
9905 // For backward compatibility
9906 if ($modulepart == 'product') {
9907 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
9908 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9909 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9910 }
9911 }
9912 if ($modulepart == 'category') {
9913 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9914 $pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
9915 }
9916
9917 // Defined relative dir to DOL_DATA_ROOT
9918 $relativedir = '';
9919 if ($dir) {
9920 $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
9921 $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
9922 $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
9923 }
9924
9925 $dirthumb = $dir.'thumbs/';
9926 $pdirthumb = $pdir.'thumbs/';
9927
9928 $return = '<!-- Photo -->'."\n";
9929 $nbphoto = 0;
9930
9931 $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
9932
9933 /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) // For backward compatibility, we scan also old dirs
9934 {
9935 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
9936 $filearray=array_merge($filearray, $filearrayold);
9937 }*/
9938
9939 completeFileArrayWithDatabaseInfo($filearray, $relativedir, $this);
9940 '@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';
9941
9942 if (count($filearray)) {
9943 if ($sortfield && $sortorder) {
9944 $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
9945 }
9946
9947 foreach ($filearray as $key => $val) {
9948 $photo = '';
9949 $file = $val['name'];
9950
9951 //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
9952 if (image_format_supported($file) >= 0) {
9953 $nbphoto++;
9954 $photo = $file;
9955 $viewfilename = $file;
9956
9957 if ($size == 1 || $size == 'small') { // Format vignette
9958 // Find name of thumb file
9959 $photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
9960 if (!dol_is_file($dirthumb.$photo_vignette)) {
9961 // The thumb does not exists, so we will use the original file
9962 $dirthumb = $dir;
9963 $pdirthumb = $pdir;
9964 $photo_vignette = basename($file);
9965 }
9966
9967 // Get filesize of original file
9968 $imgarray = dol_getImageSize($dir.$photo);
9969
9970 if ($nbbyrow > 0) {
9971 if ($nbphoto == 1) {
9972 $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
9973 }
9974
9975 if ($nbphoto % $nbbyrow == 1) {
9976 $return .= '<tr class="center valignmiddle" style="border: 1px">';
9977 }
9978 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">'."\n";
9979 } elseif ($nbbyrow < 0) {
9980 $return .= '<div class="inline-block">'."\n";
9981 }
9982
9983 $relativefile = preg_replace('/^\//', '', $pdir.$photo);
9984 if (empty($nolink)) {
9985 $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
9986 if ($urladvanced) {
9987 $return .= '<a href="'.$urladvanced.'">';
9988 } else {
9989 $return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank" rel="noopener noreferrer">';
9990 }
9991 }
9992
9993 // Show image (width height=$maxHeight)
9994 // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
9995 $alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
9996 $alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
9997 if ($overwritetitle) {
9998 if (is_numeric($overwritetitle)) {
9999 $alt = '';
10000 } else {
10001 $alt = $overwritetitle;
10002 }
10003 }
10004 if (empty($cache) && !empty($val['label'])) {
10005 // label is md5 of file
10006 // use it in url to say we want to cache this version of the file
10007 $cache = $val['label'];
10008 }
10009 if ($usesharelink) {
10010 if (array_key_exists('share', $val) && $val['share']) {
10011 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10012 $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
10013 $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).'">';
10014 } else {
10015 $return .= '<!-- Show original file -->';
10016 $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).'">';
10017 }
10018 } else {
10019 $return .= '<!-- Show nophoto file (because file is not shared) -->';
10020 $return .= '<img class="photo photowithmargin'.($addphotorefcss ? ' '.$addphotorefcss : '').'" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
10021 }
10022 } else {
10023 if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
10024 $return .= '<!-- Show thumb -->';
10025 $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).'">';
10026 } else {
10027 $return .= '<!-- Show original file -->';
10028 $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).'">';
10029 }
10030 }
10031
10032 if (empty($nolink)) {
10033 $return .= '</a>';
10034 }
10035
10036 if ($showfilename) {
10037 $return .= '<br>'.$viewfilename;
10038 }
10039 if ($showaction) {
10040 $return .= '<br>';
10041 // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
10042 if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
10043 $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>';
10044 }
10045 // Special case for product
10046 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10047 // Link to resize
10048 $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; ';
10049
10050 // Link to delete
10051 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10052 $return .= img_delete().'</a>';
10053 }
10054 }
10055 $return .= "\n";
10056
10057 if ($nbbyrow > 0) {
10058 $return .= '</td>';
10059 if (($nbphoto % $nbbyrow) == 0) {
10060 $return .= '</tr>';
10061 }
10062 } elseif ($nbbyrow < 0) {
10063 $return .= '</div>'."\n";
10064 }
10065 }
10066
10067 if (empty($size)) { // Format origine
10068 $return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
10069
10070 if ($showfilename) {
10071 $return .= '<br>'.$viewfilename;
10072 }
10073 if ($showaction) {
10074 // Special case for product
10075 if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
10076 // Link to resize
10077 $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; ';
10078
10079 // Link to delete
10080 $return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
10081 $return .= img_delete().'</a>';
10082 }
10083 }
10084 }
10085
10086 // On continue ou on arrete de boucler ?
10087 if ($nbmax && $nbphoto >= $nbmax) {
10088 break;
10089 }
10090 }
10091 }
10092
10093 if ($size == 1 || $size == 'small') {
10094 if ($nbbyrow > 0) {
10095 // Ferme tableau
10096 while ($nbphoto % $nbbyrow) {
10097 $return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
10098 $nbphoto++;
10099 }
10100
10101 if ($nbphoto) {
10102 $return .= '</table>';
10103 }
10104 }
10105 }
10106 }
10107
10108 $this->nbphoto = $nbphoto;
10109
10110 return $return;
10111 }
10112
10113
10120 protected function isArray($info)
10121 {
10122 if (is_array($info)) {
10123 if (isset($info['type']) && $info['type'] == 'array') {
10124 return true;
10125 } else {
10126 return false;
10127 }
10128 }
10129 return false;
10130 }
10131
10138 public function isDate($info)
10139 {
10140 if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
10141 return true;
10142 }
10143 return false;
10144 }
10145
10152 public function isDuration($info)
10153 {
10154 if (is_array($info)) {
10155 if (isset($info['type']) && ($info['type'] == 'duration')) {
10156 return true;
10157 } else {
10158 return false;
10159 }
10160 } else {
10161 return false;
10162 }
10163 }
10164
10171 public function isInt($info)
10172 {
10173 if (is_array($info)) {
10174 if (isset($info['type']) && (preg_match('/(^int|int$)/i', $info['type']))) {
10175 return true;
10176 } else {
10177 return false;
10178 }
10179 } else {
10180 return false;
10181 }
10182 }
10183
10190 public function isFloat($info)
10191 {
10192 if (is_array($info)) {
10193 if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
10194 return true;
10195 } else {
10196 return false;
10197 }
10198 }
10199 return false;
10200 }
10201
10208 public function isText($info)
10209 {
10210 if (is_array($info)) {
10211 if (isset($info['type']) && $info['type'] == 'text') {
10212 return true;
10213 } else {
10214 return false;
10215 }
10216 }
10217 return false;
10218 }
10219
10226 protected function canBeNull($info)
10227 {
10228 if (is_array($info)) {
10229 if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10230 return true;
10231 } else {
10232 return false;
10233 }
10234 }
10235 return true;
10236 }
10237
10244 protected function isForcedToNullIfZero($info)
10245 {
10246 if (is_array($info)) {
10247 if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
10248 return true;
10249 } else {
10250 return false;
10251 }
10252 }
10253 return false;
10254 }
10255
10262 protected function isIndex($info)
10263 {
10264 if (is_array($info)) {
10265 if (array_key_exists('index', $info) && $info['index'] == true) {
10266 return true;
10267 } else {
10268 return false;
10269 }
10270 }
10271 return false;
10272 }
10273
10274
10283 protected function setSaveQuery()
10284 {
10285 global $conf;
10286
10287 $queryarray = array();
10288 foreach ($this->fields as $field => $info) { // Loop on definition of fields
10289 // Depending on field type ('datetime', ...)
10290 if ($this->isDate($info)) {
10291 if (empty($this->{$field})) {
10292 $queryarray[$field] = null;
10293 } else {
10294 $queryarray[$field] = $this->db->idate($this->{$field});
10295 }
10296 } elseif ($this->isDuration($info)) {
10297 // $this->{$field} may be null, '', 0, '0', 123, '123'
10298 if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
10299 if (!isset($this->{$field})) {
10300 if (!empty($info['default'])) {
10301 $queryarray[$field] = $info['default'];
10302 } else {
10303 $queryarray[$field] = 0;
10304 }
10305 } else {
10306 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10307 }
10308 } else {
10309 $queryarray[$field] = null;
10310 }
10311 } elseif ($this->isInt($info) || $this->isFloat($info)) {
10312 if ($field == 'entity' && is_null($this->{$field})) {
10313 $queryarray[$field] = ((int) $conf->entity);
10314 } else {
10315 // $this->{$field} may be null, '', 0, '0', 123, '123'
10316 if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
10317 if (!isset($this->{$field})) {
10318 $queryarray[$field] = 0;
10319 } elseif ($this->isInt($info)) {
10320 $queryarray[$field] = (int) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10321 } elseif ($this->isFloat($info)) {
10322 $queryarray[$field] = (float) $this->{$field}; // If '0', it may be set to null later if $info['notnull'] == -1
10323 }
10324 } else {
10325 $queryarray[$field] = null;
10326 }
10327 }
10328 } else {
10329 // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
10330 // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
10331 $queryarray[$field] = $this->{$field};
10332 }
10333
10334 if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
10335 unset($queryarray[$field]);
10336 }
10337 if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
10338 $queryarray[$field] = null; // May force 0 to null
10339 }
10340 }
10341
10342 return $queryarray;
10343 }
10344
10351 public function setVarsFromFetchObj(&$obj)
10352 {
10353 global $db;
10354
10355 foreach ($this->fields as $field => $info) {
10356 if ($this->isDate($info)) {
10357 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') {
10358 $this->$field = '';
10359 } else {
10360 $this->$field = $db->jdate($obj->$field);
10361 }
10362 } elseif ($this->isInt($info)) {
10363 if ($field == 'rowid') {
10364 $this->id = (int) $obj->$field;
10365 } else {
10366 if ($this->isForcedToNullIfZero($info)) {
10367 if (empty($obj->$field)) {
10368 $this->$field = null;
10369 } else {
10370 $this->$field = (int) $obj->$field;
10371 }
10372 } else {
10373 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10374 $this->$field = (int) $obj->$field;
10375 } else {
10376 $this->$field = null;
10377 }
10378 }
10379 }
10380 } elseif ($this->isFloat($info)) {
10381 if ($this->isForcedToNullIfZero($info)) {
10382 if (empty($obj->$field)) {
10383 $this->$field = null;
10384 } else {
10385 $this->$field = (float) $obj->$field;
10386 }
10387 } else {
10388 if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
10389 $this->$field = (float) $obj->$field;
10390 } else {
10391 $this->$field = null;
10392 }
10393 }
10394 } else {
10395 $this->$field = isset($obj->$field) ? $obj->$field : null;
10396 }
10397 }
10398
10399 // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
10400 if (!isset($this->fields['ref']) && isset($this->id)) {
10401 $this->ref = (string) $this->id;
10402 }
10403 }
10404
10409 public function emtpyObjectVars()
10410 {
10411 foreach ($this->fields as $field => $arr) {
10412 $this->$field = null;
10413 }
10414 }
10415
10423 public function getFieldList($alias = '', $excludefields = array())
10424 {
10425 $keys = array_keys($this->fields);
10426 if (!empty($alias)) {
10427 $keys_with_alias = array();
10428 foreach ($keys as $fieldname) {
10429 if (!empty($excludefields)) {
10430 if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
10431 continue;
10432 }
10433 }
10434 $keys_with_alias[] = $alias . '.' . $fieldname;
10435 }
10436 return implode(', ', $keys_with_alias);
10437 } else {
10438 return implode(', ', $keys);
10439 }
10440 }
10441
10449 protected function quote($value, $fieldsentry)
10450 {
10451 if (is_null($value)) {
10452 return 'NULL';
10453 } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
10454 return price2num((string) $value);
10455 } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
10456 return (int) $value;
10457 } elseif ($fieldsentry['type'] == 'boolean') {
10458 if ($value) {
10459 return 'true';
10460 } else {
10461 return 'false';
10462 }
10463 } else {
10464 return "'".$this->db->escape($value)."'";
10465 }
10466 }
10467
10468
10476 public function createCommon(User $user, $notrigger = 0)
10477 {
10478 global $langs;
10479
10480 dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
10481
10482 $error = 0;
10483
10484 $now = dol_now();
10485
10486 $fieldvalues = $this->setSaveQuery();
10487
10488 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10489
10490 if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
10491 $fieldvalues['date_creation'] = $this->db->idate($now);
10492 $this->date_creation = $this->db->idate($now);
10493 }
10494 // For backward compatibility, if a property ->fk_user_creat exists and not filled.
10495 if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
10496 $fieldvalues['fk_user_creat'] = $user->id;
10497 $this->fk_user_creat = $user->id;
10498 }
10499 if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
10500 $fieldvalues['user_creation_id'] = $user->id;
10501 $this->user_creation_id = $user->id;
10502 }
10503 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
10504 // @phan-suppress-next-line PhanUndeclaredProperty
10505 $tmparray = dol_hash($this->pass, '0', 0, 1);
10506 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10507 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10508 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10509 }
10510 }
10511 if (array_key_exists('ref', $fieldvalues)) {
10512 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10513 }
10514
10515 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
10516
10517 $keys = array();
10518 $values = array(); // Array to store string forged for SQL syntax
10519 foreach ($fieldvalues as $k => $v) {
10520 $keys[$k] = $k;
10521 $value = $this->fields[$k];
10522 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10523 $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
10524 }
10525
10526 // Clean and check mandatory
10527 foreach ($keys as $key) {
10528 if (!isset($this->fields[$key])) {
10529 continue;
10530 }
10531 $key_fields = $this->fields[$key];
10532
10533 // If field is an implicit foreign key field (so type = 'integer:...')
10534 if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
10535 $values[$key] = '';
10536 }
10537 if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
10538 $values[$key] = '';
10539 }
10540
10541 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
10542 $error++;
10543 $langs->load("errors");
10544 dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
10545 $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
10546 }
10547
10548 // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10549 if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && !is_null($key_fields['default'])) {
10550 $values[$key] = $this->quote($key_fields['default'], $key_fields);
10551 }
10552
10553 // If field is an implicit foreign key field (so type = 'integer:...')
10554 if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
10555 if (isset($key_fields['default'])) {
10556 $values[$key] = ((int) $key_fields['default']);
10557 } else {
10558 $values[$key] = 'null';
10559 }
10560 }
10561 if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
10562 $values[$key] = 'null';
10563 }
10564 }
10565
10566 if ($error) {
10567 return -1;
10568 }
10569
10570 $this->db->begin();
10571
10572 if (!$error) {
10573 $sql = "INSERT INTO ".$this->db->prefix().$this->table_element;
10574 $sql .= " (".implode(", ", $keys).')';
10575 $sql .= " VALUES (".implode(", ", $values).")"; // $values can contains 'abc' or 123
10576
10577 $res = $this->db->query($sql);
10578 if (!$res) {
10579 $error++;
10580 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
10581 $this->errors[] = "ErrorRefAlreadyExists";
10582 } else {
10583 $this->errors[] = $this->db->lasterror();
10584 }
10585 }
10586 }
10587
10588 if (!$error) {
10589 $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
10590 }
10591
10592 // If we have a field ref with a default value of (PROV)
10593 if (!$error) {
10594 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
10595 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)') {
10596 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ref = '(PROV".((int) $this->id).")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
10597 $resqlupdate = $this->db->query($sql);
10598
10599 if ($resqlupdate === false) {
10600 $error++;
10601 $this->errors[] = $this->db->lasterror();
10602 } else {
10603 $this->ref = '(PROV'.$this->id.')';
10604 }
10605 }
10606 }
10607
10608 // Create extrafields
10609 if (!$error) {
10610 $result = $this->insertExtraFields();
10611 if ($result < 0) {
10612 $error++;
10613 }
10614 }
10615
10616 // Create lines
10617 if (!empty($this->table_element_line) && !empty($this->fk_element) && !empty($this->lines)) {
10618 foreach ($this->lines as $line) {
10619 $keyforparent = $this->fk_element;
10620 $line->$keyforparent = $this->id;
10621
10622 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
10623 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
10624 if (!is_object($line)) {
10625 $line = (object) $line;
10626 }
10627
10628 $result = 0;
10629 if (method_exists($line, 'insert')) {
10630 $result = $line->insert($user, 1);
10631 } elseif (method_exists($line, 'create')) {
10632 $result = $line->create($user, 1);
10633 }
10634 if ($result < 0) {
10635 $this->error = $line->error;
10636 $this->db->rollback();
10637 return -1;
10638 }
10639 }
10640 }
10641
10642 // Triggers
10643 if (!$error && !$notrigger) {
10644 // Call triggers
10645 $result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
10646 if ($result < 0) {
10647 $error++;
10648 }
10649 // End call triggers
10650 }
10651
10652 // Commit or rollback
10653 if ($error) {
10654 $this->db->rollback();
10655 return -1;
10656 } else {
10657 $this->db->commit();
10658 return $this->id;
10659 }
10660 }
10661
10662
10672 public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
10673 {
10674 if (empty($id) && empty($ref) && empty($morewhere)) {
10675 return -1;
10676 }
10677
10678 $fieldlist = $this->getFieldList('t');
10679 if (empty($fieldlist)) {
10680 return 0;
10681 }
10682
10683 $sql = "SELECT ".$fieldlist;
10684 $sql .= " FROM ".$this->db->prefix().$this->table_element.' as t';
10685
10686 if (!empty($id)) {
10687 $sql .= ' WHERE t.rowid = '.((int) $id);
10688 } elseif (!empty($ref)) {
10689 $sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
10690 } else {
10691 $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
10692 }
10693 if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
10694 $sql .= ' AND t.entity IN ('.getEntity($this->element).')';
10695 }
10696 if ($morewhere) {
10697 $sql .= $morewhere;
10698 }
10699 $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
10700
10701 $res = $this->db->query($sql);
10702 if ($res) {
10703 $obj = $this->db->fetch_object($res);
10704 if ($obj) {
10705 $this->setVarsFromFetchObj($obj);
10706
10707 // Retrieve all extrafield
10708 // fetch optionals attributes and labels
10709 if (empty($noextrafields)) {
10710 $result = $this->fetch_optionals();
10711 if ($result < 0) {
10712 $this->error = $this->db->lasterror();
10713 $this->errors[] = $this->error;
10714 return -4;
10715 }
10716 }
10717
10718 return $this->id;
10719 } else {
10720 return 0;
10721 }
10722 } else {
10723 $this->error = $this->db->lasterror();
10724 $this->errors[] = $this->error;
10725 return -1;
10726 }
10727 }
10728
10736 public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
10737 {
10738 $objectlineclassname = get_class($this).'Line';
10739 if (!class_exists($objectlineclassname)) {
10740 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
10741 return -1;
10742 }
10743
10744 $objectline = new $objectlineclassname($this->db);
10745 '@phan-var-force CommonObjectLine $objectline';
10746
10747 $sql = "SELECT ".$objectline->getFieldList('l');
10748 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
10749 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
10750 if ($morewhere) {
10751 $sql .= $morewhere;
10752 }
10753 if (isset($objectline->fields['position'])) {
10754 $sql .= $this->db->order('position', 'ASC');
10755 }
10756
10757 $resql = $this->db->query($sql);
10758 if ($resql) {
10759 $num_rows = $this->db->num_rows($resql);
10760 $i = 0;
10761 $this->lines = array();
10762 while ($i < $num_rows) {
10763 $obj = $this->db->fetch_object($resql);
10764 if ($obj) {
10765 $newline = new $objectlineclassname($this->db);
10766 '@phan-var-force CommonObjectLine $newline';
10767 $newline->setVarsFromFetchObj($obj);
10768
10769 // Load also extrafields for the line
10770 if (empty($noextrafields)) {
10771 $newline->fetch_optionals();
10772 }
10773
10774 $this->lines[] = $newline;
10775 }
10776 $i++;
10777 }
10778
10779 return 1;
10780 } else {
10781 $this->error = $this->db->lasterror();
10782 $this->errors[] = $this->error;
10783 return -1;
10784 }
10785 }
10786
10794 public function updateCommon(User $user, $notrigger = 0)
10795 {
10796 dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
10797
10798 $error = 0;
10799
10800 $now = dol_now();
10801
10802 // $this->oldcopy should have been set by the caller of update
10803 //if (empty($this->oldcopy)) {
10804 // dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
10805 // $this->oldcopy = dol_clone($this, 2);
10806 //}
10807
10808 $fieldvalues = $this->setSaveQuery();
10809
10810 // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10811
10812 if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) {
10813 $fieldvalues['date_modification'] = $this->db->idate($now);
10814 }
10815 if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) {
10816 $fieldvalues['fk_user_modif'] = $user->id;
10817 }
10818 if (array_key_exists('user_modification_id', $fieldvalues) && !($fieldvalues['user_modification_id'] > 0)) {
10819 $fieldvalues['user_modification_id'] = $user->id;
10820 }
10821 // @phan-suppress-next-line PhanUndeclaredProperty
10822 if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass') && !empty($this->pass)) {
10823 // @phan-suppress-next-line PhanUndeclaredProperty
10824 $tmparray = dol_hash($this->pass, '0', 0, 1);
10825 $fieldvalues['pass_crypted'] = $tmparray['pass_encrypted'];
10826 if (array_key_exists('pass_encoding', $fieldvalues) && property_exists($this, 'pass_encoding')) {
10827 $fieldvalues['pass_encoding'] = $tmparray['pass_encoding'];
10828 }
10829 }
10830 if (array_key_exists('ref', $fieldvalues)) {
10831 $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10832 }
10833
10834 unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
10835
10836 // Add quotes and escape on fields with type string
10837 $keys = array();
10838 $values = array();
10839 $tmp = array();
10840 foreach ($fieldvalues as $k => $v) {
10841 $keys[$k] = $k;
10842 $value = $this->fields[$k];
10843 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10844 $values[$k] = $this->quote($v, $value);
10845 if (($value["type"] == "text") && !empty($value['arrayofkeyval']) && is_array($value['arrayofkeyval'])) {
10846 // Clean values for text with selectbox
10847 $v = preg_replace('/\s/', ',', $v);
10848 $v = preg_replace('/,+/', ',', $v);
10849 }
10850 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10851 $tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
10852 }
10853
10854 // Clean and check mandatory fields
10855 foreach ($keys as $key) {
10856 if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
10857 $values[$key] = ''; // This is an implicit foreign key field
10858 }
10859 if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
10860 $values[$key] = ''; // This is an explicit foreign key field
10861 }
10862
10863 //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
10864 /*
10865 if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
10866 {
10867 $error++;
10868 $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
10869 }*/
10870 }
10871
10872 $sql = 'UPDATE '.$this->db->prefix().$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
10873
10874 $this->db->begin();
10875
10876 if (!$error) {
10877 $res = $this->db->query($sql);
10878 if (!$res) {
10879 $error++;
10880 $this->errors[] = $this->db->lasterror();
10881 }
10882 }
10883
10884 // Update extrafield
10885 if (!$error) {
10886 $result = $this->insertExtraFields(); // This delete and reinsert extrafields
10887 if ($result < 0) {
10888 $error++;
10889 }
10890 }
10891
10892 // Triggers
10893 if (!$error && !$notrigger) {
10894 // Call triggers
10895 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
10896 if ($result < 0) {
10897 $error++;
10898 } //Do also here what you must do to rollback action if trigger fail
10899 // End call triggers
10900 }
10901
10902 // Commit or rollback
10903 if ($error) {
10904 $this->db->rollback();
10905 return -1;
10906 } else {
10907 $this->db->commit();
10908 return $this->id;
10909 }
10910 }
10911
10920 public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
10921 {
10922 dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
10923
10924 $error = 0;
10925
10926 $this->db->begin();
10927
10928 if ($forcechilddeletion) { // Force also delete of childtables that should lock deletion in standard case when option force is off
10929 foreach ($this->childtables as $table) {
10930 $sql = "DELETE FROM ".$this->db->prefix().$table." WHERE ".$this->fk_element." = ".((int) $this->id);
10931 $resql = $this->db->query($sql);
10932 if (!$resql) {
10933 $this->error = $this->db->lasterror();
10934 $this->errors[] = $this->error;
10935 $this->db->rollback();
10936 return -1;
10937 }
10938 }
10939 } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
10940 $objectisused = $this->isObjectUsed($this->id);
10941 if (!empty($objectisused)) {
10942 dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
10943 $this->error = 'ErrorRecordHasChildren';
10944 $this->errors[] = $this->error;
10945 $this->db->rollback();
10946 return 0;
10947 }
10948 }
10949
10950 // Delete cascade first
10951 if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
10952 foreach ($this->childtablesoncascade as $tabletodelete) {
10953 $deleteFromObject = explode(':', $tabletodelete, 4);
10954 if (count($deleteFromObject) >= 2) {
10955 $className = str_replace('@', '', $deleteFromObject[0]);
10956 $filePath = $deleteFromObject[1];
10957 $columnName = $deleteFromObject[2];
10958 $filter = '';
10959 if (!empty($deleteFromObject[3])) {
10960 $filter = $deleteFromObject[3];
10961 }
10962
10963 if (dol_include_once($filePath)) {
10964 $childObject = new $className($this->db);
10965 if (method_exists($childObject, 'deleteByParentField')) {
10966 '@phan-var-force CommonObject $childObject';
10967 $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
10968 if ($result < 0) {
10969 $error++;
10970 $this->errors[] = $childObject->error;
10971 break;
10972 }
10973 } else {
10974 $error++;
10975 $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
10976 break;
10977 }
10978 } else {
10979 $error++;
10980 $this->errors[] = 'Cannot include child class file '.$filePath;
10981 break;
10982 }
10983 } else {
10984 // Delete record in child table
10985 $sql = "DELETE FROM ".$this->db->prefix().$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
10986
10987 $resql = $this->db->query($sql);
10988 if (!$resql) {
10989 $error++;
10990 $this->error = $this->db->lasterror();
10991 $this->errors[] = $this->error;
10992 break;
10993 }
10994 }
10995 }
10996 }
10997
10998 if (!$error) {
10999 if (!$notrigger) {
11000 // Call triggers
11001 $result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
11002 if ($result < 0) {
11003 $error++;
11004 } // Do also here what you must do to rollback action if trigger fail
11005 // End call triggers
11006 }
11007 }
11008
11009 // Delete llx_ecm_files
11010 if (!$error) {
11011 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
11012 if (!$res) {
11013 $error++;
11014 }
11015 }
11016
11017 // Delete linked object
11018 $res = $this->deleteObjectLinked();
11019 if ($res < 0) {
11020 $error++;
11021 }
11022
11023 if (!$error && !empty($this->isextrafieldmanaged)) {
11024 $result = $this->deleteExtraFields();
11025 if ($result < 0) {
11026 $error++;
11027 }
11028 }
11029
11030 if (!$error) {
11031 $sql = 'DELETE FROM '.$this->db->prefix().$this->table_element.' WHERE rowid='.((int) $this->id);
11032
11033 $resql = $this->db->query($sql);
11034 if (!$resql) {
11035 $error++;
11036 $this->errors[] = $this->db->lasterror();
11037 }
11038 }
11039
11040 // Commit or rollback
11041 if ($error) {
11042 $this->db->rollback();
11043 return -1;
11044 } else {
11045 $this->db->commit();
11046 return 1;
11047 }
11048 }
11049
11061 public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
11062 {
11063 global $user;
11064
11065 $error = 0;
11066 $deleted = 0;
11067
11068 //dol_syslog("deleteByParentField for ".$parentId.' '.$parentField);
11069
11070 if (!empty($parentId) && !empty($parentField)) {
11071 if (empty($this->table_element)) {
11072 $this->error = 'Property table_element for object is not defined';
11073 $this->errors[] = $this->error;
11074 $error++;
11075 return -1;
11076 }
11077 if (!method_exists($this, 'fetch')) {
11078 $this->error = 'Method fetch for object is not defined';
11079 $this->errors[] = $this->error;
11080 $error++;
11081 return -1;
11082 }
11083 if (!method_exists($this, 'delete')) {
11084 $this->error = 'Method delete for object is not defined';
11085 $this->errors[] = $this->error;
11086 $error++;
11087 return -1;
11088 }
11089
11090 $this->db->begin();
11091
11092 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
11093 $sql .= " WHERE ".$this->db->sanitize($parentField)." = ".(int) $parentId;
11094
11095 // Manage filter
11096 $errormessage = '';
11097 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
11098 if ($errormessage) {
11099 $this->errors[] = $errormessage;
11100 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
11101 return -1;
11102 }
11103
11104 $resql = $this->db->query($sql);
11105 if (!$resql) {
11106 $this->errors[] = $this->db->lasterror();
11107 $error++;
11108 } else {
11109 while ($obj = $this->db->fetch_object($resql)) {
11110 $result = $this->fetch($obj->rowid); // @phpstan-ignore-line
11111 if ($result < 0) {
11112 $error++;
11113 $this->errors[] = $this->error;
11114 } else {
11115 $result = $this->delete($user); // @phpstan-ignore-line
11116 if ($result < 0) {
11117 $error++;
11118 $this->errors[] = $this->error;
11119 } else {
11120 $deleted++;
11121 }
11122 }
11123 }
11124 }
11125
11126 if (empty($error)) {
11127 $this->db->commit();
11128 return $deleted;
11129 } else {
11130 $this->error = implode(', ', $this->errors);
11131 $this->db->rollback();
11132 return $error * -1;
11133 }
11134 }
11135
11136 return $deleted;
11137 }
11138
11147 public function deleteLineCommon(User $user, $idline, $notrigger = 0)
11148 {
11149 $error = 0;
11150
11151 $tmpforobjectclass = get_class($this);
11152 $tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
11153
11154 $this->db->begin();
11155
11156 // Call trigger
11157 $result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
11158 if ($result < 0) {
11159 $error++;
11160 }
11161 // End call triggers
11162
11163 if (empty($error)) {
11164 $sql = "DELETE FROM ".$this->db->prefix().$this->table_element_line;
11165 $sql .= " WHERE rowid = ".((int) $idline);
11166
11167 $resql = $this->db->query($sql);
11168 if (!$resql) {
11169 $this->error = "Error ".$this->db->lasterror();
11170 $error++;
11171 }
11172 }
11173
11174 if (empty($error)) {
11175 // Remove extrafields
11176 $tmpobjectline = new $tmpforobjectlineclass($this->db);
11177 '@phan-var-force CommonObjectLine $tmpobjectline';
11178 if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
11179 $tmpobjectline->id = $idline;
11180 $result = $tmpobjectline->deleteExtraFields();
11181 if ($result < 0) {
11182 $error++;
11183 $this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
11184 }
11185 }
11186 }
11187
11188 if (empty($error)) {
11189 $this->db->commit();
11190 return 1;
11191 } else {
11192 dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
11193 $this->db->rollback();
11194 return -1;
11195 }
11196 }
11197
11198
11208 public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
11209 {
11210 $error = 0;
11211
11212 $this->db->begin();
11213
11214 $statusfield = 'status';
11215 if (in_array($this->element, array('don', 'donation', 'shipping', 'project_task'))) {
11216 $statusfield = 'fk_statut';
11217 }
11218
11219 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
11220 $sql .= " SET ".$statusfield." = ".((int) $status);
11221 $sql .= " WHERE rowid = ".((int) $this->id);
11222
11223 if ($this->db->query($sql)) {
11224 if (!$error) {
11225 $this->oldcopy = clone $this;
11226 }
11227
11228 if (!$error && !$notrigger) {
11229 // Call trigger
11230 $result = $this->call_trigger($triggercode, $user);
11231 if ($result < 0) {
11232 $error++;
11233 }
11234 }
11235
11236 if (!$error) {
11237 $this->status = $status;
11238 if (property_exists($this, 'statut')) { // For backward compatibility
11239 $this->statut = $status;
11240 }
11241 $this->db->commit();
11242 return 1;
11243 } else {
11244 $this->db->rollback();
11245 return -1;
11246 }
11247 } else {
11248 $this->error = $this->db->error();
11249 $this->db->rollback();
11250 return -1;
11251 }
11252 }
11253
11260 public function initAsSpecimenCommon()
11261 {
11262 global $user;
11263
11264 $this->id = 0;
11265 $this->specimen = 1;
11266 $fields = array(
11267 'label' => 'This is label',
11268 'ref' => 'ABCD1234',
11269 'description' => 'This is a description',
11270 'qty' => 123.12,
11271 'note_public' => 'Public note',
11272 'note_private' => 'Private note',
11273 'date_creation' => (dol_now() - 3600 * 48),
11274 'date_modification' => (dol_now() - 3600 * 24),
11275 'fk_user_creat' => $user->id,
11276 'fk_user_modif' => $user->id,
11277 'date' => dol_now(),
11278 );
11279 foreach ($fields as $key => $value) {
11280 if (array_key_exists($key, $this->fields)) {
11281 $this->{$key} = $value; // @phpstan-ignore-line
11282 }
11283 }
11284
11285 // Force values to default values when known
11286 if (property_exists($this, 'fields')) {
11287 foreach ($this->fields as $key => $value) {
11288 // If fields are already set, do nothing
11289 if (array_key_exists($key, $fields)) {
11290 continue;
11291 }
11292
11293 if (!empty($value['default'])) {
11294 $this->$key = $value['default'];
11295 }
11296 }
11297 }
11298
11299 return 1;
11300 }
11301
11302
11303 /* Part for comments */
11304
11310 public function fetchComments()
11311 {
11312 require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
11313
11314 $comment = new Comment($this->db);
11315 $result = $comment->fetchAllFor($this->element, $this->id);
11316 if ($result < 0) {
11317 $this->errors = array_merge($this->errors, $comment->errors);
11318 return -1;
11319 } else {
11320 $this->comments = $comment->comments;
11321 }
11322 return count($this->comments);
11323 }
11324
11330 public function getNbComments()
11331 {
11332 return count($this->comments);
11333 }
11334
11341 public function trimParameters($parameters)
11342 {
11343 if (!is_array($parameters)) {
11344 return;
11345 }
11346 foreach ($parameters as $parameter) {
11347 if (isset($this->$parameter)) {
11348 $this->$parameter = trim($this->$parameter);
11349 }
11350 }
11351 }
11352
11353 /* Part for categories/tags */
11354
11365 public function getCategoriesCommon($type_categ)
11366 {
11367 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11368
11369 // Get current categories
11370 $c = new Categorie($this->db);
11371 $existing = $c->containing($this->id, $type_categ, 'id');
11372
11373 return $existing;
11374 }
11375
11388 public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
11389 {
11390 // Handle single category
11391 if (!is_array($categories)) {
11392 $categories = array($categories);
11393 }
11394
11395 dol_syslog(get_class($this)."::setCategoriesCommon Object Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
11396
11397 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11398
11399 if (empty($type_categ)) {
11400 dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
11401 return -1;
11402 }
11403
11404 // Get current categories
11405 $c = new Categorie($this->db);
11406 $existing = $c->containing($this->id, $type_categ, 'id');
11407 if ($remove_existing) {
11408 // Diff
11409 if (is_array($existing)) {
11410 $to_del = array_diff($existing, $categories);
11411 $to_add = array_diff($categories, $existing);
11412 } else {
11413 $to_del = array(); // Nothing to delete
11414 $to_add = $categories;
11415 }
11416 } else {
11417 $to_del = array(); // Nothing to delete
11418 $to_add = array_diff($categories, $existing);
11419 }
11420
11421 $error = 0;
11422 $ok = 0;
11423
11424 // Process
11425 foreach ($to_del as $del) {
11426 if ($c->fetch($del) > 0) {
11427 $result = $c->del_type($this, $type_categ);
11428 if ($result < 0) {
11429 $error++;
11430 $this->error = $c->error;
11431 $this->errors = $c->errors;
11432 break;
11433 } else {
11434 $ok += $result;
11435 }
11436 }
11437 }
11438 foreach ($to_add as $add) {
11439 if ($c->fetch($add) > 0) {
11440 $result = $c->add_type($this, $type_categ);
11441 if ($result < 0) {
11442 $error++;
11443 $this->error = $c->error;
11444 $this->errors = $c->errors;
11445 break;
11446 } else {
11447 $ok += $result;
11448 }
11449 }
11450 }
11451
11452 return $error ? (-1 * $error) : $ok;
11453 }
11454
11463 public function cloneCategories($fromId, $toId, $type = '')
11464 {
11465 $this->db->begin();
11466
11467 if (empty($type)) {
11468 $type = $this->table_element;
11469 }
11470
11471 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
11472 $categorystatic = new Categorie($this->db);
11473
11474 $sql = "INSERT INTO ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type])." (fk_categorie, fk_product)";
11475 $sql .= " SELECT fk_categorie, $toId FROM ".$this->db->prefix()."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
11476 $sql .= " WHERE fk_product = ".((int) $fromId);
11477
11478 if (!$this->db->query($sql)) {
11479 $this->error = $this->db->lasterror();
11480 $this->db->rollback();
11481 return -1;
11482 }
11483
11484 $this->db->commit();
11485 return 1;
11486 }
11487
11494 public function deleteEcmFiles($mode = 0)
11495 {
11496 global $conf;
11497
11498 $this->db->begin();
11499
11500 // Delete in database with mode 0
11501 if ($mode == 0) {
11502 switch ($this->element) {
11503 case 'propal':
11504 $element = 'propale';
11505 break;
11506 case 'product':
11507 $element = 'produit';
11508 break;
11509 case 'order_supplier':
11510 $element = 'fournisseur/commande';
11511 break;
11512 case 'invoice_supplier':
11513 // Special cases that need to use get_exdir to get real dir of object
11514 // In future, all object should use this to define path of documents.
11515 $element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
11516 break;
11517 case 'shipping':
11518 $element = 'expedition/sending';
11519 break;
11520 case 'task':
11521 case 'project_task':
11522 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
11523
11524 $project_result = $this->fetchProject();
11525 if ($project_result >= 0) {
11526 $element = 'projet/'.dol_sanitizeFileName($this->project->ref).'/';
11527 }
11528 // no break
11529 case 'contrat':
11530 $element = 'contract';
11531 break;
11532 default:
11533 $element = $this->element;
11534 }
11535 '@phan-var-force string $element';
11536
11537 // Delete ecm_files_extrafields with mode 0 (using name)
11538 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files_extrafields WHERE fk_object IN (";
11539 $sql .= " SELECT rowid FROM ".$this->db->prefix()."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11540 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11541 $sql .= ")";
11542
11543 if (!$this->db->query($sql)) {
11544 $this->error = $this->db->lasterror();
11545 $this->db->rollback();
11546 return false;
11547 }
11548
11549 // Delete ecm_files with mode 0 (using name)
11550 $sql = "DELETE FROM ".$this->db->prefix()."ecm_files";
11551 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
11552 $sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
11553
11554 if (!$this->db->query($sql)) {
11555 $this->error = $this->db->lasterror();
11556 $this->db->rollback();
11557 return false;
11558 }
11559 }
11560
11561 // Delete in database with mode 1
11562 if ($mode == 1) {
11563 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files_extrafields";
11564 $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).")";
11565 $resql = $this->db->query($sql);
11566 if (!$resql) {
11567 $this->error = $this->db->lasterror();
11568 $this->db->rollback();
11569 return false;
11570 }
11571
11572 $sql = 'DELETE FROM '.$this->db->prefix()."ecm_files";
11573 $sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
11574 $resql = $this->db->query($sql);
11575 if (!$resql) {
11576 $this->error = $this->db->lasterror();
11577 $this->db->rollback();
11578 return false;
11579 }
11580 }
11581
11582 $this->db->commit();
11583 return true;
11584 }
11585}
$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.