dolibarr 20.0.4
commonobject.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2006-2015 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
5 * Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
6 * Copyright (C) 2011-2022 Philippe Grand <philippe.grand@atoo-net.com>
7 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
8 * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
10 * Copyright (C) 2015-2022 Alexandre Spangaro <aspangaro@open-dsi.fr>
11 * Copyright (C) 2016 Bahfir abbes <bafbes@gmail.com>
12 * Copyright (C) 2017 ATM Consulting <support@atm-consulting.fr>
13 * Copyright (C) 2017-2019 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2017 Rui Strecht <rui.strecht@aliartalentos.com>
15 * Copyright (C) 2018-2024 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 MDW <mdeweerd@users.noreply.github.com>
21 *
22 * This program is free software; you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation; either version 3 of the License, or
25 * (at your option) any later version.
26 *
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program. If not, see <https://www.gnu.org/licenses/>.
34 */
35
42require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
43
49abstract class CommonObject
50{
51 use DolDeprecationHandler;
52
53 const TRIGGER_PREFIX = ''; // to be overridden in child class implementations, i.e. 'BILL', 'TASK', 'PROPAL', etc.
54
58 public $module;
59
63 public $db;
64
68 public $id;
69
73 public $entity;
74
79 public $error;
80
84 public $errorhidden;
85
89 public $errors = array();
90
94 private $validateFieldsErrors = array();
95
99 public $element;
100
105 public $fk_element;
106
112 public $element_for_permission;
113
117 public $table_element;
118
122 public $table_element_line = '';
123
128 public $ismultientitymanaged;
129
133 public $import_key;
134
138 public $array_options = array();
139
140
144 public $fields = array();
145
150 public $array_languages = null; // Value is array() when load already tried
151
155 public $contacts_ids;
156
160 public $linked_objects;
161
165 public $linkedObjectsIds;
166
170 public $linkedObjects;
171
175 private $linkedObjectsFullLoaded = array();
176
180 public $oldcopy;
181
185 public $oldref;
186
190 protected $table_ref_field = '';
191
195 public $restrictiononfksoc = 0;
196
197
198 // The following vars are used by some objects only.
199 // We keep these properties in CommonObject in order to provide common methods using them.
200
204 public $context = array();
205
209 public $actionmsg;
213 public $actionmsg2;
214
218 public $canvas;
219
224 public $project;
225
230 public $fk_project;
231
237 private $projet;
238
244 public $fk_projet;
245
250 public $contact;
251
256 public $contact_id;
257
262 public $thirdparty;
263
268 public $user;
269
274 public $origin_type;
275
280 public $origin_id;
281
286
292 public $origin;
293
302 private $expedition;
303
309 private $livraison;
310
316 private $commandeFournisseur;
317
318
322 public $ref;
323
327 public $ref_ext;
328
332 public $ref_previous;
333
337 public $ref_next;
338
342 public $newref;
343
350 public $statut;
351
359 public $status;
360
361
366 public $country;
367
372 public $country_id;
373
378 public $country_code;
379
384 public $state;
385
390 public $state_id;
391
396 public $fk_departement;
397
402 public $state_code;
403
408 public $region_id;
409
414 public $region_code;
415
420 public $region;
421
422
427 public $barcode_type;
428
433 public $barcode_type_code;
434
439 public $barcode_type_label;
440
445 public $barcode_type_coder;
446
451 public $mode_reglement_id;
452
457 public $cond_reglement_id;
458
462 public $demand_reason_id;
463
468 public $transport_mode_id;
469
477 private $cond_reglement; // Private to call DolDeprecationHandler
481 protected $depr_cond_reglement; // Internal value for deprecation
482
488 public $fk_delivery_address;
489
494 public $shipping_method_id;
495
500 public $shipping_method;
501
502 // Multicurrency
506 public $fk_multicurrency;
507
512 public $multicurrency_code;
513
518 public $multicurrency_tx;
519
523 public $multicurrency_total_ht;
524
528 public $multicurrency_total_tva;
529
533 public $multicurrency_total_ttc;
534
538 public $multicurrency_total_localtax1; // not in database
539
543 public $multicurrency_total_localtax2; // not in database
544
549 public $model_pdf;
550
555 public $last_main_doc;
556
562 public $fk_bank;
563
568 public $fk_account;
569
574 public $note_public;
575
580 public $note_private;
581
587 public $note;
588
593 public $total_ht;
594
599 public $total_tva;
600
605 public $total_localtax1;
606
611 public $total_localtax2;
612
617 public $total_ttc;
618
619
623 public $lines;
624
628 public $actiontypecode;
629
634 public $comments = array();
635
639 public $name;
640
644 public $lastname;
645
649 public $firstname;
650
654 public $civility_id;
655
656 // Dates
660 public $date_creation;
661
665 public $date_validation;
666
670 public $date_modification;
671
677 public $tms;
678
684 private $date_update;
685
689 public $date_cloture;
690
695 public $user_author;
696
701 public $user_creation;
702
706 public $user_creation_id;
707
712 public $user_valid;
713
718 public $user_validation;
719
723 public $user_validation_id;
724
728 public $user_closing_id;
729
734 public $user_modification;
735
739 public $user_modification_id;
740
745 public $fk_user_creat;
746
751 public $fk_user_modif;
752
753
757 public $next_prev_filter;
758
762 public $specimen = 0;
763
767 public $sendtoid;
768
774 private $alreadypaid;
775
779 public $totalpaid;
780
784 public $labelStatus = array();
785
789 public $labelStatusShort = array();
790
794 public $tpl;
795
796
800 public $showphoto_on_popup;
801
805 public $nb = array();
806
810 public $nbphoto;
811
815 public $output;
816
820 public $extraparams = array();
821
825 protected $childtables = array();
826
832 protected $childtablesoncascade = array();
833
837 public $product;
838
842 public $cond_reglement_supplier_id;
843
849 public $deposit_percent;
850
851
855 public $retained_warranty_fk_cond_reglement;
856
860 public $warehouse_id;
861
865 public $isextrafieldmanaged = 0;
866
867
868 // No constructor as it is an abstract class
869
875 protected function deprecatedProperties()
876 {
877 return array(
878 'alreadypaid' => 'totalpaid',
879 'cond_reglement' => 'depr_cond_reglement',
880 //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
881 'commandeFournisseur' => 'origin_object',
882 'expedition' => 'origin_object',
883 'fk_project' => 'fk_project',
884 'livraison' => 'origin_object',
885 'projet' => 'project',
886 'statut' => 'status',
887 );
888 }
889
890
901 public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
902 {
903 global $db, $conf;
904
905 $sql = "SELECT rowid, ref, ref_ext";
906 $sql .= " FROM ".$db->prefix().$element;
907 $sql .= " WHERE entity IN (".getEntity($element).")";
908
909 if ($id > 0) {
910 $sql .= " AND rowid = ".((int) $id);
911 } elseif ($ref) {
912 $sql .= " AND ref = '".$db->escape($ref)."'";
913 } elseif ($ref_ext) {
914 $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
915 } else {
916 $error = 'ErrorWrongParameters';
917 dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
918 return -1;
919 }
920 if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
921 $sql .= " AND entity = ".((int) $conf->entity);
922 }
923
924 dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
925 $resql = $db->query($sql);
926 if ($resql) {
927 $num = $db->num_rows($resql);
928 if ($num > 0) {
929 return 1;
930 } else {
931 return 0;
932 }
933 }
934 return -1;
935 }
936
942 public function isEmpty()
943 {
944 return (empty($this->id));
945 }
946
954 {
955 if (!empty($object->error)) {
956 $this->error = $object->error;
957 }
958 if (!empty($object->errors)) {
959 $this->errors = array_merge($this->errors, $object->errors);
960 }
961 }
962
970 public function getTooltipContentArray($params)
971 {
972 return [];
973 }
974
982 public function getTooltipContent($params)
983 {
984 global $action, $extrafields, $langs, $hookmanager;
985
986 // If there is too much extrafields, we do not include them into tooltip
987 $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
988
989 $data = $this->getTooltipContentArray($params);
990 $count = 0;
991
992 // Add extrafields
993 if (!empty($extrafields->attributes[$this->table_element]['label'])) {
994 $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
995 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
996 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
997 continue;
998 }
999 if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1000 $data['more_extrafields'] = '<br>...';
1001 break;
1002 }
1003 $enabled = 1;
1004 if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1005 $enabled = (int) dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1006 }
1007 if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1008 $enabled = (int) dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1009 }
1010 $perms = 1;
1011 if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1012 $perms = (int) dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1013 }
1014 if (empty($enabled)) {
1015 continue; // 0 = Never visible field
1016 }
1017 if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1018 continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1019 }
1020 if (empty($perms)) {
1021 continue; // 0 = Not visible
1022 }
1023 if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1024 $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1025 }
1026 $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1027 if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1028 $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1029 } else {
1030 $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1031 $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1032 $count++;
1033 }
1034 }
1035 $data['closedivextra'] = '</div>';
1036 }
1037
1038 $hookmanager->initHooks(array($this->element . 'dao'));
1039 $parameters = array(
1040 'tooltipcontentarray' => &$data,
1041 'params' => $params,
1042 );
1043 // Note that $action and $object may have been modified by some hooks
1044 $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1045
1046 //var_dump($data);
1047 $label = implode($data);
1048
1049 return $label;
1050 }
1051
1052
1058 public function errorsToString()
1059 {
1060 return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1061 }
1062
1063
1070 public function getFormatedCustomerRef($objref)
1071 {
1072 global $hookmanager;
1073
1074 $parameters = array('objref' => $objref);
1075 $action = '';
1076 $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1077 if ($reshook > 0) {
1078 return $hookmanager->resArray['objref'];
1079 }
1080 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1081 }
1082
1089 public function getFormatedSupplierRef($objref)
1090 {
1091 global $hookmanager;
1092
1093 $parameters = array('objref' => $objref);
1094 $action = '';
1095 $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1096 if ($reshook > 0) {
1097 return $hookmanager->resArray['objref'];
1098 }
1099 return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1100 }
1101
1111 public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1112 {
1113 if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1114 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1115 $tmparray = getCountry($this->country_id, 'all');
1116 $this->country_code = $tmparray['code'];
1117 $this->country = $tmparray['label'];
1118 }
1119
1120 if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1121 require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1122 $tmparray = getState($this->state_id, 'all', null, 1);
1123 $this->state_code = $tmparray['code'];
1124 $this->state = $tmparray['label'];
1125 $this->region_code = $tmparray['region_code'];
1126 $this->region = $tmparray['region'];
1127 }
1128
1129 return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1130 }
1131
1132
1141 public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1142 {
1143 global $user, $dolibarr_main_url_root;
1144
1145 if (empty($this->last_main_doc)) {
1146 return ''; // No way to known which document name to use
1147 }
1148
1149 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1150 $ecmfile = new EcmFiles($this->db);
1151 $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1152 if ($result < 0) {
1153 $this->error = $ecmfile->error;
1154 $this->errors = $ecmfile->errors;
1155 return -1;
1156 }
1157
1158 if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1159 // Add entry into index
1160 if ($initsharekey) {
1161 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1162
1163 // 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
1164 /*
1165 $ecmfile->filepath = $rel_dir;
1166 $ecmfile->filename = $filename;
1167 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1168 $ecmfile->fullpath_orig = '';
1169 $ecmfile->gen_or_uploaded = 'generated';
1170 $ecmfile->description = ''; // indexed content
1171 $ecmfile->keywords = ''; // keyword content
1172 $ecmfile->share = getRandomPassword(true);
1173 $result = $ecmfile->create($user);
1174 if ($result < 0)
1175 {
1176 $this->error = $ecmfile->error;
1177 $this->errors = $ecmfile->errors;
1178 }
1179 */
1180 } else {
1181 return '';
1182 }
1183 } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1184 // Add entry into index
1185 if ($initsharekey) {
1186 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1187 $ecmfile->share = getRandomPassword(true);
1188 $ecmfile->update($user);
1189 } else {
1190 return '';
1191 }
1192 }
1193 // Define $urlwithroot
1194 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1195 // This is to use external domain name found into config file
1196 //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1197 //else
1198 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1199 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1200
1201 $forcedownload = 0;
1202
1203 $paramlink = '';
1204 //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1205 //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1206 //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1207 if (!empty($ecmfile->share)) {
1208 $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1209 }
1210 if ($forcedownload) {
1211 $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1212 }
1213
1214 if ($relativelink) {
1215 $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1216 } else {
1217 $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1218 }
1219
1220 // Here $ecmfile->share is defined
1221 return $linktoreturn;
1222 }
1223
1224
1225 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1235 public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1236 {
1237 // phpcs:enable
1238 global $user, $langs;
1239
1240
1241 dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1242
1243 // Check parameters
1244 if ($fk_socpeople <= 0) {
1245 $langs->load("errors");
1246 $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1247 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1248 return -1;
1249 }
1250 if (!$type_contact) {
1251 $langs->load("errors");
1252 $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1253 dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1254 return -2;
1255 }
1256
1257 $id_type_contact = 0;
1258 if (is_numeric($type_contact)) {
1259 $id_type_contact = $type_contact;
1260 } else {
1261 // We look for id type_contact
1262 $sql = "SELECT tc.rowid";
1263 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1264 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1265 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1266 $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1267 //print $sql;
1268 $resql = $this->db->query($sql);
1269 if ($resql) {
1270 $obj = $this->db->fetch_object($resql);
1271 if ($obj) {
1272 $id_type_contact = $obj->rowid;
1273 }
1274 }
1275 }
1276
1277 if ($id_type_contact == 0) {
1278 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");
1279 return 0;
1280 }
1281
1282 $datecreate = dol_now();
1283
1284 // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1285 $TListeContacts = $this->liste_contact(-1, $source);
1286 $already_added = false;
1287 if (is_array($TListeContacts) && !empty($TListeContacts)) {
1288 foreach ($TListeContacts as $array_contact) {
1289 if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1290 $already_added = true;
1291 break;
1292 }
1293 }
1294 }
1295
1296 if (!$already_added) {
1297 $this->db->begin();
1298
1299 // Insert into database
1300 $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1301 $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1302 $sql .= " VALUES (".$this->id.", ".((int) $fk_socpeople)." , ";
1303 $sql .= "'".$this->db->idate($datecreate)."'";
1304 $sql .= ", 4, ".((int) $id_type_contact);
1305 $sql .= ")";
1306
1307 $resql = $this->db->query($sql);
1308 if ($resql) {
1309 if (!$notrigger) {
1310 $result = $this->call_trigger(strtoupper($this->element).'_ADD_CONTACT', $user);
1311 if ($result < 0) {
1312 $this->db->rollback();
1313 return -1;
1314 }
1315 }
1316
1317 $this->db->commit();
1318 return 1;
1319 } else {
1320 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1321 $this->error = $this->db->errno();
1322 $this->db->rollback();
1323 return -2;
1324 } else {
1325 $this->error = $this->db->lasterror();
1326 $this->db->rollback();
1327 return -1;
1328 }
1329 }
1330 } else {
1331 return 0;
1332 }
1333 }
1334
1335 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1343 public function copy_linked_contact($objFrom, $source = 'internal')
1344 {
1345 // phpcs:enable
1346 $contacts = $objFrom->liste_contact(-1, $source);
1347 foreach ($contacts as $contact) {
1348 if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1349 return -1;
1350 }
1351 }
1352 return 1;
1353 }
1354
1355 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1365 public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1366 {
1367 // phpcs:enable
1368 // Insert into database
1369 $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1370 $sql .= " statut = ".$statut;
1371 if ($type_contact_id) {
1372 $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1373 }
1374 if ($fk_socpeople) {
1375 $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1376 }
1377 $sql .= " where rowid = ".((int) $rowid);
1378 $resql = $this->db->query($sql);
1379 if ($resql) {
1380 return 0;
1381 } else {
1382 $this->error = $this->db->lasterror();
1383 return -1;
1384 }
1385 }
1386
1387 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1395 public function delete_contact($rowid, $notrigger = 0)
1396 {
1397 // phpcs:enable
1398 global $user;
1399
1400 $error = 0;
1401
1402 $this->db->begin();
1403
1404 if (!$error && empty($notrigger)) {
1405 // Call trigger
1406 $this->context['contact_id'] = ((int) $rowid);
1407 $result = $this->call_trigger(strtoupper($this->element).'_DELETE_CONTACT', $user);
1408 if ($result < 0) {
1409 $error++;
1410 }
1411 // End call triggers
1412 }
1413
1414 if (!$error) {
1415 dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1416
1417 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1418 $sql .= " WHERE rowid = ".((int) $rowid);
1419
1420 $result = $this->db->query($sql);
1421 if (!$result) {
1422 $error++;
1423 $this->errors[] = $this->db->lasterror();
1424 }
1425 }
1426
1427 if (!$error) {
1428 $this->db->commit();
1429 return 1;
1430 } else {
1431 $this->error = $this->db->lasterror();
1432 $this->db->rollback();
1433 return -1;
1434 }
1435 }
1436
1437 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1445 public function delete_linked_contact($source = '', $code = '')
1446 {
1447 // phpcs:enable
1448 $listId = '';
1449 $temp = array();
1450 $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1451
1452 if (!empty($typeContact)) {
1453 foreach ($typeContact as $key => $value) {
1454 array_push($temp, $key);
1455 }
1456 $listId = implode(",", $temp);
1457 }
1458
1459 // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1460 // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1461 if (empty($listId)) {
1462 return 0;
1463 }
1464
1465 $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1466 $sql .= " WHERE element_id = ".((int) $this->id);
1467 $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1468
1469 dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1470 if ($this->db->query($sql)) {
1471 return 1;
1472 } else {
1473 $this->error = $this->db->lasterror();
1474 return -1;
1475 }
1476 }
1477
1478 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1490 public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1491 {
1492 // phpcs:enable
1493 global $langs;
1494
1495 $tab = array();
1496
1497 $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
1498 if ($source == 'internal') {
1499 $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender";
1500 }
1501 if ($source == 'external' || $source == 'thirdparty') {
1502 $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
1503 }
1504 $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
1505 $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label";
1506 $sql .= " FROM ".$this->db->prefix()."c_type_contact tc,";
1507 $sql .= " ".$this->db->prefix()."element_contact ec";
1508 if ($source == 'internal') { // internal contact (user)
1509 $sql .= " LEFT JOIN ".$this->db->prefix()."user t on ec.fk_socpeople = t.rowid";
1510 }
1511 if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1512 $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople t on ec.fk_socpeople = t.rowid";
1513 }
1514 $sql .= " WHERE ec.element_id = ".((int) $this->id);
1515 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1516 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1517 if ($code) {
1518 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1519 }
1520 if ($source == 'internal') {
1521 $sql .= " AND tc.source = 'internal'";
1522 if ($status >= 0) {
1523 $sql .= " AND t.statut = ".((int) $status);
1524 }
1525 }
1526 if ($source == 'external' || $source == 'thirdparty') {
1527 $sql .= " AND tc.source = 'external'";
1528 if ($status >= 0) {
1529 $sql .= " AND t.statut = ".((int) $status); // t is llx_socpeople
1530 }
1531 }
1532 $sql .= " AND tc.active = 1";
1533 if ($statusoflink >= 0) {
1534 $sql .= " AND ec.statut = ".((int) $statusoflink);
1535 }
1536 $sql .= " ORDER BY t.lastname ASC";
1537
1538 dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1539 $resql = $this->db->query($sql);
1540 if ($resql) {
1541 $num = $this->db->num_rows($resql);
1542 $i = 0;
1543 while ($i < $num) {
1544 $obj = $this->db->fetch_object($resql);
1545
1546 if (!$list) {
1547 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1548 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1549 $tab[$i] = array(
1550 'parentId' => $this->id,
1551 'source' => $obj->source,
1552 'socid' => $obj->socid,
1553 'id' => $obj->id,
1554 'nom' => $obj->lastname, // For backward compatibility
1555 'civility' => $obj->civility,
1556 'lastname' => $obj->lastname,
1557 'firstname' => $obj->firstname,
1558 'email' => $obj->email,
1559 'login' => (empty($obj->login) ? '' : $obj->login),
1560 'photo' => (empty($obj->photo) ? '' : $obj->photo),
1561 'gender' => (empty($obj->gender) ? '' : $obj->gender),
1562 'statuscontact' => $obj->statuscontact,
1563 'rowid' => $obj->rowid,
1564 'code' => $obj->code,
1565 'libelle' => $libelle_type,
1566 'status' => $obj->statuslink,
1567 'fk_c_type_contact' => $obj->fk_c_type_contact
1568 );
1569 } else {
1570 $tab[$i] = $obj->id;
1571 }
1572
1573 $i++;
1574 }
1575
1576 return $tab;
1577 } else {
1578 $this->error = $this->db->lasterror();
1579 dol_print_error($this->db);
1580 return -1;
1581 }
1582 }
1583
1584
1591 public function swapContactStatus($rowid)
1592 {
1593 $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1594 $sql .= " tc.code, tc.libelle as type_label";
1595 $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1596 $sql .= " WHERE ec.rowid =".((int) $rowid);
1597 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1598 $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1599
1600 dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1601 $resql = $this->db->query($sql);
1602 if ($resql) {
1603 $obj = $this->db->fetch_object($resql);
1604 $newstatut = ($obj->statut == 4) ? 5 : 4;
1605 $result = $this->update_contact($rowid, $newstatut);
1606 $this->db->free($resql);
1607 return $result;
1608 } else {
1609 $this->error = $this->db->error();
1610 dol_print_error($this->db);
1611 return -1;
1612 }
1613 }
1614
1615 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1626 public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1627 {
1628 // phpcs:enable
1629 global $langs;
1630
1631 if (empty($order)) {
1632 $order = 'position';
1633 }
1634 if ($order == 'position') {
1635 $order .= ',code';
1636 }
1637
1638 $tab = array();
1639 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1640 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1641 $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1642 if ($activeonly == 1) {
1643 $sql .= " AND tc.active=1"; // only the active types
1644 }
1645 if (!empty($source) && $source != 'all') {
1646 $sql .= " AND tc.source='".$this->db->escape($source)."'";
1647 }
1648 if (!empty($code)) {
1649 $sql .= " AND tc.code='".$this->db->escape($code)."'";
1650 }
1651 $sql .= $this->db->order($order, 'ASC');
1652
1653 //print "sql=".$sql;
1654 $resql = $this->db->query($sql);
1655 if ($resql) {
1656 $num = $this->db->num_rows($resql);
1657 $i = 0;
1658 while ($i < $num) {
1659 $obj = $this->db->fetch_object($resql);
1660
1661 $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1662 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1663 if (empty($option)) {
1664 $tab[$obj->rowid] = $libelle_type;
1665 } else {
1666 $tab[$obj->code] = $libelle_type;
1667 }
1668 $i++;
1669 }
1670 return $tab;
1671 } else {
1672 $this->error = $this->db->lasterror();
1673 //dol_print_error($this->db);
1674 return null;
1675 }
1676 }
1677
1689 public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1690 {
1691 global $langs, $conf;
1692
1693 $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1694
1695 $tab = array();
1696
1697 $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element, tc.module";
1698 $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1699
1700 $sqlWhere = array();
1701 if (!empty($element)) {
1702 $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1703 }
1704 if (!empty($excludeelement)) {
1705 $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1706 }
1707
1708 if ($activeonly == 1) {
1709 $sqlWhere[] = " tc.active=1"; // only the active types
1710 }
1711
1712 if (!empty($source) && $source != 'all') {
1713 $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1714 }
1715
1716 if (!empty($code)) {
1717 $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1718 }
1719
1720 if (count($sqlWhere) > 0) {
1721 $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1722 }
1723
1724 $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1725
1726 dol_syslog(__METHOD__, LOG_DEBUG);
1727 $resql = $this->db->query($sql);
1728 if ($resql) {
1729 $num = $this->db->num_rows($resql);
1730 if ($num > 0) {
1731 $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1732
1733 while ($obj = $this->db->fetch_object($resql)) {
1734 $modulename = $obj->module ?? $obj->element;
1735 if (strpos($obj->element, 'project') !== false) {
1736 $modulename = 'projet';
1737 } elseif ($obj->element == 'contrat') {
1738 $element = 'contract';
1739 } elseif ($obj->element == 'action') {
1740 $modulename = 'agenda';
1741 } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1742 $modulename = 'fournisseur';
1743 }
1744 if (!empty($conf->{$modulename}->enabled)) {
1745 $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1746 $tmpelement = $obj->element;
1747 $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1748 $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1749 $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1750 }
1751 }
1752 }
1753 return $tab;
1754 } else {
1755 $this->error = $this->db->lasterror();
1756 return null;
1757 }
1758 }
1759
1771 public function getIdContact($source, $code, $status = 0)
1772 {
1773 global $conf;
1774
1775 $result = array();
1776 $i = 0;
1777 // Particular case for shipping
1778 if ($this->element == 'shipping' && $this->origin_id != 0) {
1779 $id = $this->origin_id;
1780 $element = 'commande';
1781 } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1782 $id = $this->origin_id;
1783 $element = 'order_supplier';
1784 } else {
1785 $id = $this->id;
1786 $element = $this->element;
1787 }
1788
1789 $sql = "SELECT ec.fk_socpeople";
1790 $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1791 if ($source == 'internal') {
1792 $sql .= " ".$this->db->prefix()."user as c,";
1793 }
1794 if ($source == 'external') {
1795 $sql .= " ".$this->db->prefix()."socpeople as c,";
1796 }
1797 $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1798 $sql .= " WHERE ec.element_id = ".((int) $id);
1799 $sql .= " AND ec.fk_socpeople = c.rowid";
1800 if ($source == 'internal') {
1801 $sql .= " AND c.entity IN (".getEntity('user').")";
1802 }
1803 if ($source == 'external') {
1804 $sql .= " AND c.entity IN (".getEntity('societe').")";
1805 }
1806 $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1807 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1808 $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1809 if ($code) {
1810 $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1811 }
1812 $sql .= " AND tc.active = 1";
1813 if ($status) {
1814 $sql .= " AND ec.statut = ".((int) $status);
1815 }
1816
1817 dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1818 $resql = $this->db->query($sql);
1819 if ($resql) {
1820 while ($obj = $this->db->fetch_object($resql)) {
1821 $result[$i] = $obj->fk_socpeople;
1822 $i++;
1823 }
1824 } else {
1825 $this->error = $this->db->error();
1826 return null;
1827 }
1828
1829 return $result;
1830 }
1831
1832 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1839 public function fetch_contact($contactid = null)
1840 {
1841 // phpcs:enable
1842 if (empty($contactid)) {
1843 $contactid = $this->contact_id;
1844 }
1845
1846 if (empty($contactid)) {
1847 return 0;
1848 }
1849
1850 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1851 $contact = new Contact($this->db);
1852 $result = $contact->fetch($contactid);
1853 $this->contact = $contact;
1854 return $result;
1855 }
1856
1857 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1865 public function fetch_thirdparty($force_thirdparty_id = 0)
1866 {
1867 // phpcs:enable
1868 if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1869 return 0;
1870 }
1871
1872 require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1873
1874 $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1875 if ($force_thirdparty_id) {
1876 $idtofetch = $force_thirdparty_id;
1877 }
1878
1879 if ($idtofetch) {
1880 $thirdparty = new Societe($this->db);
1881 $result = $thirdparty->fetch($idtofetch);
1882 if ($result < 0) {
1883 $this->errors = array_merge($this->errors, $thirdparty->errors);
1884 }
1885 $this->thirdparty = $thirdparty;
1886
1887 // Use first price level if level not defined for third party
1888 if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($this->thirdparty->price_level)) {
1889 $this->thirdparty->price_level = 1;
1890 }
1891
1892 return $result;
1893 } else {
1894 return -1;
1895 }
1896 }
1897
1898
1906 public function fetchOneLike($ref)
1907 {
1908 if (!$this->table_ref_field) {
1909 return 0;
1910 }
1911
1912 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
1913 $sql .= " WHERE ".$this->table_ref_field." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
1914 $sql .= " LIMIT 1";
1915
1916 $query = $this->db->query($sql);
1917
1918 if (!$this->db->num_rows($query)) {
1919 return 0;
1920 }
1921
1922 $result = $this->db->fetch_object($query);
1923
1924 if (method_exists($this, 'fetch')) {
1925 return $this->fetch($result->rowid);
1926 } else {
1927 $this->error = 'Fetch method not implemented on '.get_class($this);
1928 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
1929 array_push($this->errors, $this->error);
1930 return -1;
1931 }
1932 }
1933
1934 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1942 public function fetch_barcode()
1943 {
1944 // phpcs:enable
1945 global $conf;
1946
1947 dol_syslog(get_class($this).'::fetch_barcode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
1948
1949 $idtype = $this->barcode_type;
1950 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
1951 if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
1952 $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
1953 } elseif ($this->element == 'societe') {
1954 $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
1955 } else {
1956 dol_syslog('Call fetch_barcode with barcode_type not defined and cannot be guessed', LOG_WARNING);
1957 }
1958 }
1959
1960 if ($idtype > 0) {
1961 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
1962 $sql = "SELECT rowid, code, libelle as label, coder";
1963 $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
1964 $sql .= " WHERE rowid = ".((int) $idtype);
1965 dol_syslog(get_class($this).'::fetch_barcode', LOG_DEBUG);
1966 $resql = $this->db->query($sql);
1967 if ($resql) {
1968 $obj = $this->db->fetch_object($resql);
1969 $this->barcode_type = $obj->rowid;
1970 $this->barcode_type_code = $obj->code;
1971 $this->barcode_type_label = $obj->label;
1972 $this->barcode_type_coder = $obj->coder;
1973 return 1;
1974 } else {
1975 dol_print_error($this->db);
1976 return -1;
1977 }
1978 }
1979 }
1980 return 0;
1981 }
1982
1983 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1989 public function fetch_project()
1990 {
1991 // phpcs:enable
1992 return $this->fetch_projet();
1993 }
1994
1995 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2001 public function fetch_projet()
2002 {
2003 // phpcs:enable
2004 include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2005
2006 if (empty($this->fk_project) && !empty($this->fk_projet)) {
2007 $this->fk_project = $this->fk_projet; // For backward compatibility
2008 }
2009 if (empty($this->fk_project)) {
2010 return 0;
2011 }
2012
2013 $project = new Project($this->db);
2014 $result = $project->fetch($this->fk_project);
2015
2016 $this->projet = $project; // deprecated
2017 $this->project = $project;
2018 return $result;
2019 }
2020
2021 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2027 public function fetch_product()
2028 {
2029 // phpcs:enable
2030 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2031
2032 // @phan-suppress-next-line PhanUndeclaredProperty
2033 if (empty($this->fk_product)) {
2034 return 0;
2035 }
2036
2037 $product = new Product($this->db);
2038 // @phan-suppress-next-line PhanUndeclaredProperty
2039 $result = $product->fetch($this->fk_product);
2040
2041 $this->product = $product;
2042 return $result;
2043 }
2044
2045 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2052 public function fetch_user($userid)
2053 {
2054 // phpcs:enable
2055 $user = new User($this->db);
2056 $result = $user->fetch($userid);
2057 $this->user = $user;
2058 return $result;
2059 }
2060
2061 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2068 public function fetch_origin()
2069 {
2070 // phpcs:enable
2071 $origin = $this->origin ? $this->origin : $this->origin_type;
2072
2073 // Manage classes with non standard name
2074 if ($origin == 'shipping') {
2075 $origin = 'expedition';
2076 }
2077 if ($origin == 'delivery') {
2078 $origin = 'livraison';
2079 }
2080 if ($origin == 'order_supplier' || $origin == 'supplier_order') {
2081 $origin = 'commandeFournisseur';
2082 }
2083
2084 $classname = ucfirst($origin);
2085 $this->origin_object = new $classname($this->db);
2086 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2087 $this->origin_object->fetch($this->origin_id);
2088 }
2089
2099 public function fetchObjectFrom($table, $field, $key, $element = null)
2100 {
2101 global $conf;
2102
2103 $result = false;
2104
2105 $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2106 $sql .= " WHERE ".$field." = '".$this->db->escape($key)."'";
2107 if (!empty($element)) {
2108 $sql .= " AND entity IN (".getEntity($element).")";
2109 } else {
2110 $sql .= " AND entity = ".((int) $conf->entity);
2111 }
2112
2113 dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2114 $resql = $this->db->query($sql);
2115 if ($resql) {
2116 $obj = $this->db->fetch_object($resql);
2117 // Test for avoid error -1
2118 if ($obj) {
2119 if (method_exists($this, 'fetch')) {
2120 $result = $this->fetch($obj->rowid);
2121 } else {
2122 $this->error = 'fetch() method not implemented on '.get_class($this);
2123 dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2124 array_push($this->errors, $this->error);
2125 $result = -1;
2126 }
2127 }
2128 }
2129
2130 return $result;
2131 }
2132
2141 public function getValueFrom($table, $id, $field)
2142 {
2143 $result = false;
2144 if (!empty($id) && !empty($field) && !empty($table)) {
2145 $sql = "SELECT ".$field." FROM ".$this->db->prefix().$table;
2146 $sql .= " WHERE rowid = ".((int) $id);
2147
2148 dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2149 $resql = $this->db->query($sql);
2150 if ($resql) {
2151 $row = $this->db->fetch_row($resql);
2152 $result = $row[0];
2153 }
2154 }
2155 return $result;
2156 }
2157
2174 public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2175 {
2176 global $user;
2177
2178 if (empty($table)) {
2179 $table = $this->table_element;
2180 }
2181 if (empty($id)) {
2182 $id = $this->id;
2183 }
2184 if (empty($format)) {
2185 $format = 'text';
2186 }
2187 if (empty($id_field)) {
2188 $id_field = 'rowid';
2189 }
2190
2191 // Special case
2192 if ($table == 'product' && $field == 'note_private') {
2193 $field = 'note';
2194 }
2195
2196 if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2197 $fk_user_field = 'fk_user_mod';
2198 }
2199 if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2200 $fk_user_field = '';
2201 }
2202
2203 $oldvalue = null;
2204 if ($trigkey) {
2205 $sql = "SELECT " . $field;
2206 $sql .= " FROM " . MAIN_DB_PREFIX . $table;
2207 $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2208
2209 $resql = $this->db->query($sql);
2210 if ($resql) {
2211 if ($obj = $this->db->fetch_object($resql)) {
2212 if ($format == 'date') {
2213 $oldvalue = $this->db->jdate($obj->$field);
2214 } else {
2215 $oldvalue = $obj->$field;
2216 }
2217 }
2218 } else {
2219 $this->error = $this->db->lasterror();
2220 return -1;
2221 }
2222 }
2223
2224 $error = 0;
2225
2226 dol_syslog(__METHOD__, LOG_DEBUG);
2227
2228 $this->db->begin();
2229
2230 $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2231
2232 if ($format == 'text') {
2233 $sql .= $field." = '".$this->db->escape($value)."'";
2234 } elseif ($format == 'int') {
2235 $sql .= $field." = ".((int) $value);
2236 } elseif ($format == 'date') {
2237 $sql .= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2238 } elseif ($format == 'dategmt') {
2239 $sql .= $field." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2240 }
2241
2242 if ($fk_user_field) {
2243 if (!empty($fuser) && is_object($fuser)) {
2244 $sql .= ", ".$fk_user_field." = ".((int) $fuser->id);
2245 } elseif (empty($fuser) || $fuser != 'none') {
2246 $sql .= ", ".$fk_user_field." = ".((int) $user->id);
2247 }
2248 }
2249
2250 $sql .= " WHERE ".$id_field." = ".((int) $id);
2251
2252 $resql = $this->db->query($sql);
2253 if ($resql) {
2254 if ($trigkey) {
2255 // call trigger with updated object values
2256 if (method_exists($this, 'fetch')) {
2257 $result = $this->fetch($id);
2258 } else {
2259 $result = $this->fetchCommon($id);
2260 }
2261 $this->oldcopy = clone $this;
2262 if (property_exists($this->oldcopy, $field)) {
2263 $this->oldcopy->$field = $oldvalue;
2264 }
2265
2266 if ($result >= 0) {
2267 $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2268 }
2269 if ($result < 0) {
2270 $error++;
2271 }
2272 }
2273
2274 if (!$error) {
2275 if (property_exists($this, $field)) {
2276 $this->$field = $value;
2277 }
2278 $this->db->commit();
2279 return 1;
2280 } else {
2281 $this->db->rollback();
2282 return -2;
2283 }
2284 } else {
2285 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2286 $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2287 } else {
2288 $this->error = $this->db->lasterror();
2289 }
2290 $this->db->rollback();
2291 return -1;
2292 }
2293 }
2294
2295 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2306 public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2307 {
2308 // phpcs:enable
2309 global $conf, $user;
2310
2311 if (!$this->table_element) {
2312 dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2313 return -1;
2314 }
2315 if ($fieldid == 'none') {
2316 return 1;
2317 }
2318
2319 // For backward compatibility
2320 if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2321 $fieldid = 'titre';
2322 }
2323
2324 // Security on socid
2325 $socid = 0;
2326 if ($user->socid > 0) {
2327 $socid = $user->socid;
2328 }
2329
2330 // this->ismultientitymanaged contains
2331 // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2332 $aliastablesociete = 's';
2333 if ($this->element == 'societe') {
2334 $aliastablesociete = 'te'; // te as table_element
2335 }
2336 $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2337 $sql = "SELECT MAX(te.".$fieldid.")";
2338 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2339 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2340 $tmparray = explode('@', $this->ismultientitymanaged);
2341 $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
2342 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2343 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2344 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2345 $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
2346 }
2347 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2348 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2349 }
2350 if ($fieldid == 'rowid') {
2351 $sql .= " WHERE te.".$fieldid." < ".((int) $this->id);
2352 } else {
2353 $sql .= " WHERE te.".$fieldid." < '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2354 }
2355 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2356 $sql .= " AND sc.fk_user = ".((int) $user->id);
2357 }
2358 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2359 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2360 }
2361
2362 $filtermax = $filter;
2363
2364 // Manage filter
2365 $errormessage = '';
2366 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2367 if ($errormessage) {
2368 if (!preg_match('/^\s*AND/i', $filtermax)) {
2369 $sql .= " AND ";
2370 }
2371 $sql .= $filtermax;
2372 } else {
2373 $sql .= $tmpsql;
2374 }
2375
2376 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2377 $tmparray = explode('@', $this->ismultientitymanaged);
2378 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2379 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2380 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2381 }
2382 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2383 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2384 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2385 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2386 } else {
2387 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2388 }
2389 } else {
2390 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2391 }
2392 }
2393 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2394 $tmparray = explode('@', $this->ismultientitymanaged);
2395 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2396 }
2397 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2398 $sql .= ' AND te.fk_soc = '.((int) $socid);
2399 }
2400 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2401 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2402 }
2403 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2404 $sql .= ' AND te.rowid = '.((int) $socid);
2405 }
2406 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2407
2408 $result = $this->db->query($sql);
2409 if (!$result) {
2410 $this->error = $this->db->lasterror();
2411 return -1;
2412 }
2413 $row = $this->db->fetch_row($result);
2414 $this->ref_previous = $row[0];
2415
2416 $sql = "SELECT MIN(te.".$fieldid.")";
2417 $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2418 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2419 $tmparray = explode('@', $this->ismultientitymanaged);
2420 $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
2421 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2422 $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2423 } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2424 $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
2425 }
2426 if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2427 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2428 }
2429 if ($fieldid == 'rowid') {
2430 $sql .= " WHERE te.".$fieldid." > ".((int) $this->id);
2431 } else {
2432 $sql .= " WHERE te.".$fieldid." > '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2433 }
2434 if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2435 $sql .= " AND (sc.fk_user = ".((int) $user->id);
2436 if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2437 $userschilds = $user->getAllChildIds();
2438 $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2439 }
2440 $sql .= ')';
2441 }
2442 if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2443 $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2444 }
2445
2446 $filtermin = $filter;
2447
2448 // Manage filter
2449 $errormessage = '';
2450 $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2451 if ($errormessage) {
2452 if (!preg_match('/^\s*AND/i', $filtermin)) {
2453 $sql .= " AND ";
2454 }
2455 $sql .= $filtermin;
2456
2457 $filtermin = '';
2458 } else {
2459 $sql .= $tmpsql;
2460 }
2461
2462 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2463 $tmparray = explode('@', $this->ismultientitymanaged);
2464 $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2465 } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2466 $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2467 }
2468 if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2469 if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2470 if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2471 $sql .= " AND te.entity IS NOT NULL"; // Show all users
2472 } else {
2473 $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2474 }
2475 } else {
2476 $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2477 }
2478 }
2479 if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2480 $tmparray = explode('@', $this->ismultientitymanaged);
2481 $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2482 }
2483 if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2484 $sql .= ' AND te.fk_soc = '.((int) $socid);
2485 }
2486 if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2487 $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2488 }
2489 if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2490 $sql .= ' AND te.rowid = '.((int) $socid);
2491 }
2492 //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2493 // 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
2494
2495 $result = $this->db->query($sql);
2496 if (!$result) {
2497 $this->error = $this->db->lasterror();
2498 return -2;
2499 }
2500 $row = $this->db->fetch_row($result);
2501 $this->ref_next = $row[0];
2502
2503 return 1;
2504 }
2505
2506
2514 public function getListContactId($source = 'external')
2515 {
2516 $contactAlreadySelected = array();
2517 $tab = $this->liste_contact(-1, $source);
2518 $num = count($tab);
2519 $i = 0;
2520 while ($i < $num) {
2521 if ($source == 'thirdparty') {
2522 $contactAlreadySelected[$i] = $tab[$i]['socid'];
2523 } else {
2524 $contactAlreadySelected[$i] = $tab[$i]['id'];
2525 }
2526 $i++;
2527 }
2528 return $contactAlreadySelected;
2529 }
2530
2531
2539 public function setProject($projectid, $notrigger = 0)
2540 {
2541 global $user;
2542 $error = 0;
2543
2544 if (!$this->table_element) {
2545 dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2546 return -1;
2547 }
2548
2549 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2550 if (!empty($this->fields['fk_project'])) { // Common case
2551 if ($projectid) {
2552 $sql .= " SET fk_project = ".((int) $projectid);
2553 } else {
2554 $sql .= " SET fk_project = NULL";
2555 }
2556 $sql .= ' WHERE rowid = '.((int) $this->id);
2557 } elseif ($this->table_element == 'actioncomm') { // Special case for actioncomm
2558 if ($projectid) {
2559 $sql .= " SET fk_project = ".((int) $projectid);
2560 } else {
2561 $sql .= " SET fk_project = NULL";
2562 }
2563 $sql .= ' WHERE id = '.((int) $this->id);
2564 } else { // Special case for old architecture objects
2565 if ($projectid) {
2566 $sql .= ' SET fk_projet = '.((int) $projectid);
2567 } else {
2568 $sql .= ' SET fk_projet = NULL';
2569 }
2570 $sql .= " WHERE rowid = ".((int) $this->id);
2571 }
2572
2573 $this->db->begin();
2574
2575 dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2576 if ($this->db->query($sql)) {
2577 $this->fk_project = ((int) $projectid);
2578 } else {
2579 dol_print_error($this->db);
2580 $error++;
2581 }
2582
2583 // Triggers
2584 if (!$error && !$notrigger) {
2585 // Call triggers
2586 $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2587 if ($result < 0) {
2588 $error++;
2589 } //Do also here what you must do to rollback action if trigger fail
2590 // End call triggers
2591 }
2592
2593 // Commit or rollback
2594 if ($error) {
2595 $this->db->rollback();
2596 return -1;
2597 } else {
2598 $this->db->commit();
2599 return 1;
2600 }
2601 }
2602
2609 public function setPaymentMethods($id)
2610 {
2611 global $user;
2612
2613 $error = 0;
2614 $notrigger = 0;
2615
2616 dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2617
2618 if ($this->status >= 0 || $this->element == 'societe') {
2619 // TODO uniformize field name
2620 $fieldname = 'fk_mode_reglement';
2621 if ($this->element == 'societe') {
2622 $fieldname = 'mode_reglement';
2623 }
2624 if (get_class($this) == 'Fournisseur') {
2625 $fieldname = 'mode_reglement_supplier';
2626 }
2627 if (get_class($this) == 'Tva') {
2628 $fieldname = 'fk_typepayment';
2629 }
2630 if (get_class($this) == 'Salary') {
2631 $fieldname = 'fk_typepayment';
2632 }
2633
2634 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2635 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2636 $sql .= ' WHERE rowid='.((int) $this->id);
2637
2638 if ($this->db->query($sql)) {
2639 $this->mode_reglement_id = $id;
2640 // for supplier
2641 if (get_class($this) == 'Fournisseur') {
2642 $this->mode_reglement_supplier_id = $id;
2643 }
2644 // Triggers
2645 if (!$error && !$notrigger) {
2646 // Call triggers
2647 if (get_class($this) == 'Commande') {
2648 $result = $this->call_trigger('ORDER_MODIFY', $user);
2649 } else {
2650 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
2651 }
2652 if ($result < 0) {
2653 $error++;
2654 }
2655 // End call triggers
2656 }
2657 return 1;
2658 } else {
2659 dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2660 $this->error = $this->db->error();
2661 return -1;
2662 }
2663 } else {
2664 dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2665 $this->error = 'Status of the object is incompatible '.$this->status;
2666 return -2;
2667 }
2668 }
2669
2676 public function setMulticurrencyCode($code)
2677 {
2678 dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2679 if ($this->status >= 0 || $this->element == 'societe') {
2680 $fieldname = 'multicurrency_code';
2681
2682 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2683 $sql .= " SET ".$fieldname." = '".$this->db->escape($code)."'";
2684 $sql .= ' WHERE rowid='.((int) $this->id);
2685
2686 if ($this->db->query($sql)) {
2687 $this->multicurrency_code = $code;
2688
2689 list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2690 if ($rate) {
2691 $this->setMulticurrencyRate($rate, 2);
2692 }
2693
2694 return 1;
2695 } else {
2696 dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2697 $this->error = $this->db->error();
2698 return -1;
2699 }
2700 } else {
2701 dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2702 $this->error = 'Status of the object is incompatible '.$this->status;
2703 return -2;
2704 }
2705 }
2706
2714 public function setMulticurrencyRate($rate, $mode = 1)
2715 {
2716 dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2717 if ($this->status >= 0 || $this->element == 'societe') {
2718 $fieldname = 'multicurrency_tx';
2719
2720 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2721 $sql .= " SET ".$fieldname." = ".((float) $rate);
2722 $sql .= ' WHERE rowid='.((int) $this->id);
2723
2724 if ($this->db->query($sql)) {
2725 $this->multicurrency_tx = $rate;
2726
2727 // Update line price
2728 if (!empty($this->lines)) {
2729 foreach ($this->lines as &$line) {
2730 // Amounts in company currency will be recalculated
2731 if ($mode == 1) {
2732 $line->subprice = 0;
2733 }
2734
2735 // Amounts in foreign currency will be recalculated
2736 if ($mode == 2) {
2737 $line->multicurrency_subprice = 0;
2738 }
2739
2740 switch ($this->element) {
2741 case 'propal':
2744 $this->updateline(
2745 $line->id,
2746 $line->subprice,
2747 $line->qty,
2748 $line->remise_percent,
2749 $line->tva_tx,
2750 $line->localtax1_tx,
2751 $line->localtax2_tx,
2752 ($line->description ? $line->description : $line->desc),
2753 'HT',
2754 $line->info_bits,
2755 $line->special_code,
2756 $line->fk_parent_line,
2757 $line->skip_update_total,
2758 $line->fk_fournprice,
2759 $line->pa_ht,
2760 $line->label,
2761 $line->product_type,
2762 $line->date_start,
2763 $line->date_end,
2764 $line->array_options,
2765 $line->fk_unit,
2766 $line->multicurrency_subprice
2767 );
2768 break;
2769 case 'commande':
2772 $this->updateline(
2773 $line->id,
2774 ($line->description ? $line->description : $line->desc),
2775 $line->subprice,
2776 $line->qty,
2777 $line->remise_percent,
2778 $line->tva_tx,
2779 $line->localtax1_tx,
2780 $line->localtax2_tx,
2781 'HT',
2782 $line->info_bits,
2783 $line->date_start,
2784 $line->date_end,
2785 $line->product_type,
2786 $line->fk_parent_line,
2787 $line->skip_update_total,
2788 $line->fk_fournprice,
2789 $line->pa_ht,
2790 $line->label,
2791 $line->special_code,
2792 $line->array_options,
2793 $line->fk_unit,
2794 $line->multicurrency_subprice
2795 );
2796 break;
2797 case 'facture':
2800 $this->updateline(
2801 $line->id,
2802 ($line->description ? $line->description : $line->desc),
2803 $line->subprice,
2804 $line->qty,
2805 $line->remise_percent,
2806 $line->date_start,
2807 $line->date_end,
2808 $line->tva_tx,
2809 $line->localtax1_tx,
2810 $line->localtax2_tx,
2811 'HT',
2812 $line->info_bits,
2813 $line->product_type,
2814 $line->fk_parent_line,
2815 $line->skip_update_total,
2816 $line->fk_fournprice,
2817 $line->pa_ht,
2818 $line->label,
2819 $line->special_code,
2820 $line->array_options,
2821 $line->situation_percent,
2822 $line->fk_unit,
2823 $line->multicurrency_subprice
2824 );
2825 break;
2826 case 'supplier_proposal':
2829 $this->updateline(
2830 $line->id,
2831 $line->subprice,
2832 $line->qty,
2833 $line->remise_percent,
2834 $line->tva_tx,
2835 $line->localtax1_tx,
2836 $line->localtax2_tx,
2837 ($line->description ? $line->description : $line->desc),
2838 'HT',
2839 $line->info_bits,
2840 $line->special_code,
2841 $line->fk_parent_line,
2842 $line->skip_update_total,
2843 $line->fk_fournprice,
2844 $line->pa_ht,
2845 $line->label,
2846 $line->product_type,
2847 $line->array_options,
2848 $line->ref_fourn,
2849 $line->multicurrency_subprice
2850 );
2851 break;
2852 case 'order_supplier':
2855 $this->updateline(
2856 $line->id,
2857 ($line->description ? $line->description : $line->desc),
2858 $line->subprice,
2859 $line->qty,
2860 $line->remise_percent,
2861 $line->tva_tx,
2862 $line->localtax1_tx,
2863 $line->localtax2_tx,
2864 'HT',
2865 $line->info_bits,
2866 $line->product_type,
2867 false,
2868 $line->date_start,
2869 $line->date_end,
2870 $line->array_options,
2871 $line->fk_unit,
2872 $line->multicurrency_subprice,
2873 $line->ref_supplier
2874 );
2875 break;
2876 case 'invoice_supplier':
2879 $this->updateline(
2880 $line->id,
2881 ($line->description ? $line->description : $line->desc),
2882 $line->subprice,
2883 $line->tva_tx,
2884 $line->localtax1_tx,
2885 $line->localtax2_tx,
2886 $line->qty,
2887 0,
2888 'HT',
2889 $line->info_bits,
2890 $line->product_type,
2891 $line->remise_percent,
2892 false,
2893 $line->date_start,
2894 $line->date_end,
2895 $line->array_options,
2896 $line->fk_unit,
2897 $line->multicurrency_subprice,
2898 $line->ref_supplier
2899 );
2900 break;
2901 default:
2902 dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2903 break;
2904 }
2905 }
2906 }
2907
2908 return 1;
2909 } else {
2910 dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
2911 $this->error = $this->db->error();
2912 return -1;
2913 }
2914 } else {
2915 dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
2916 $this->error = 'Status of the object is incompatible '.$this->status;
2917 return -2;
2918 }
2919 }
2920
2928 public function setPaymentTerms($id, $deposit_percent = null)
2929 {
2930 dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.var_export($deposit_percent, true).')');
2931 if ($this->status >= 0 || $this->element == 'societe') {
2932 // TODO uniformize field name
2933 $fieldname = 'fk_cond_reglement';
2934 if ($this->element == 'societe') {
2935 $fieldname = 'cond_reglement';
2936 }
2937 if (get_class($this) == 'Fournisseur') {
2938 $fieldname = 'cond_reglement_supplier';
2939 }
2940
2941 if (empty($deposit_percent) || $deposit_percent < 0) {
2942 $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
2943 }
2944
2945 if ($deposit_percent > 100) {
2946 $deposit_percent = 100;
2947 }
2948
2949 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2950 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2951 if (in_array($this->table_element, array('propal', 'commande', 'societe'))) {
2952 $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape($deposit_percent)."'");
2953 }
2954 $sql .= ' WHERE rowid='.((int) $this->id);
2955
2956 if ($this->db->query($sql)) {
2957 $this->cond_reglement_id = $id;
2958 // for supplier
2959 if (get_class($this) == 'Fournisseur') {
2960 $this->cond_reglement_supplier_id = $id;
2961 }
2962 $this->cond_reglement = $id; // for compatibility
2963 $this->deposit_percent = $deposit_percent;
2964 return 1;
2965 } else {
2966 dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
2967 $this->error = $this->db->error();
2968 return -1;
2969 }
2970 } else {
2971 dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
2972 $this->error = 'Status of the object is incompatible '.$this->status;
2973 return -2;
2974 }
2975 }
2976
2983 public function setTransportMode($id)
2984 {
2985 dol_syslog(get_class($this).'::setTransportMode('.$id.')');
2986 if ($this->status >= 0 || $this->element == 'societe') {
2987 $fieldname = 'fk_transport_mode';
2988 if ($this->element == 'societe') {
2989 $fieldname = 'transport_mode';
2990 }
2991 if (get_class($this) == 'Fournisseur') {
2992 $fieldname = 'transport_mode_supplier';
2993 }
2994
2995 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2996 $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2997 $sql .= ' WHERE rowid='.((int) $this->id);
2998
2999 if ($this->db->query($sql)) {
3000 $this->transport_mode_id = $id;
3001 // for supplier
3002 if (get_class($this) == 'Fournisseur') {
3003 $this->transport_mode_supplier_id = $id;
3004 }
3005 return 1;
3006 } else {
3007 dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3008 $this->error = $this->db->error();
3009 return -1;
3010 }
3011 } else {
3012 dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3013 $this->error = 'Status of the object is incompatible '.$this->status;
3014 return -2;
3015 }
3016 }
3017
3025 {
3026 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
3027 if ($this->status >= 0 || $this->element == 'societe') {
3028 $fieldname = 'retained_warranty_fk_cond_reglement';
3029
3030 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3031 $sql .= " SET ".$fieldname." = ".((int) $id);
3032 $sql .= ' WHERE rowid='.((int) $this->id);
3033
3034 if ($this->db->query($sql)) {
3035 $this->retained_warranty_fk_cond_reglement = $id;
3036 return 1;
3037 } else {
3038 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
3039 $this->error = $this->db->error();
3040 return -1;
3041 }
3042 } else {
3043 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3044 $this->error = 'Status of the object is incompatible '.$this->status;
3045 return -2;
3046 }
3047 }
3048
3056 public function setDeliveryAddress($id)
3057 {
3058 $fieldname = 'fk_delivery_address';
3059 if ($this->element == 'delivery' || $this->element == 'shipping') {
3060 $fieldname = 'fk_address';
3061 }
3062
3063 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$fieldname." = ".((int) $id);
3064 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3065
3066 if ($this->db->query($sql)) {
3067 $this->fk_delivery_address = $id;
3068 return 1;
3069 } else {
3070 $this->error = $this->db->error();
3071 dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3072 return -1;
3073 }
3074 }
3075
3076
3085 public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3086 {
3087 global $user;
3088
3089 if (empty($userused)) {
3090 $userused = $user;
3091 }
3092
3093 $error = 0;
3094
3095 if (!$this->table_element) {
3096 dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3097 return -1;
3098 }
3099
3100 $this->db->begin();
3101
3102 if ($shipping_method_id < 0) {
3103 $shipping_method_id = 'NULL';
3104 }
3105 dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3106
3107 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3108 $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3109 $sql .= " WHERE rowid=".((int) $this->id);
3110 $resql = $this->db->query($sql);
3111 if (!$resql) {
3112 dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3113 $this->error = $this->db->lasterror();
3114 $error++;
3115 } else {
3116 if (!$notrigger) {
3117 // Call trigger
3118 $this->context = array('shippingmethodupdate' => 1);
3119 $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
3120 if ($result < 0) {
3121 $error++;
3122 }
3123 // End call trigger
3124 }
3125 }
3126 if ($error) {
3127 $this->db->rollback();
3128 return -1;
3129 } else {
3130 $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3131 $this->db->commit();
3132 return 1;
3133 }
3134 }
3135
3136
3143 public function setWarehouse($warehouse_id)
3144 {
3145 if (!$this->table_element) {
3146 dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3147 return -1;
3148 }
3149 if ($warehouse_id < 0) {
3150 $warehouse_id = 'NULL';
3151 }
3152 dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3153
3154 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3155 $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3156 $sql .= " WHERE rowid=".((int) $this->id);
3157
3158 if ($this->db->query($sql)) {
3159 $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3160 return 1;
3161 } else {
3162 dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3163 $this->error = $this->db->error();
3164 return 0;
3165 }
3166 }
3167
3168
3176 public function setDocModel($user, $modelpdf)
3177 {
3178 if (!$this->table_element) {
3179 dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3180 return -1;
3181 }
3182
3183 $newmodelpdf = dol_trunc($modelpdf, 255);
3184
3185 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3186 $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3187 $sql .= " WHERE rowid = ".((int) $this->id);
3188
3189 dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3190 $resql = $this->db->query($sql);
3191 if ($resql) {
3192 $this->model_pdf = $modelpdf;
3193 return 1;
3194 } else {
3195 dol_print_error($this->db);
3196 return 0;
3197 }
3198 }
3199
3200
3209 public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3210 {
3211 global $user;
3212
3213 if (empty($userused)) {
3214 $userused = $user;
3215 }
3216
3217 $error = 0;
3218
3219 if (!$this->table_element) {
3220 dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3221 return -1;
3222 }
3223 $this->db->begin();
3224
3225 if ($fk_account < 0) {
3226 $fk_account = 'NULL';
3227 }
3228 dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3229
3230 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3231 $sql .= " SET fk_account = ".((int) $fk_account);
3232 $sql .= " WHERE rowid=".((int) $this->id);
3233
3234 $resql = $this->db->query($sql);
3235 if (!$resql) {
3236 dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3237 $this->error = $this->db->lasterror();
3238 $error++;
3239 } else {
3240 if (!$notrigger) {
3241 // Call trigger
3242 $this->context['bankaccountupdate'] = 1;
3243 $triggerName = strtoupper(get_class($this)).'_MODIFY';
3244 // Special cases
3245 if ($triggerName == 'FACTUREREC_MODIFY') {
3246 $triggerName = 'BILLREC_MODIFY';
3247 }
3248 $result = $this->call_trigger($triggerName, $userused);
3249 if ($result < 0) {
3250 $error++;
3251 }
3252 // End call trigger
3253 }
3254 }
3255 if ($error) {
3256 $this->db->rollback();
3257 return -1;
3258 } else {
3259 $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3260 $this->db->commit();
3261 return 1;
3262 }
3263 }
3264
3265
3266 // TODO: Move line related operations to CommonObjectLine?
3267
3268 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3278 public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3279 {
3280 // phpcs:enable
3281 if (!$this->table_element_line) {
3282 dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3283 return -1;
3284 }
3285 if (!$this->fk_element) {
3286 dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3287 return -1;
3288 }
3289
3290 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3291 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3292 $fieldposition = 'position';
3293 }
3294
3295 // Count number of lines to reorder (according to choice $renum)
3296 $nl = 0;
3297 $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->table_element_line;
3298 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3299 if (!$renum) {
3300 $sql .= " AND " . $fieldposition . " = 0";
3301 }
3302 if ($renum) {
3303 $sql .= " AND " . $fieldposition . " <> 0";
3304 }
3305
3306 dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3307 $resql = $this->db->query($sql);
3308 if ($resql) {
3309 $row = $this->db->fetch_row($resql);
3310 $nl = $row[0];
3311 } else {
3312 dol_print_error($this->db);
3313 }
3314 if ($nl > 0) {
3315 // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3316 $rows = array();
3317
3318 // We first search all lines that are parent lines (for multilevel details lines)
3319 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3320 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3321 if ($fk_parent_line) {
3322 $sql .= ' AND fk_parent_line IS NULL';
3323 }
3324 $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3325
3326 dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3327 $resql = $this->db->query($sql);
3328 if ($resql) {
3329 $i = 0;
3330 $num = $this->db->num_rows($resql);
3331 while ($i < $num) {
3332 $row = $this->db->fetch_row($resql);
3333 $rows[] = $row[0]; // Add parent line into array rows
3334 $children = $this->getChildrenOfLine($row[0]);
3335 if (!empty($children)) {
3336 foreach ($children as $child) {
3337 array_push($rows, $child);
3338 }
3339 }
3340 $i++;
3341 }
3342
3343 // Now we set a new number for each lines (parent and children with children included into parent tree)
3344 if (!empty($rows)) {
3345 foreach ($rows as $key => $row) {
3346 $this->updateRangOfLine($row, ($key + 1));
3347 }
3348 }
3349 } else {
3350 dol_print_error($this->db);
3351 }
3352 }
3353 return 1;
3354 }
3355
3363 public function getChildrenOfLine($id, $includealltree = 0)
3364 {
3365 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3366 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3367 $fieldposition = 'position';
3368 }
3369
3370 $rows = array();
3371
3372 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3373 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3374 $sql .= ' AND fk_parent_line = '.((int) $id);
3375 $sql .= " ORDER BY " . $fieldposition . " ASC";
3376
3377 dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3378 $resql = $this->db->query($sql);
3379 if ($resql) {
3380 if ($this->db->num_rows($resql) > 0) {
3381 while ($row = $this->db->fetch_row($resql)) {
3382 $rows[] = $row[0];
3383 if (!empty($includealltree)) {
3384 $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree));
3385 }
3386 }
3387 }
3388 }
3389 return $rows;
3390 }
3391
3392 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3400 public function line_up($rowid, $fk_parent_line = true)
3401 {
3402 // phpcs:enable
3403 $this->line_order(false, 'ASC', $fk_parent_line);
3404
3405 // Get rang of line
3406 $rang = $this->getRangOfLine($rowid);
3407
3408 // Update position of line
3409 $this->updateLineUp($rowid, $rang);
3410 }
3411
3412 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3420 public function line_down($rowid, $fk_parent_line = true)
3421 {
3422 // phpcs:enable
3423 $this->line_order(false, 'ASC', $fk_parent_line);
3424
3425 // Get rang of line
3426 $rang = $this->getRangOfLine($rowid);
3427
3428 // Get max value for rang
3429 $max = $this->line_max();
3430
3431 // Update position of line
3432 $this->updateLineDown($rowid, $rang, $max);
3433 }
3434
3442 public function updateRangOfLine($rowid, $rang)
3443 {
3444 global $hookmanager;
3445 $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3446 if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3447 $fieldposition = 'position';
3448 }
3449
3450 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3451 $sql .= ' WHERE rowid = '.((int) $rowid);
3452
3453 dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3454 if (!$this->db->query($sql)) {
3455 dol_print_error($this->db);
3456 return -1;
3457 } else {
3458 $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3459 $action = '';
3460 $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3461 return 1;
3462 }
3463 }
3464
3465 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3472 public function line_ajaxorder($rows)
3473 {
3474 // phpcs:enable
3475 $num = count($rows);
3476 for ($i = 0; $i < $num; $i++) {
3477 $this->updateRangOfLine($rows[$i], ($i + 1));
3478 }
3479 }
3480
3488 public function updateLineUp($rowid, $rang)
3489 {
3490 if ($rang > 1) {
3491 $fieldposition = 'rang';
3492 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3493 $fieldposition = 'position';
3494 }
3495
3496 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3497 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3498 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3499 if ($this->db->query($sql)) {
3500 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3501 $sql .= ' WHERE rowid = '.((int) $rowid);
3502 if (!$this->db->query($sql)) {
3503 dol_print_error($this->db);
3504 }
3505 } else {
3506 dol_print_error($this->db);
3507 }
3508 }
3509 }
3510
3519 public function updateLineDown($rowid, $rang, $max)
3520 {
3521 if ($rang < $max) {
3522 $fieldposition = 'rang';
3523 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3524 $fieldposition = 'position';
3525 }
3526
3527 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3528 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3529 $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3530 if ($this->db->query($sql)) {
3531 $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3532 $sql .= ' WHERE rowid = '.((int) $rowid);
3533 if (!$this->db->query($sql)) {
3534 dol_print_error($this->db);
3535 }
3536 } else {
3537 dol_print_error($this->db);
3538 }
3539 }
3540 }
3541
3548 public function getRangOfLine($rowid)
3549 {
3550 $fieldposition = 'rang';
3551 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3552 $fieldposition = 'position';
3553 }
3554
3555 $sql = "SELECT " . $fieldposition . " FROM ".$this->db->prefix().$this->table_element_line;
3556 $sql .= " WHERE rowid = ".((int) $rowid);
3557
3558 dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3559 $resql = $this->db->query($sql);
3560 if ($resql) {
3561 $row = $this->db->fetch_row($resql);
3562 return $row[0];
3563 }
3564
3565 return 0;
3566 }
3567
3574 public function getIdOfLine($rang)
3575 {
3576 $fieldposition = 'rang';
3577 if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3578 $fieldposition = 'position';
3579 }
3580
3581 $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3582 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3583 $sql .= " AND " . $fieldposition . " = ".((int) $rang);
3584 $resql = $this->db->query($sql);
3585 if ($resql) {
3586 $row = $this->db->fetch_row($resql);
3587 return $row[0];
3588 }
3589
3590 return 0;
3591 }
3592
3593 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3600 public function line_max($fk_parent_line = 0)
3601 {
3602 // phpcs:enable
3603 $positionfield = 'rang';
3604 if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3605 $positionfield = 'position';
3606 }
3607
3608 // Search the last rang with fk_parent_line
3609 if ($fk_parent_line) {
3610 $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3611 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3612 $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3613
3614 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3615 $resql = $this->db->query($sql);
3616 if ($resql) {
3617 $row = $this->db->fetch_row($resql);
3618 if (!empty($row[0])) {
3619 return $row[0];
3620 } else {
3621 return $this->getRangOfLine($fk_parent_line);
3622 }
3623 }
3624 } else {
3625 // If not, search the last rang of element
3626 $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3627 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3628
3629 dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3630 $resql = $this->db->query($sql);
3631 if ($resql) {
3632 $row = $this->db->fetch_row($resql);
3633 return $row[0];
3634 }
3635 }
3636
3637 return 0;
3638 }
3639
3640 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3647 public function update_ref_ext($ref_ext)
3648 {
3649 // phpcs:enable
3650 if (!$this->table_element) {
3651 dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3652 return -1;
3653 }
3654
3655 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3656 $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3657 $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3658
3659 dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3660 if ($this->db->query($sql)) {
3661 $this->ref_ext = $ref_ext;
3662 return 1;
3663 } else {
3664 $this->error = $this->db->error();
3665 return -1;
3666 }
3667 }
3668
3669 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3678 public function update_note($note, $suffix = '', $notrigger = 0)
3679 {
3680 // phpcs:enable
3681 global $user;
3682
3683 if (!$this->table_element) {
3684 $this->error = 'update_note was called on object with property table_element not defined';
3685 dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3686 return -1;
3687 }
3688 if (!in_array($suffix, array('', '_public', '_private'))) {
3689 $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3690 dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3691 return -2;
3692 }
3693
3694 $newsuffix = $suffix;
3695
3696 // Special case
3697 if ($this->table_element == 'product' && $newsuffix == '_private') {
3698 $newsuffix = '';
3699 }
3700 if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3701 $fieldusermod = "fk_user_mod";
3702 } elseif ($this->table_element == 'ecm_files') {
3703 $fieldusermod = "fk_user_m";
3704 } else {
3705 $fieldusermod = "fk_user_modif";
3706 }
3707 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3708 $sql .= " SET note".$newsuffix." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3709 $sql .= ", ".$fieldusermod." = ".((int) $user->id);
3710 $sql .= " WHERE rowid = ".((int) $this->id);
3711
3712 dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3713 if ($this->db->query($sql)) {
3714 if ($suffix == '_public') {
3715 $this->note_public = $note;
3716 } elseif ($suffix == '_private') {
3717 $this->note_private = $note;
3718 } else {
3719 $this->note = $note; // deprecated
3720 $this->note_private = $note;
3721 }
3722 if (empty($notrigger)) {
3723 switch ($this->element) {
3724 case 'societe':
3725 $trigger_name = 'COMPANY_MODIFY';
3726 break;
3727 case 'commande':
3728 $trigger_name = 'ORDER_MODIFY';
3729 break;
3730 case 'facture':
3731 $trigger_name = 'BILL_MODIFY';
3732 break;
3733 case 'invoice_supplier':
3734 $trigger_name = 'BILL_SUPPLIER_MODIFY';
3735 break;
3736 case 'facturerec':
3737 $trigger_name = 'BILLREC_MODIFIY';
3738 break;
3739 case 'expensereport':
3740 $trigger_name = 'EXPENSE_REPORT_MODIFY';
3741 break;
3742 default:
3743 $trigger_name = strtoupper($this->element) . '_MODIFY';
3744 }
3745 $ret = $this->call_trigger($trigger_name, $user);
3746 if ($ret < 0) {
3747 return -1;
3748 }
3749 }
3750 return 1;
3751 } else {
3752 $this->error = $this->db->lasterror();
3753 return -1;
3754 }
3755 }
3756
3757 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3766 public function update_note_public($note)
3767 {
3768 // phpcs:enable
3769 return $this->update_note($note, '_public');
3770 }
3771
3772 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3783 public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
3784 {
3785 // phpcs:enable
3786 global $conf, $hookmanager, $action;
3787
3788 $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3789 $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3790 if ($reshook > 0) {
3791 return 1; // replacement code
3792 } elseif ($reshook < 0) {
3793 return -1; // failure
3794 } // reshook = 0 => execute normal code
3795
3796 // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3797 $isElementForSupplier = false;
3798 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
3799 $MODULE = "";
3800 if ($this->element == 'propal') {
3801 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3802 } elseif ($this->element == 'commande' || $this->element == 'order') {
3803 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3804 } elseif ($this->element == 'facture' || $this->element == 'invoice') {
3805 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3806 } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
3807 $isElementForSupplier = true;
3808 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3809 } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3810 $isElementForSupplier = true;
3811 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3812 } elseif ($this->element == 'supplier_proposal') {
3813 $isElementForSupplier = true;
3814 $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3815 }
3816 if ($isElementForSupplier) {
3817 $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
3818 }
3819
3820 if (!empty($MODULE)) {
3821 if (getDolGlobalString($MODULE)) {
3822 $modsactivated = explode(',', getDolGlobalString($MODULE));
3823 foreach ($modsactivated as $mod) {
3824 if (isModEnabled($mod)) {
3825 return 1; // update was disabled by specific setup
3826 }
3827 }
3828 }
3829 }
3830
3831 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3832
3833 $forcedroundingmode = $roundingadjust;
3834 if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
3835 $forcedroundingmode = getDolGlobalString($roundTotalConstName);
3836 } elseif ($forcedroundingmode == 'auto') {
3837 $forcedroundingmode = '0';
3838 }
3839
3840 $error = 0;
3841
3842 $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3843
3844 // Define constants to find lines to sum (field name int the table_element_line not into table_element)
3845 $fieldtva = 'total_tva';
3846 $fieldlocaltax1 = 'total_localtax1';
3847 $fieldlocaltax2 = 'total_localtax2';
3848 $fieldup = 'subprice';
3849 $base_price_type = 'HT';
3850 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3851 $fieldtva = 'tva';
3852 $fieldup = 'pu_ht';
3853 }
3854 if ($this->element == 'invoice_supplier_rec') {
3855 $fieldup = 'pu_ht';
3856 }
3857 if ($this->element == 'expensereport') {
3858 $fieldup = 'value_unit';
3859 $base_price_type = 'TTC';
3860 }
3861
3862 $sql = "SELECT rowid, qty, ".$fieldup." as up, remise_percent, total_ht, ".$fieldtva." as total_tva, total_ttc, ".$fieldlocaltax1." as total_localtax1, ".$fieldlocaltax2." as total_localtax2,";
3863 $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
3864 if ($this->table_element_line == 'facturedet') {
3865 $sql .= ', situation_percent';
3866 }
3867 $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
3868 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
3869 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3870 if ($exclspec) {
3871 $product_field = 'product_type';
3872 if ($this->table_element_line == 'contratdet') {
3873 $product_field = ''; // contratdet table has no product_type field
3874 }
3875 if ($product_field) {
3876 $sql .= " AND ".$product_field." <> 9";
3877 }
3878 }
3879 $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
3880
3881 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3882
3883 $resql = $this->db->query($sql);
3884 if ($resql) {
3885 $this->total_ht = 0;
3886 $this->total_tva = 0;
3887 $this->total_localtax1 = 0;
3888 $this->total_localtax2 = 0;
3889 $this->total_ttc = 0;
3890 $total_ht_by_vats = array();
3891 $total_tva_by_vats = array();
3892 $total_ttc_by_vats = array();
3893 $this->multicurrency_total_ht = 0;
3894 $this->multicurrency_total_tva = 0;
3895 $this->multicurrency_total_ttc = 0;
3896
3897 $this->db->begin();
3898
3899 $num = $this->db->num_rows($resql);
3900 $i = 0;
3901 while ($i < $num) {
3902 $obj = $this->db->fetch_object($resql);
3903
3904 // Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
3905 $parameters = array('fk_element' => $obj->rowid);
3906 $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3907
3908 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'
3909 // This part of code is to fix data. We should not call it too often.
3910 $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
3911 $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);
3912
3913 $diff_when_using_price_ht = price2num($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.
3914 $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
3915 //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
3916 //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
3917
3918 if ($diff_on_current_total) {
3919 // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
3920 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
3921 $sqlfix .= " SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
3922 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
3923 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
3924 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);
3925 $resqlfix = $this->db->query($sqlfix);
3926 if (!$resqlfix) {
3927 dol_print_error($this->db, 'Failed to update line');
3928 }
3929 $obj->total_tva = $tmpcal[1];
3930 $obj->total_ttc = $tmpcal[2];
3931 $obj->multicurrency_total_tva = $tmpcal[17];
3932 $obj->multicurrency_total_ttc = $tmpcal[18];
3933 } elseif ($diff_when_using_price_ht) {
3934 // 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
3935 // 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).
3936 if ((float) $tmpcal[0] == (float) $obj->total_ht) {
3937 // 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,
3938 // 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.
3939
3940 // 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.
3941 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line;
3942 $sqlfix .= " SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]);
3943 $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]);
3944 $sqlfix .= " WHERE rowid = ".((int) $obj->rowid);
3945 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);
3946 $resqlfix = $this->db->query($sqlfix);
3947 if (!$resqlfix) {
3948 dol_print_error($this->db, 'Failed to update line');
3949 }
3950 $obj->total_tva = $tmpcal[1];
3951 $obj->total_ttc = $tmpcal[2];
3952 $obj->multicurrency_total_tva = $tmpcal[17];
3953 $obj->multicurrency_total_ttc = $tmpcal[18];
3954 }
3955 }
3956 }
3957
3958 $this->total_ht += $obj->total_ht; // The field visible at end of line detail
3959 $this->total_tva += $obj->total_tva;
3960 $this->total_localtax1 += $obj->total_localtax1;
3961 $this->total_localtax2 += $obj->total_localtax2;
3962 $this->total_ttc += $obj->total_ttc;
3963 $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
3964 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
3965 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
3966
3967 if (!isset($total_ht_by_vats[$obj->vatrate])) {
3968 $total_ht_by_vats[$obj->vatrate] = 0;
3969 }
3970 if (!isset($total_tva_by_vats[$obj->vatrate])) {
3971 $total_tva_by_vats[$obj->vatrate] = 0;
3972 }
3973 if (!isset($total_ttc_by_vats[$obj->vatrate])) {
3974 $total_ttc_by_vats[$obj->vatrate] = 0;
3975 }
3976 $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
3977 $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
3978 $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
3979
3980 if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
3981 $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
3982 $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
3983 //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";
3984 if ($diff) {
3985 $maxdiff = (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)));
3986 if (abs((float) $diff) > $maxdiff) {
3987 // If error is more than 10 times the accuracy of rounding. This should not happen.
3988 $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.';
3989 dol_syslog($errmsg, LOG_WARNING);
3990 $this->error = $errmsg;
3991 $error++;
3992 break;
3993 }
3994 $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num($obj->total_tva - (float) $diff).", total_ttc = ".price2num($obj->total_ttc - (float) $diff)." WHERE rowid = ".((int) $obj->rowid);
3995 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);
3996
3997 $resqlfix = $this->db->query($sqlfix);
3998
3999 if (!$resqlfix) {
4000 dol_print_error($this->db, 'Failed to update line');
4001 }
4002
4003 $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4004 $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4005 $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4006 $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4007 }
4008 }
4009
4010 $i++;
4011 }
4012
4013 // Add revenue stamp to total
4014 $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4015 $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4016
4017 // Situations totals
4018 if (!empty($this->situation_cycle_ref) && !empty($this->situation_counter) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits')) {
4019 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
4020 if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4021 if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4022 $prev_sits = $this->get_prev_sits();
4023
4024 foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4025 $this->total_ht -= $sit->total_ht;
4026 $this->total_tva -= $sit->total_tva;
4027 $this->total_localtax1 -= $sit->total_localtax1;
4028 $this->total_localtax2 -= $sit->total_localtax2;
4029 $this->total_ttc -= $sit->total_ttc;
4030 $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4031 $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4032 $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4033 }
4034 }
4035 }
4036 }
4037
4038 // Clean total
4039 $this->total_ht = (float) price2num($this->total_ht);
4040 $this->total_tva = (float) price2num($this->total_tva);
4041 $this->total_localtax1 = (float) price2num($this->total_localtax1);
4042 $this->total_localtax2 = (float) price2num($this->total_localtax2);
4043 $this->total_ttc = (float) price2num($this->total_ttc);
4044
4045 $this->db->free($resql);
4046
4047 // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4048 $fieldht = 'total_ht';
4049 $fieldtva = 'tva';
4050 $fieldlocaltax1 = 'localtax1';
4051 $fieldlocaltax2 = 'localtax2';
4052 $fieldttc = 'total_ttc';
4053 // Specific code for backward compatibility with old field names
4054 if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4055 $fieldtva = 'total_tva';
4056 }
4057
4058 if (!$error && empty($nodatabaseupdate)) {
4059 $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4060 $sql .= " ".$fieldht." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4061 $sql .= " ".$fieldtva." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4062 $sql .= " ".$fieldlocaltax1." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4063 $sql .= " ".$fieldlocaltax2." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4064 $sql .= " ".$fieldttc." = ".((float) price2num($this->total_ttc, 'MT', 1));
4065 $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4066 $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4067 $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4068 $sql .= " WHERE rowid = ".((int) $this->id);
4069
4070 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4071 $resql = $this->db->query($sql);
4072
4073 if (!$resql) {
4074 $error++;
4075 $this->error = $this->db->lasterror();
4076 $this->errors[] = $this->db->lasterror();
4077 }
4078 }
4079
4080 if (!$error) {
4081 $this->db->commit();
4082 return 1;
4083 } else {
4084 $this->db->rollback();
4085 return -1;
4086 }
4087 } else {
4088 dol_print_error($this->db, 'Bad request in update_price');
4089 return -1;
4090 }
4091 }
4092
4093 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4104 public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4105 {
4106 // phpcs:enable
4107 global $user, $hookmanager, $action;
4108 $origin = (!empty($origin) ? $origin : $this->origin);
4109 $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4110 $f_user = isset($f_user) ? $f_user : $user;
4111
4112 // Special case
4113 if ($origin == 'order') {
4114 $origin = 'commande';
4115 }
4116 if ($origin == 'invoice') {
4117 $origin = 'facture';
4118 }
4119 if ($origin == 'invoice_template') {
4120 $origin = 'facturerec';
4121 }
4122 if ($origin == 'supplierorder') {
4123 $origin = 'order_supplier';
4124 }
4125
4126 // Add module part to target type
4127 $targettype = $this->getElementType();
4128
4129 $parameters = array('targettype' => $targettype);
4130 // Hook for explicitly set the targettype if it must be different than $this->element
4131 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4132 if ($reshook > 0) {
4133 if (!empty($hookmanager->resArray['targettype'])) {
4134 $targettype = $hookmanager->resArray['targettype'];
4135 }
4136 }
4137
4138 $this->db->begin();
4139 $error = 0;
4140
4141 $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4142 $sql .= "fk_source";
4143 $sql .= ", sourcetype";
4144 $sql .= ", fk_target";
4145 $sql .= ", targettype";
4146 $sql .= ") VALUES (";
4147 $sql .= ((int) $origin_id);
4148 $sql .= ", '" . $this->db->escape($origin) . "'";
4149 $sql .= ", " . ((int) $this->id);
4150 $sql .= ", '" . $this->db->escape($targettype) . "'";
4151 $sql .= ")";
4152
4153 dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4154 if ($this->db->query($sql)) {
4155 if (!$notrigger) {
4156 // Call trigger
4157 $this->context['link_origin'] = $origin;
4158 $this->context['link_origin_id'] = $origin_id;
4159 $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
4160 if ($result < 0) {
4161 $error++;
4162 }
4163 // End call triggers
4164 }
4165 } else {
4166 $this->error = $this->db->lasterror();
4167 $error++;
4168 }
4169
4170 if (!$error) {
4171 $this->db->commit();
4172 return 1;
4173 } else {
4174 $this->db->rollback();
4175 return 0;
4176 }
4177 }
4178
4184 public function getElementType()
4185 {
4186 // 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.
4187 // 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).
4188 $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4189 // Add module part to target type if object has $module property and isn't in core modules.
4190 return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4191 }
4192
4193
4216 public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4217 {
4218 global $conf, $hookmanager, $action;
4219
4220 // Important for pdf generation time reduction
4221 // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4222 // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4223 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4224 return 1;
4225 }
4226
4227 $this->linkedObjectsIds = array();
4228 $this->linkedObjects = array();
4229
4230 $justsource = false;
4231 $justtarget = false;
4232 $withtargettype = false;
4233 $withsourcetype = false;
4234
4235 $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4236 // Hook for explicitly set the targettype if it must be differtent than $this->element
4237 $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4238 if ($reshook > 0) {
4239 if (!empty($hookmanager->resArray['sourcetype'])) {
4240 $sourcetype = $hookmanager->resArray['sourcetype'];
4241 }
4242 if (!empty($hookmanager->resArray['sourceid'])) {
4243 $sourceid = $hookmanager->resArray['sourceid'];
4244 }
4245 if (!empty($hookmanager->resArray['targettype'])) {
4246 $targettype = $hookmanager->resArray['targettype'];
4247 }
4248 if (!empty($hookmanager->resArray['targetid'])) {
4249 $targetid = $hookmanager->resArray['targetid'];
4250 }
4251 }
4252
4253 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4254 $justsource = true; // the source (id and type) is a search criteria
4255 if (!empty($targettype)) {
4256 $withtargettype = true;
4257 }
4258 }
4259 if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4260 $justtarget = true; // the target (id and type) is a search criteria
4261 if (!empty($sourcetype)) {
4262 $withsourcetype = true;
4263 }
4264 }
4265
4266 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4267 $targetid = (!empty($targetid) ? $targetid : $this->id);
4268 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->getElementType());
4269 $targettype = (!empty($targettype) ? $targettype : $this->getElementType());
4270
4271 /*if (empty($sourceid) && empty($targetid))
4272 {
4273 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4274 return -1;
4275 }*/
4276
4277 // Links between objects are stored in table element_element
4278 $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4279 $sql .= " FROM ".$this->db->prefix()."element_element";
4280 $sql .= " WHERE ";
4281 if ($justsource || $justtarget) {
4282 if ($justsource) {
4283 $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4284 if ($withtargettype) {
4285 $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4286 }
4287 } elseif ($justtarget) {
4288 $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4289 if ($withsourcetype) {
4290 $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4291 }
4292 }
4293 } else {
4294 $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4295 $sql .= " ".$clause." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4296 if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4297 $this->linkedObjectsFullLoaded[$this->id] = true;
4298 }
4299 }
4300 $sql .= " ORDER BY ".$orderby;
4301
4302 dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4303 $resql = $this->db->query($sql);
4304 if ($resql) {
4305 $num = $this->db->num_rows($resql);
4306 $i = 0;
4307 while ($i < $num) {
4308 $obj = $this->db->fetch_object($resql);
4309 if ($justsource || $justtarget) {
4310 if ($justsource) {
4311 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4312 } elseif ($justtarget) {
4313 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4314 }
4315 } else {
4316 if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4317 $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4318 }
4319 if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4320 $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4321 }
4322 }
4323 $i++;
4324 }
4325
4326 if (!empty($this->linkedObjectsIds)) {
4327 $tmparray = $this->linkedObjectsIds;
4328 foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4329 $element_properties = getElementProperties($objecttype);
4330 $element = $element_properties['element'];
4331 $classPath = $element_properties['classpath'];
4332 $classFile = $element_properties['classfile'];
4333 $className = $element_properties['classname'];
4334 $module = $element_properties['module'];
4335
4336 // Here $module, $classFile and $className are set, we can use them.
4337 if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4338 if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4339 dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4340 if (class_exists($className)) {
4341 foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4342 $object = new $className($this->db);
4343 $ret = $object->fetch($objectid);
4344 if ($ret >= 0) {
4345 $this->linkedObjects[$objecttype][$i] = $object;
4346 }
4347 }
4348 }
4349 }
4350 } else {
4351 unset($this->linkedObjectsIds[$objecttype]);
4352 }
4353 }
4354 }
4355 return 1;
4356 } else {
4357 dol_print_error($this->db);
4358 return -1;
4359 }
4360 }
4361
4368 public function clearObjectLinkedCache()
4369 {
4370 if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4371 unset($this->linkedObjectsFullLoaded[$this->id]);
4372 }
4373
4374 return 1;
4375 }
4376
4389 public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4390 {
4391 global $user;
4392 $updatesource = false;
4393 $updatetarget = false;
4394 $f_user = isset($f_user) ? $f_user : $user;
4395
4396 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4397 $updatesource = true;
4398 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4399 $updatetarget = true;
4400 }
4401
4402 $this->db->begin();
4403 $error = 0;
4404
4405 $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4406 if ($updatesource) {
4407 $sql .= "fk_source = " . ((int) $sourceid);
4408 $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4409 $sql .= " WHERE fk_target = " . ((int) $this->id);
4410 $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4411 } elseif ($updatetarget) {
4412 $sql .= "fk_target = " . ((int) $targetid);
4413 $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4414 $sql .= " WHERE fk_source = " . ((int) $this->id);
4415 $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4416 }
4417
4418 dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4419 if ($this->db->query($sql)) {
4420 if (!$notrigger) {
4421 // Call trigger
4422 $this->context['link_source_id'] = $sourceid;
4423 $this->context['link_source_type'] = $sourcetype;
4424 $this->context['link_target_id'] = $targetid;
4425 $this->context['link_target_type'] = $targettype;
4426 $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user);
4427 if ($result < 0) {
4428 $error++;
4429 }
4430 // End call triggers
4431 }
4432 } else {
4433 $this->error = $this->db->lasterror();
4434 $error++;
4435 }
4436
4437 if (!$error) {
4438 $this->db->commit();
4439 return 1;
4440 } else {
4441 $this->db->rollback();
4442 return -1;
4443 }
4444 }
4445
4459 public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4460 {
4461 global $user;
4462 $deletesource = false;
4463 $deletetarget = false;
4464 $f_user = isset($f_user) ? $f_user : $user;
4465
4466 if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4467 $deletesource = true;
4468 } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4469 $deletetarget = true;
4470 }
4471
4472 $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4473 $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
4474 $targetid = (!empty($targetid) ? $targetid : $this->id);
4475 $targettype = (!empty($targettype) ? $targettype : $this->element);
4476 $this->db->begin();
4477 $error = 0;
4478
4479 if (!$notrigger) {
4480 // Call trigger
4481 $this->context['link_id'] = $rowid;
4482 $this->context['link_source_id'] = $sourceid;
4483 $this->context['link_source_type'] = $sourcetype;
4484 $this->context['link_target_id'] = $targetid;
4485 $this->context['link_target_type'] = $targettype;
4486 $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
4487 if ($result < 0) {
4488 $error++;
4489 }
4490 // End call triggers
4491 }
4492
4493 if (!$error) {
4494 $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4495 $sql .= " WHERE";
4496 if ($rowid > 0) {
4497 $sql .= " rowid = " . ((int) $rowid);
4498 } else {
4499 if ($deletesource) {
4500 $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4501 $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "'";
4502 } elseif ($deletetarget) {
4503 $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4504 $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4505 } else {
4506 $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
4507 $sql .= " OR";
4508 $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "')";
4509 }
4510 }
4511
4512 dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4513 if (!$this->db->query($sql)) {
4514 $this->error = $this->db->lasterror();
4515 $this->errors[] = $this->error;
4516 $error++;
4517 }
4518 }
4519
4520 if (!$error) {
4521 $this->db->commit();
4522 return 1;
4523 } else {
4524 $this->db->rollback();
4525 return 0;
4526 }
4527 }
4528
4538 public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4539 {
4540 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4541 return -1;
4542 }
4543 if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4544 dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4545 }
4546
4547 global $db;
4548
4549 $sql = "SELECT ".$field_select." FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4550 $resql = $db->query($sql);
4551
4552 $TRes = array();
4553 if (!empty($resql)) {
4554 while ($res = $db->fetch_object($resql)) {
4555 $TRes[] = $res->{$field_select};
4556 }
4557 }
4558
4559 return $TRes;
4560 }
4561
4570 public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4571 {
4572 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4573 return -1;
4574 }
4575
4576 global $db;
4577
4578 $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4579 $resql = $db->query($sql);
4580 $n = 0;
4581 if ($resql) {
4582 $res = $db->fetch_object($resql);
4583 if ($res) {
4584 $n = $res->nb;
4585 }
4586 }
4587
4588 return $n;
4589 }
4590
4599 public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4600 {
4601 if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4602 return -1;
4603 }
4604
4605 global $db;
4606
4607 $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4608 $resql = $db->query($sql);
4609
4610 if (empty($resql)) {
4611 return 0;
4612 }
4613
4614 return 1;
4615 }
4616
4627 public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = 'fk_statut')
4628 {
4629 global $user;
4630
4631 $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4632
4633 $elementId = (!empty($elementId) ? $elementId : $this->id);
4634 $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4635
4636 $this->db->begin();
4637
4638 if ($elementTable == 'facture_rec') {
4639 $fieldstatus = "suspended";
4640 }
4641 if ($elementTable == 'mailing') {
4642 $fieldstatus = "statut";
4643 }
4644 if ($elementTable == 'cronjob') {
4645 $fieldstatus = "status";
4646 }
4647 if ($elementTable == 'user') {
4648 $fieldstatus = "statut";
4649 }
4650 if ($elementTable == 'expensereport') {
4651 $fieldstatus = "fk_statut";
4652 }
4653 if ($elementTable == 'receptiondet_batch') {
4654 $fieldstatus = "status";
4655 }
4656 if ($elementTable == 'prelevement_bons') {
4657 $fieldstatus = "statut";
4658 }
4659 if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4660 $fieldstatus = 'status';
4661 }
4662
4663 $sql = "UPDATE ".$this->db->prefix().$this->db->sanitize($elementTable);
4664 $sql .= " SET ".$this->db->sanitize($fieldstatus)." = ".((int) $status);
4665 // If status = 1 = validated, update also fk_user_valid
4666 // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields
4667 if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4668 $sql .= ", fk_user_valid = ".((int) $user->id);
4669 }
4670 if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4671 $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
4672 }
4673 if ($status == 1 && in_array($elementTable, array('inventory'))) {
4674 $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
4675 }
4676 $sql .= " WHERE rowid = ".((int) $elementId);
4677 $sql .= " AND ".$fieldstatus." <> ".((int) $status); // We avoid update if status already correct
4678
4679 dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4680 $resql = $this->db->query($sql);
4681 if ($resql) {
4682 $error = 0;
4683
4684 $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
4685
4686 if ($nb_rows_affected > 0) {
4687 if (empty($trigkey)) {
4688 // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
4689 if ($this->element == 'supplier_proposal' && $status == 2) {
4690 $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4691 }
4692 if ($this->element == 'supplier_proposal' && $status == 3) {
4693 $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4694 }
4695 if ($this->element == 'supplier_proposal' && $status == 4) {
4696 $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4697 }
4698 if ($this->element == 'fichinter' && $status == 3) {
4699 $trigkey = 'FICHINTER_CLASSIFY_DONE';
4700 }
4701 if ($this->element == 'fichinter' && $status == 2) {
4702 $trigkey = 'FICHINTER_CLASSIFY_BILLED';
4703 }
4704 if ($this->element == 'fichinter' && $status == 1) {
4705 $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4706 }
4707 }
4708
4709 $this->context = array_merge($this->context, array('newstatus' => $status));
4710
4711 if ($trigkey) {
4712 // Call trigger
4713 $result = $this->call_trigger($trigkey, $user);
4714 if ($result < 0) {
4715 $error++;
4716 }
4717 // End call triggers
4718 }
4719 } else {
4720 // The status was probably already good. We do nothing more, no triggers.
4721 }
4722
4723 if (!$error) {
4724 $this->db->commit();
4725
4726 if (empty($savElementId)) {
4727 // If the element we update is $this (so $elementId was provided as null)
4728 if ($fieldstatus == 'tosell') {
4729 $this->status = $status;
4730 } elseif ($fieldstatus == 'tobuy') {
4731 $this->status_buy = $status; // @phpstan-ignore-line
4732 } else {
4733 $this->status = $status;
4734 }
4735 }
4736
4737 return 1;
4738 } else {
4739 $this->db->rollback();
4740 dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
4741 return -1;
4742 }
4743 } else {
4744 $this->error = $this->db->lasterror();
4745 $this->db->rollback();
4746 return -1;
4747 }
4748 }
4749
4750
4758 public function getCanvas($id = 0, $ref = '')
4759 {
4760 global $conf;
4761
4762 if (empty($id) && empty($ref)) {
4763 return 0;
4764 }
4765 if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
4766 return 0; // To increase speed. Not enabled by default.
4767 }
4768
4769 // Clean parameters
4770 $ref = trim($ref);
4771
4772 $sql = "SELECT rowid, canvas";
4773 $sql .= " FROM ".$this->db->prefix().$this->table_element;
4774 $sql .= " WHERE entity IN (".getEntity($this->element).")";
4775 if (!empty($id)) {
4776 $sql .= " AND rowid = ".((int) $id);
4777 }
4778 if (!empty($ref)) {
4779 $sql .= " AND ref = '".$this->db->escape($ref)."'";
4780 }
4781
4782 $resql = $this->db->query($sql);
4783 if ($resql) {
4784 $obj = $this->db->fetch_object($resql);
4785 if ($obj) {
4786 $this->canvas = $obj->canvas;
4787 return 1;
4788 } else {
4789 return 0;
4790 }
4791 } else {
4792 dol_print_error($this->db);
4793 return -1;
4794 }
4795 }
4796
4797
4804 public function getSpecialCode($lineid)
4805 {
4806 $sql = "SELECT special_code FROM ".$this->db->prefix().$this->table_element_line;
4807 $sql .= " WHERE rowid = ".((int) $lineid);
4808 $resql = $this->db->query($sql);
4809 if ($resql) {
4810 $row = $this->db->fetch_row($resql);
4811 return (!empty($row[0]) ? $row[0] : 0);
4812 }
4813
4814 return 0;
4815 }
4816
4825 public function isObjectUsed($id = 0, $entity = 0)
4826 {
4827 global $langs;
4828
4829 if (empty($id)) {
4830 $id = $this->id;
4831 }
4832
4833 // Check parameters
4834 if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
4835 dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
4836 return -1;
4837 }
4838
4839 $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
4840 // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
4841 $tmparray = array_keys($this->childtables);
4842 if (is_numeric($tmparray[0])) {
4843 $arraytoscan = array_flip($this->childtables);
4844 }
4845
4846 // Test if child exists
4847 $haschild = 0;
4848 foreach ($arraytoscan as $table => $element) {
4849 //print $id.'-'.$table.'-'.$elementname.'<br>';
4850
4851 // Check if module is enabled (to avoid error if tables of module not created)
4852 if (isset($element['enabled']) && !empty($element['enabled'])) {
4853 $enabled = (int) dol_eval($element['enabled'], 1);
4854 if (empty($enabled)) {
4855 continue;
4856 }
4857 }
4858
4859 // Check if element can be deleted
4860 $sql = "SELECT COUNT(*) as nb";
4861 $sql .= " FROM ".$this->db->prefix().$table." as c";
4862 if (!empty($element['parent']) && !empty($element['parentkey'])) {
4863 $sql .= ", ".$this->db->prefix().$element['parent']." as p";
4864 }
4865 if (!empty($element['fk_element'])) {
4866 $sql .= " WHERE c.".$element['fk_element']." = ".((int) $id);
4867 } else {
4868 $sql .= " WHERE c.".$this->fk_element." = ".((int) $id);
4869 }
4870 if (!empty($element['parent']) && !empty($element['parentkey'])) {
4871 $sql .= " AND c.".$element['parentkey']." = p.rowid";
4872 }
4873 if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
4874 $sql .= " AND c.".$element['parenttypefield']." = '".$this->db->escape($element['parenttypevalue'])."'";
4875 }
4876 if (!empty($entity)) {
4877 if (!empty($element['parent']) && !empty($element['parentkey'])) {
4878 $sql .= " AND p.entity = ".((int) $entity);
4879 } else {
4880 $sql .= " AND c.entity = ".((int) $entity);
4881 }
4882 }
4883
4884 $resql = $this->db->query($sql);
4885 if ($resql) {
4886 $obj = $this->db->fetch_object($resql);
4887 if ($obj->nb > 0) {
4888 $langs->load("errors");
4889 //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
4890 $haschild += $obj->nb;
4891 if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
4892 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
4893 } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
4894 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
4895 } else { // new usage: $element['name']=Translation key
4896 $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
4897 }
4898 break; // We found at least one, we stop here
4899 }
4900 } else {
4901 $this->errors[] = $this->db->lasterror();
4902 return -1;
4903 }
4904 }
4905 if ($haschild > 0) {
4906 $this->errors[] = "ErrorRecordHasChildren";
4907 return $haschild;
4908 } else {
4909 return 0;
4910 }
4911 }
4912
4919 public function hasProductsOrServices($predefined = -1)
4920 {
4921 $nb = 0;
4922
4923 foreach ($this->lines as $key => $val) {
4924 $qualified = 0;
4925 if ($predefined == -1) {
4926 $qualified = 1;
4927 }
4928 if ($predefined == 1 && $val->fk_product > 0) {
4929 $qualified = 1;
4930 }
4931 if ($predefined == 0 && $val->fk_product <= 0) {
4932 $qualified = 1;
4933 }
4934 if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
4935 $qualified = 1;
4936 }
4937 if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
4938 $qualified = 1;
4939 }
4940 if ($qualified) {
4941 $nb++;
4942 }
4943 }
4944 dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
4945 return $nb;
4946 }
4947
4953 public function getTotalDiscount()
4954 {
4955 if (!empty($this->table_element_line)) {
4956 $total_discount = 0.00;
4957
4958 $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
4959 $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
4960 $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4961
4962 dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
4963 $resql = $this->db->query($sql);
4964 if ($resql) {
4965 $num = $this->db->num_rows($resql);
4966 $i = 0;
4967 while ($i < $num) {
4968 $obj = $this->db->fetch_object($resql);
4969
4970 $pu_ht = $obj->pu_ht;
4971 $qty = $obj->qty;
4972 $total_ht = $obj->total_ht;
4973
4974 $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
4975 $total_discount += $total_discount_line;
4976
4977 $i++;
4978 }
4979 }
4980
4981 //print $total_discount; exit;
4982 return (float) price2num($total_discount);
4983 }
4984
4985 return null;
4986 }
4987
4988
4995 public function getTotalWeightVolume()
4996 {
4997 $totalWeight = 0;
4998 $totalVolume = 0;
4999 // defined for shipment only
5000 $totalOrdered = 0;
5001 // defined for shipment only
5002 $totalToShip = 0;
5003
5004 if (empty($this->lines)) {
5005 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5006 }
5007
5008 foreach ($this->lines as $line) {
5009 if (isset($line->qty_asked)) {
5010 $totalOrdered += $line->qty_asked; // defined for shipment only
5011 }
5012 if (isset($line->qty_shipped)) {
5013 $totalToShip += $line->qty_shipped; // defined for shipment only
5014 } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5015 if (empty($totalToShip)) {
5016 $totalToShip = 0;
5017 }
5018 $totalToShip += $line->qty; // defined for reception only
5019 }
5020
5021 // Define qty, weight, volume, weight_units, volume_units
5022 if ($this->element == 'shipping') {
5023 // for shipments
5024 $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5025 } else {
5026 $qty = $line->qty ? $line->qty : 0;
5027 }
5028
5029 $weight = !empty($line->weight) ? $line->weight : 0;
5030 ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5031 $volume = !empty($line->volume) ? $line->volume : 0;
5032 ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5033
5034 $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5035 ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5036 $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5037 ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5038
5039 $weightUnit = 0;
5040 $volumeUnit = 0;
5041 if (!empty($weight_units)) {
5042 $weightUnit = $weight_units;
5043 }
5044 if (!empty($volume_units)) {
5045 $volumeUnit = $volume_units;
5046 }
5047
5048 if (empty($totalWeight)) {
5049 $totalWeight = 0; // Avoid warning because $totalWeight is ''
5050 }
5051 if (empty($totalVolume)) {
5052 $totalVolume = 0; // Avoid warning because $totalVolume is ''
5053 }
5054
5055 //var_dump($line->volume_units);
5056 if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5057 $trueWeightUnit = pow(10, $weightUnit);
5058 $totalWeight += $weight * $qty * $trueWeightUnit;
5059 } else {
5060 if ($weight_units == 99) {
5061 // conversion 1 Pound = 0.45359237 KG
5062 $trueWeightUnit = 0.45359237;
5063 $totalWeight += $weight * $qty * $trueWeightUnit;
5064 } elseif ($weight_units == 98) {
5065 // conversion 1 Ounce = 0.0283495 KG
5066 $trueWeightUnit = 0.0283495;
5067 $totalWeight += $weight * $qty * $trueWeightUnit;
5068 } else {
5069 $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5070 }
5071 }
5072 if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5073 //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5074 $trueVolumeUnit = pow(10, $volumeUnit);
5075 //print $line->volume;
5076 $totalVolume += $volume * $qty * $trueVolumeUnit;
5077 } else {
5078 $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5079 }
5080 }
5081
5082 return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5083 }
5084
5085
5091 public function setExtraParameters()
5092 {
5093 $this->db->begin();
5094
5095 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5096
5097 $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5098 $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5099 $sql .= " WHERE rowid = ".((int) $this->id);
5100
5101 dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5102 $resql = $this->db->query($sql);
5103 if (!$resql) {
5104 $this->error = $this->db->lasterror();
5105 $this->db->rollback();
5106 return -1;
5107 } else {
5108 $this->db->commit();
5109 return 1;
5110 }
5111 }
5112
5113
5114 // --------------------
5115 // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5116 // --------------------
5117
5118 /* This is to show add lines */
5119
5129 public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5130 {
5131 global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5132
5133 // Line extrafield
5134 if (!is_object($extrafields)) {
5135 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5136 $extrafields = new ExtraFields($this->db);
5137 }
5138 $extrafields->fetch_name_optionals_label($this->table_element_line);
5139
5140 // Output template part (modules that overwrite templates must declare this into descriptor)
5141 // Use global variables + $dateSelector + $seller and $buyer
5142 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5143 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5144 foreach ($dirtpls as $module => $reldir) {
5145 if (!empty($module)) {
5146 $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5147 } else {
5148 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5149 }
5150
5151 if (empty($conf->file->strict_mode)) {
5152 $res = @include $tpl;
5153 } else {
5154 $res = include $tpl; // for debug
5155 }
5156 if ($res) {
5157 break;
5158 }
5159 }
5160 }
5161
5162
5163
5164 /* This is to show array of line of details */
5165
5166
5181 public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5182 {
5183 global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5184 // TODO We should not use global var for this
5185 global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5186
5187 // Define $usemargins (used by objectline_xxx.tpl.php files)
5188 $usemargins = 0;
5189 if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5190 $usemargins = 1;
5191 }
5192
5193 $num = count($this->lines);
5194
5195 // Line extrafield
5196 if (!is_object($extrafields)) {
5197 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5198 $extrafields = new ExtraFields($this->db);
5199 }
5200 $extrafields->fetch_name_optionals_label($this->table_element_line);
5201
5202 $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5203 $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5204 if (empty($reshook)) {
5205 // Output template part (modules that overwrite templates must declare this into descriptor)
5206 // Use global variables + $dateSelector + $seller and $buyer
5207 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5208 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5209 foreach ($dirtpls as $module => $reldir) {
5210 $res = 0;
5211 if (!empty($module)) {
5212 $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5213 } else {
5214 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5215 }
5216 if (file_exists($tpl)) {
5217 if (empty($conf->file->strict_mode)) {
5218 $res = @include $tpl;
5219 } else {
5220 $res = include $tpl; // for debug
5221 }
5222 }
5223 if ($res) {
5224 break;
5225 }
5226 }
5227 }
5228
5229 $i = 0;
5230
5231 print "<!-- begin printObjectLines() --><tbody>\n";
5232 foreach ($this->lines as $line) {
5233 //Line extrafield
5234 $line->fetch_optionals();
5235
5236 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line)))
5237 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5238 if (empty($line->fk_parent_line)) {
5239 $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5240 $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5241 } else {
5242 $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);
5243 $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5244 }
5245 }
5246 if (empty($reshook)) {
5247 $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5248 }
5249
5250 $i++;
5251 }
5252 print "</tbody><!-- end printObjectLines() -->\n";
5253 }
5254
5272 public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5273 {
5274 global $conf, $langs, $user, $object, $hookmanager;
5275 global $form;
5276 global $object_rights, $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5277
5278 $object_rights = $this->getRights();
5279
5280 // var used into tpl
5281 $text = '';
5282 $description = '';
5283
5284 // Line in view mode
5285 if ($action != 'editline' || $selected != $line->id) {
5286 // Product
5287 if (!empty($line->fk_product) && $line->fk_product > 0) {
5288 $product_static = new Product($this->db);
5289 $product_static->fetch($line->fk_product);
5290
5291 $product_static->ref = $line->ref; //can change ref in hook
5292 $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5293
5294 $text = $product_static->getNomUrl(1);
5295
5296 // Define output language and label
5297 if (getDolGlobalInt('MAIN_MULTILANGS')) {
5298 if (property_exists($this, 'socid') && !is_object($this->thirdparty)) {
5299 dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5300 return;
5301 }
5302
5303 $prod = new Product($this->db);
5304 $prod->fetch($line->fk_product);
5305
5306 $outputlangs = $langs;
5307 $newlang = '';
5308 if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5309 $newlang = GETPOST('lang_id', 'aZ09');
5310 }
5311 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5312 $newlang = $this->thirdparty->default_lang; // To use language of customer
5313 }
5314 if (!empty($newlang)) {
5315 $outputlangs = new Translate("", $conf);
5316 $outputlangs->setDefaultLang($newlang);
5317 }
5318
5319 $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5320 } else {
5321 $label = $line->product_label;
5322 }
5323
5324 $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5325 $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.
5326 }
5327
5328 $line->pu_ttc = price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5329
5330 // Output template part (modules that overwrite templates must declare this into descriptor)
5331 // Use global variables + $dateSelector + $seller and $buyer
5332 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5333 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5334 foreach ($dirtpls as $module => $reldir) {
5335 $res = 0;
5336 if (!empty($module)) {
5337 $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5338 } else {
5339 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5340 }
5341 //var_dump($tpl);
5342 if (file_exists($tpl)) {
5343 if (empty($conf->file->strict_mode)) {
5344 $res = @include $tpl;
5345 } else {
5346 $res = include $tpl; // for debug
5347 }
5348 }
5349 if ($res) {
5350 break;
5351 }
5352 }
5353 }
5354
5355 // Line in update mode
5356 if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5357 $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5358
5359 $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5360
5361 // Output template part (modules that overwrite templates must declare this into descriptor)
5362 // Use global variables + $dateSelector + $seller and $buyer
5363 // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5364 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5365 foreach ($dirtpls as $module => $reldir) {
5366 if (!empty($module)) {
5367 $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5368 } else {
5369 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5370 }
5371
5372 if (empty($conf->file->strict_mode)) {
5373 $res = @include $tpl;
5374 } else {
5375 $res = include $tpl; // for debug
5376 }
5377 if ($res) {
5378 break;
5379 }
5380 }
5381 }
5382 }
5383
5384
5385 /* This is to show array of line of details of source object */
5386
5387
5398 public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5399 {
5400 global $langs, $hookmanager, $form, $action;
5401
5402 print '<tr class="liste_titre">';
5403 print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5404 print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5405 print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5406 print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5407 if (isModEnabled("multicurrency")) {
5408 print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5409 }
5410 print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5411 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5412 print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5413 }
5414 print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5415 print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5416 print '<td class="center">'.$form->showCheckAddButtons('checkforselect', 1).'</td>';
5417 print '</tr>';
5418 $i = 0;
5419
5420 if (!empty($this->lines)) {
5421 foreach ($this->lines as $line) {
5422 $reshook = 0;
5423 //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5424 if (is_object($hookmanager)) { // Old code is commented on preceding line.
5425 $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5426 if (!empty($line->fk_parent_line)) {
5427 $parameters['fk_parent_line'] = $line->fk_parent_line;
5428 }
5429 $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5430 }
5431 if (empty($reshook)) {
5432 $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5433 }
5434
5435 $i++;
5436 }
5437 }
5438 }
5439
5453 public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5454 {
5455 global $langs, $conf;
5456
5457 //var_dump($line);
5458 if (!empty($line->date_start)) {
5459 $date_start = $line->date_start;
5460 } else {
5461 $date_start = $line->date_debut_prevue;
5462 if ($line->date_debut_reel) {
5463 $date_start = $line->date_debut_reel;
5464 }
5465 }
5466 if (!empty($line->date_end)) {
5467 $date_end = $line->date_end;
5468 } else {
5469 $date_end = $line->date_fin_prevue;
5470 if ($line->date_fin_reel) {
5471 $date_end = $line->date_fin_reel;
5472 }
5473 }
5474
5475 $this->tpl['id'] = $line->id;
5476
5477 $this->tpl['label'] = '';
5478 if (!empty($line->fk_parent_line)) {
5479 $this->tpl['label'] .= img_picto('', 'rightarrow');
5480 }
5481
5482 if (($line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5483 $discount = new DiscountAbsolute($this->db);
5484 if (property_exists($this, 'socid')) {
5485 $discount->fk_soc = $this->socid;
5486 $discount->socid = $this->socid;
5487 }
5488 $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5489 } elseif (!empty($line->fk_product)) {
5490 $productstatic = new Product($this->db);
5491 $productstatic->id = $line->fk_product;
5492 $productstatic->ref = $line->ref;
5493 $productstatic->type = $line->fk_product_type;
5494 if (empty($productstatic->ref)) {
5495 $line->fetch_product();
5496 $productstatic = $line->product;
5497 }
5498
5499 $this->tpl['label'] .= $productstatic->getNomUrl(1);
5500 $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5501 // Dates
5502 if ($line->product_type == 1 && ($date_start || $date_end)) {
5503 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5504 }
5505 } else {
5506 $this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
5507 if (!empty($line->desc)) {
5508 $this->tpl['label'] .= $line->desc;
5509 } else {
5510 $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5511 }
5512
5513 // Dates
5514 if ($line->product_type == 1 && ($date_start || $date_end)) {
5515 $this->tpl['label'] .= get_date_range($date_start, $date_end);
5516 }
5517 }
5518
5519 if (!empty($line->desc)) {
5520 if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5521 $discount = new DiscountAbsolute($this->db);
5522 $discount->fetch($line->fk_remise_except);
5523 $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5524 } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5525 $discount = new DiscountAbsolute($this->db);
5526 $discount->fetch($line->fk_remise_except);
5527 $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5528 } elseif ($line->desc == '(EXCESS RECEIVED)') {
5529 $discount = new DiscountAbsolute($this->db);
5530 $discount->fetch($line->fk_remise_except);
5531 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5532 } elseif ($line->desc == '(EXCESS PAID)') {
5533 $discount = new DiscountAbsolute($this->db);
5534 $discount->fetch($line->fk_remise_except);
5535 $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5536 } else {
5537 $this->tpl['description'] = dol_trunc($line->desc, 60);
5538 }
5539 } else {
5540 $this->tpl['description'] = '&nbsp;';
5541 }
5542
5543 // VAT Rate
5544 $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5545 $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
5546 if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5547 $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5548 }
5549
5550 $this->tpl['price'] = price($line->subprice);
5551 $this->tpl['total_ht'] = price($line->total_ht);
5552 $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5553 $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5554 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5555 $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
5556 }
5557 $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
5558
5559 // Is the line strike or not
5560 $this->tpl['strike'] = 0;
5561 if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5562 $this->tpl['strike'] = 1;
5563 }
5564
5565 // Output template part (modules that overwrite templates must declare this into descriptor)
5566 // Use global variables + $dateSelector + $seller and $buyer
5567 $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5568 foreach ($dirtpls as $module => $reldir) {
5569 if (!empty($module)) {
5570 $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5571 } else {
5572 $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5573 }
5574
5575 if (empty($conf->file->strict_mode)) {
5576 $res = @include $tpl;
5577 } else {
5578 $res = include $tpl; // for debug
5579 }
5580 if ($res) {
5581 break;
5582 }
5583 }
5584 }
5585
5586
5587 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5598 public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
5599 {
5600 // phpcs:enable
5601 $this->db->begin();
5602
5603 $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5604 $sql .= "resource_id";
5605 $sql .= ", resource_type";
5606 $sql .= ", element_id";
5607 $sql .= ", element_type";
5608 $sql .= ", busy";
5609 $sql .= ", mandatory";
5610 $sql .= ") VALUES (";
5611 $sql .= ((int) $resource_id);
5612 $sql .= ", '".$this->db->escape($resource_type)."'";
5613 $sql .= ", '".$this->db->escape($this->id)."'";
5614 $sql .= ", '".$this->db->escape($this->element)."'";
5615 $sql .= ", '".$this->db->escape($busy)."'";
5616 $sql .= ", '".$this->db->escape($mandatory)."'";
5617 $sql .= ")";
5618
5619 dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5620 if ($this->db->query($sql)) {
5621 $this->db->commit();
5622 return 1;
5623 } else {
5624 $this->error = $this->db->lasterror();
5625 $this->db->rollback();
5626 return 0;
5627 }
5628 }
5629
5630 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5639 public function delete_resource($rowid, $element, $notrigger = 0)
5640 {
5641 // phpcs:enable
5642 global $user;
5643
5644 $this->db->begin();
5645
5646 $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
5647 $sql .= " WHERE rowid = ".((int) $rowid);
5648
5649 dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5650
5651 $resql = $this->db->query($sql);
5652 if (!$resql) {
5653 $this->error = $this->db->lasterror();
5654 $this->db->rollback();
5655 return -1;
5656 } else {
5657 if (!$notrigger) {
5658 $result = $this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
5659 if ($result < 0) {
5660 $this->db->rollback();
5661 return -1;
5662 }
5663 }
5664 $this->db->commit();
5665 return 1;
5666 }
5667 }
5668
5669
5675 public function __clone()
5676 {
5677 // Force a copy of this->lines, otherwise it will point to same object.
5678 if (isset($this->lines) && is_array($this->lines)) {
5679 $nboflines = count($this->lines);
5680 for ($i = 0; $i < $nboflines; $i++) {
5681 if (is_object($this->lines[$i])) {
5682 $this->lines[$i] = clone $this->lines[$i];
5683 }
5684 }
5685 }
5686 }
5687
5701 protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5702 {
5703 global $conf, $langs, $user, $hookmanager, $action;
5704
5705 $srctemplatepath = '';
5706
5707 $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
5708 $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5709
5710 if (!empty($reshook)) {
5711 return $reshook;
5712 }
5713
5714 dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5715
5716 if (empty($modele)) {
5717 $this->error = 'BadValueForParameterModele';
5718 return -1;
5719 }
5720
5721 // Increase limit for PDF build
5722 $err = error_reporting();
5723 error_reporting(0);
5724 @set_time_limit(120);
5725 error_reporting($err);
5726
5727 // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5728 $tmp = explode(':', $modele, 2);
5729 $saved_model = $modele;
5730 if (!empty($tmp[1])) {
5731 $modele = $tmp[0];
5732 $srctemplatepath = $tmp[1];
5733 }
5734
5735 // Search template files
5736 $file = '';
5737 $classname = '';
5738 $filefound = '';
5739 $dirmodels = array('/');
5740 if (is_array($conf->modules_parts['models'])) {
5741 $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5742 }
5743 foreach ($dirmodels as $reldir) {
5744 foreach (array('doc', 'pdf') as $prefix) {
5745 if (in_array(get_class($this), array('Adherent'))) {
5746 // Member module use prefix_modele.class.php
5747 $file = $prefix."_".$modele.".class.php";
5748 } else {
5749 // Other module use prefix_modele.modules.php
5750 $file = $prefix."_".$modele.".modules.php";
5751 }
5752
5753 $file = dol_sanitizeFileName($file);
5754
5755 // We check if the file exists
5756 $file = dol_buildpath($reldir.$modelspath.$file, 0);
5757 if (file_exists($file)) {
5758 $filefound = $file;
5759 $classname = $prefix.'_'.$modele;
5760 break;
5761 }
5762 }
5763 if ($filefound) {
5764 break;
5765 }
5766 }
5767
5768 if ($filefound === '' || $classname === '') {
5769 $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
5770 $this->errors[] = $this->error;
5771 dol_syslog($this->error, LOG_ERR);
5772 return -1;
5773 }
5774
5775 // Sanitize $filefound
5776 $filefound = dol_sanitizePathName($filefound);
5777
5778 // If generator was found
5779 global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
5780
5781 require_once $filefound;
5782
5783 $obj = new $classname($this->db);
5784
5785 // If generator is ODT, we must have srctemplatepath defined, if not we set it.
5786 if ($obj->type == 'odt' && empty($srctemplatepath)) {
5787 $varfortemplatedir = $obj->scandir;
5788 if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
5789 $dirtoscan = getDolGlobalString($varfortemplatedir);
5790
5791 $listoffiles = array();
5792
5793 // Now we add first model found in directories scanned
5794 $listofdir = explode(',', $dirtoscan);
5795 foreach ($listofdir as $key => $tmpdir) {
5796 $tmpdir = trim($tmpdir);
5797 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
5798 if (!$tmpdir) {
5799 unset($listofdir[$key]);
5800 continue;
5801 }
5802 if (is_dir($tmpdir)) {
5803 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
5804 if (count($tmpfiles)) {
5805 $listoffiles = array_merge($listoffiles, $tmpfiles);
5806 }
5807 }
5808 }
5809
5810 if (count($listoffiles)) {
5811 foreach ($listoffiles as $record) {
5812 $srctemplatepath = $record['fullname'];
5813 break;
5814 }
5815 }
5816 }
5817
5818 if (empty($srctemplatepath)) {
5819 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
5820 return -1;
5821 }
5822 }
5823
5824 if ($obj->type == 'odt' && !empty($srctemplatepath)) {
5825 if (!dol_is_file($srctemplatepath)) {
5826 dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
5827 $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
5828 return -1;
5829 }
5830 }
5831
5832 // We save charset_output to restore it because write_file can change it if needed for
5833 // output format that does not support UTF8.
5834 $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
5835
5836 // update model_pdf in object
5837 $this->model_pdf = $saved_model;
5838
5839 if (in_array(get_class($this), array('Adherent'))) {
5840 '@phan-var-force Adherent $this';
5841 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards', $moreparams);
5842 } else {
5843 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
5844 }
5845 // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
5846
5847 if ($resultwritefile > 0) {
5848 $outputlangs->charset_output = $sav_charset_output;
5849
5850 // We delete old preview
5851 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5852 dol_delete_preview($this);
5853
5854 // Index file in database
5855 if (!empty($obj->result['fullpath'])) {
5856 $destfull = $obj->result['fullpath'];
5857
5858 // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
5859 $update_main_doc_field = 0;
5860 if (!empty($obj->update_main_doc_field)) {
5861 $update_main_doc_field = 1;
5862 }
5863
5864 // Check that the file exists, before indexing it.
5865 // Hint: It does not exist, if we create a PDF and auto delete the ODT File
5866 if (dol_is_file($destfull)) {
5867 $this->indexFile($destfull, $update_main_doc_field);
5868 }
5869 } else {
5870 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);
5871 }
5872
5873 // Success in building document. We build meta file.
5874 dol_meta_create($this);
5875
5876 return 1;
5877 } else {
5878 $outputlangs->charset_output = $sav_charset_output;
5879 $this->error = $obj->error;
5880 $this->errors = $obj->errors;
5881 dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
5882 return -1;
5883 }
5884 }
5885
5895 public function indexFile($destfull, $update_main_doc_field)
5896 {
5897 global $conf, $user;
5898
5899 $upload_dir = dirname($destfull);
5900 $destfile = basename($destfull);
5901 $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
5902
5903 if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
5904 $filename = basename($destfile);
5905 $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
5906 $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
5907
5908 include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
5909 $ecmfile = new EcmFiles($this->db);
5910 $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
5911
5912 // Set the public "share" key
5913 $setsharekey = false;
5914 if ($this->element == 'propal' || $this->element == 'proposal') {
5915 if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
5916 $setsharekey = true; // feature to make online signature is not set or set to on (default)
5917 }
5918 if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
5919 $setsharekey = true;
5920 }
5921 }
5922 if ($this->element == 'commande' && getDolGlobalInt("ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
5923 $setsharekey = true;
5924 }
5925 if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
5926 $setsharekey = true;
5927 }
5928 if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
5929 $setsharekey = true;
5930 }
5931 if ($this->element == 'product' && getDolGlobalInt("PRODUCT_ALLOW_EXTERNAL_DOWNLOAD")) {
5932 $setsharekey = true;
5933 }
5934 if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
5935 $setsharekey = true;
5936 }
5937 if ($this->element == 'fichinter' && getDolGlobalInt("FICHINTER_ALLOW_EXTERNAL_DOWNLOAD")) {
5938 $setsharekey = true;
5939 }
5940 if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
5941 $setsharekey = true;
5942 }
5943 if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
5944 $setsharekey = true;
5945 }
5946
5947 if ($setsharekey) {
5948 if (empty($ecmfile->share)) { // Because object not found or share not set yet
5949 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
5950 $ecmfile->share = getRandomPassword(true);
5951 }
5952 }
5953
5954 if ($result > 0) {
5955 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5956 $ecmfile->fullpath_orig = '';
5957 $ecmfile->gen_or_uploaded = 'generated';
5958 $ecmfile->description = ''; // indexed content
5959 $ecmfile->keywords = ''; // keyword content
5960 $result = $ecmfile->update($user);
5961 if ($result < 0) {
5962 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5963 return -1;
5964 }
5965 } else {
5966 $ecmfile->entity = $conf->entity;
5967 $ecmfile->filepath = $rel_dir;
5968 $ecmfile->filename = $filename;
5969 $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5970 $ecmfile->fullpath_orig = '';
5971 $ecmfile->gen_or_uploaded = 'generated';
5972 $ecmfile->description = ''; // indexed content
5973 $ecmfile->keywords = ''; // keyword content
5974 $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
5975 $ecmfile->src_object_id = $this->id;
5976
5977 $result = $ecmfile->create($user);
5978 if ($result < 0) {
5979 setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5980 return -1;
5981 }
5982 }
5983
5984 /*$this->result['fullname']=$destfull;
5985 $this->result['filepath']=$ecmfile->filepath;
5986 $this->result['filename']=$ecmfile->filename;*/
5987 //var_dump($obj->update_main_doc_field);exit;
5988
5989 if ($update_main_doc_field && !empty($this->table_element)) {
5990 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
5991 $sql .= " WHERE rowid = ".((int) $this->id);
5992
5993 $resql = $this->db->query($sql);
5994 if (!$resql) {
5995 dol_print_error($this->db);
5996 return -1;
5997 } else {
5998 $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
5999 }
6000 }
6001 }
6002
6003 return 1;
6004 }
6005
6013 public function addThumbs($file)
6014 {
6015 $file_osencoded = dol_osencode($file);
6016
6017 if (file_exists($file_osencoded)) {
6018 require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6019
6020 $tmparraysize = getDefaultImageSizes();
6021 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6022 $maxheightsmall = $tmparraysize['maxheightsmall'];
6023 $maxwidthmini = $tmparraysize['maxwidthmini'];
6024 $maxheightmini = $tmparraysize['maxheightmini'];
6025 //$quality = $tmparraysize['quality'];
6026 $quality = 50; // For thumbs, we force quality to 50
6027
6028 // Create small thumbs for company (Ratio is near 16/9)
6029 // Used on logon for example
6030 vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6031
6032 // Create mini thumbs for company (Ratio is near 16/9)
6033 // Used on menu or for setup page for example
6034 vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6035 }
6036 }
6037
6045 public function delThumbs($file)
6046 {
6047 $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6048 dol_delete_file($imgThumbName);
6049 $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6050 dol_delete_file($imgThumbName);
6051 }
6052
6053
6054 /* Functions common to commonobject and commonobjectline */
6055
6056 /* For default values */
6057
6071 public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6072 {
6073 // If param here has been posted, we use this value first.
6074 if (GETPOSTISSET($fieldname)) {
6075 return GETPOST($fieldname, $type, 3);
6076 }
6077
6078 if (isset($alternatevalue)) {
6079 return $alternatevalue;
6080 }
6081
6082 $newelement = $this->element;
6083 if ($newelement == 'facture') {
6084 $newelement = 'invoice';
6085 }
6086 if ($newelement == 'commande') {
6087 $newelement = 'order';
6088 }
6089 if (empty($newelement)) {
6090 dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6091 return '';
6092 }
6093
6094 $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6095 //var_dump($keyforfieldname);
6096 if (getDolGlobalString($keyforfieldname)) {
6097 return getDolGlobalString($keyforfieldname);
6098 }
6099
6100 // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6101 // store content into $conf->cache['overwrite_default']
6102
6103 return '';
6104 }
6105
6106
6107 /* For triggers */
6108
6109
6110 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6121 public function call_trigger($triggerName, $user)
6122 {
6123 // phpcs:enable
6124 global $langs, $conf;
6125
6126 if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
6127 dol_print_error(null, 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
6128 exit;
6129 }
6130 if (!is_object($langs)) { // If lang was not defined, we set it. It is required by run_triggers().
6131 include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
6132 $langs = new Translate('', $conf);
6133 }
6134
6135 include_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
6136 $interface = new Interfaces($this->db);
6137 $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
6138
6139 if ($result < 0) {
6140 if (!empty($this->errors)) {
6141 $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.
6142 } else {
6143 $this->errors = $interface->errors;
6144 }
6145 }
6146 return $result;
6147 }
6148
6149
6150 /* Functions for data in other language */
6151
6152
6161 {
6162 // To avoid SQL errors. Probably not the better solution though
6163 if (!$this->element) {
6164 return 0;
6165 }
6166 if (!($this->id > 0)) {
6167 return 0;
6168 }
6169 if (is_array($this->array_languages)) {
6170 return 1;
6171 }
6172
6173 $this->array_languages = array();
6174
6175 $element = $this->element;
6176 if ($element == 'categorie') {
6177 $element = 'categories'; // For compatibility
6178 }
6179
6180 // Request to get translation values for object
6181 $sql = "SELECT rowid, property, lang , value";
6182 $sql .= " FROM ".$this->db->prefix()."object_lang";
6183 $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6184 $sql .= " AND fk_object = ".((int) $this->id);
6185
6186 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6187 $resql = $this->db->query($sql);
6188 if ($resql) {
6189 $numrows = $this->db->num_rows($resql);
6190 if ($numrows) {
6191 $i = 0;
6192 while ($i < $numrows) {
6193 $obj = $this->db->fetch_object($resql);
6194 $key = $obj->property;
6195 $value = $obj->value;
6196 $codelang = $obj->lang;
6197 $type = $this->fields[$key]['type'];
6198
6199 // we can add this attribute to object
6200 if (preg_match('/date/', $type)) {
6201 $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6202 } else {
6203 $this->array_languages[$key][$codelang] = $value;
6204 }
6205
6206 $i++;
6207 }
6208 }
6209
6210 $this->db->free($resql);
6211
6212 if ($numrows) {
6213 return $numrows;
6214 } else {
6215 return 0;
6216 }
6217 } else {
6218 dol_print_error($this->db);
6219 return -1;
6220 }
6221 }
6222
6229 public function setValuesForExtraLanguages($onlykey = '')
6230 {
6231 // Get extra fields
6232 foreach ($_POST as $postfieldkey => $postfieldvalue) {
6233 $tmparray = explode('-', $postfieldkey);
6234 if ($tmparray[0] != 'field') {
6235 continue;
6236 }
6237
6238 $element = $tmparray[1];
6239 $key = $tmparray[2];
6240 $codelang = $tmparray[3];
6241 //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6242
6243 if (!empty($onlykey) && $key != $onlykey) {
6244 continue;
6245 }
6246 if ($element != $this->element) {
6247 continue;
6248 }
6249
6250 $key_type = $this->fields[$key]['type'];
6251
6252 $enabled = 1;
6253 if (isset($this->fields[$key]['enabled'])) {
6254 $enabled = (int) dol_eval($this->fields[$key]['enabled'], 1, 1, '1');
6255 }
6256 /*$perms = 1;
6257 if (isset($this->fields[$key]['perms']))
6258 {
6259 $perms = (int) dol_eval($this->fields[$key]['perms'], 1, 1, '1');
6260 }*/
6261 if (empty($enabled)) {
6262 continue;
6263 }
6264 //if (empty($perms)) continue;
6265
6266 if (in_array($key_type, array('date'))) {
6267 // Clean parameters
6268 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6269 $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6270 } elseif (in_array($key_type, array('datetime'))) {
6271 // Clean parameters
6272 // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6273 $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6274 } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6275 $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6276 if (!empty($value_arr)) {
6277 $value_key = implode(',', $value_arr);
6278 } else {
6279 $value_key = '';
6280 }
6281 } elseif (in_array($key_type, array('price', 'double'))) {
6282 $value_arr = GETPOST($postfieldkey, 'alpha');
6283 $value_key = price2num($value_arr);
6284 } else {
6285 $value_key = GETPOST($postfieldkey);
6286 if (in_array($key_type, array('link')) && $value_key == '-1') {
6287 $value_key = '';
6288 }
6289 }
6290
6291 $this->array_languages[$key][$codelang] = $value_key;
6292
6293 /*if ($nofillrequired) {
6294 $langs->load('errors');
6295 setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6296 return -1;
6297 }*/
6298 }
6299
6300 return 1;
6301 }
6302
6303
6304 /* Functions for extrafields */
6305
6312 public function fetchNoCompute($id)
6313 {
6314 global $conf;
6315
6316 $savDisableCompute = $conf->disable_compute;
6317 $conf->disable_compute = 1;
6318
6319 $ret = $this->fetch($id); /* @phpstan-ignore-line */
6320
6321 $conf->disable_compute = $savDisableCompute;
6322
6323 return $ret;
6324 }
6325
6326 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6336 public function fetch_optionals($rowid = null, $optionsArray = null)
6337 {
6338 // phpcs:enable
6339 global $conf, $extrafields;
6340
6341 if (empty($rowid)) {
6342 $rowid = $this->id;
6343 }
6344 if (empty($rowid) && isset($this->rowid)) {
6345 $rowid = $this->rowid; // deprecated
6346 }
6347
6348 // To avoid SQL errors. Probably not the better solution though
6349 if (!$this->table_element) {
6350 return 0;
6351 }
6352
6353 $this->array_options = array();
6354
6355 if (!is_array($optionsArray)) {
6356 // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6357 if (!isset($extrafields) || !is_object($extrafields)) {
6358 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6359 $extrafields = new ExtraFields($this->db);
6360 }
6361
6362 // Load array of extrafields for elementype = $this->table_element
6363 if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6364 $extrafields->fetch_name_optionals_label($this->table_element);
6365 }
6366 $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6367 } else {
6368 global $extrafields;
6369 dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6370 }
6371
6372 $table_element = $this->table_element;
6373 if ($table_element == 'categorie') {
6374 $table_element = 'categories'; // For compatibility
6375 }
6376
6377 // Request to get complementary values
6378 if (is_array($optionsArray) && count($optionsArray) > 0) {
6379 $sql = "SELECT rowid";
6380 foreach ($optionsArray as $name => $label) {
6381 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6382 $sql .= ", ".$name;
6383 }
6384 // use geo sql fonction to read as text
6385 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'point') {
6386 $sql .= ", ST_AsWKT(".$name.") as ".$name;
6387 }
6388 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'multipts') {
6389 $sql .= ", ST_AsWKT(".$name.") as ".$name;
6390 }
6391 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'linestrg') {
6392 $sql .= ", ST_AsWKT(".$name.") as ".$name;
6393 }
6394 if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'polygon') {
6395 $sql .= ", ST_AsWKT(".$name.") as ".$name;
6396 }
6397 }
6398 $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6399 $sql .= " WHERE fk_object = ".((int) $rowid);
6400
6401 //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6402 $resql = $this->db->query($sql);
6403 if ($resql) {
6404 $numrows = $this->db->num_rows($resql);
6405 if ($numrows) {
6406 $tab = $this->db->fetch_array($resql);
6407
6408 foreach ($tab as $key => $value) {
6409 // 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)
6410 if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6411 // we can add this attribute to object
6412 if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6413 //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6414 $this->array_options["options_".$key] = $this->db->jdate($value);
6415 } else {
6416 $this->array_options["options_".$key] = $value;
6417 }
6418
6419 //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6420 }
6421 if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6422 if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6423 $this->array_options["options_".$key] = dolDecrypt($value);
6424 }
6425 }
6426 }
6427 } else {
6432 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6433 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6434 $this->array_options['options_' . $key] = null;
6435 }
6436 }
6437 }
6438
6439 // If field is a computed field, value must become result of compute (regardless of whether a row exists
6440 // in the element's extrafields table)
6441 if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6442 foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6443 if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6444 //var_dump($conf->disable_compute);
6445 if (empty($conf->disable_compute)) {
6446 global $objectoffield; // We set a global variable to $objectoffield so
6447 $objectoffield = $this; // we can use it inside computed formula
6448 $this->array_options['options_' . $key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6449 }
6450 }
6451 }
6452 }
6453
6454 $this->db->free($resql);
6455
6456 if ($numrows) {
6457 return $numrows;
6458 } else {
6459 return 0;
6460 }
6461 } else {
6462 $this->errors[] = $this->db->lasterror;
6463 return -1;
6464 }
6465 }
6466 return 0;
6467 }
6468
6475 public function deleteExtraFields()
6476 {
6477 global $conf;
6478
6479 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6480 return 0;
6481 }
6482
6483 $this->db->begin();
6484
6485 $table_element = $this->table_element;
6486 if ($table_element == 'categorie') {
6487 $table_element = 'categories'; // For compatibility
6488 }
6489
6490 dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6491
6492 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6493
6494 $resql = $this->db->query($sql_del);
6495 if (!$resql) {
6496 $this->error = $this->db->lasterror();
6497 $this->db->rollback();
6498 return -1;
6499 } else {
6500 $this->db->commit();
6501 return 1;
6502 }
6503 }
6504
6515 public function insertExtraFields($trigger = '', $userused = null)
6516 {
6517 global $conf, $langs, $user;
6518
6519 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6520 return 0;
6521 }
6522
6523 if (empty($userused)) {
6524 $userused = $user;
6525 }
6526
6527 $error = 0;
6528
6529 if (!empty($this->array_options)) {
6530 // Check parameters
6531 $langs->load('admin');
6532 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6533 $extrafields = new ExtraFields($this->db);
6534 $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6535
6536 // Eliminate copied source object extra fields that do not exist in target object
6537 $new_array_options = array();
6538 foreach ($this->array_options as $key => $value) {
6539 if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6540 $new_array_options[$key] = $value;
6541 } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6542 $new_array_options['options_'.$key] = $value;
6543 }
6544 }
6545
6546 foreach ($new_array_options as $key => $value) {
6547 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6548 $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6549 $attributeLabel = $langs->transnoentities($extrafields->attributes[$this->table_element]['label'][$attributeKey]);
6550 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6551 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6552 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6553 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6554
6555 // If we clone, we have to clean unique extrafields to prevent duplicates.
6556 // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6557 if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && !empty($attributeUnique)) {
6558 $new_array_options[$key] = null;
6559 }
6560
6561 // If we create product combination, we have to clean unique extrafields to prevent duplicates.
6562 // This behaviour can be prevented by external code by changing $this->context['createproductcombination'] value in hook
6563 if (!empty($this->context['createproductcombination']) && $this->context['createproductcombination'] == 'createproductcombination' && !empty($attributeUnique)) {
6564 $new_array_options[$key] = null;
6565 }
6566
6567 // Similar code than into insertExtraFields
6568 if ($attributeRequired) {
6569 $v = $this->array_options[$key];
6570 if (ExtraFields::isEmptyValue($v, $attributeType)) {
6571 $langs->load("errors");
6572 dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6573 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6574 return -1;
6575 }
6576 }
6577
6578 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6579 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6580
6581 if (!empty($attrfieldcomputed)) {
6582 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6583 $value = dol_eval($attrfieldcomputed, 1, 0, '2');
6584 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
6585 $new_array_options[$key] = $value;
6586 } else {
6587 $new_array_options[$key] = null;
6588 }
6589 }
6590
6591 switch ($attributeType) {
6592 case 'int':
6593 if (!is_numeric($value) && $value != '') {
6594 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6595 return -1;
6596 } elseif ($value == '') {
6597 $new_array_options[$key] = null;
6598 }
6599 break;
6600 case 'price':
6601 case 'double':
6602 $value = price2num($value);
6603 if (!is_numeric($value) && $value != '') {
6604 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6605 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6606 return -1;
6607 } elseif ($value == '') {
6608 $value = null;
6609 }
6610 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6611 $new_array_options[$key] = $value;
6612 break;
6613 /*case 'select': // Not required, we chose value='0' for undefined values
6614 if ($value=='-1')
6615 {
6616 $this->array_options[$key] = null;
6617 }
6618 break;*/
6619 case 'password':
6620 $algo = '';
6621 if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6622 // If there is an encryption choice, we use it to encrypt data before insert
6623 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6624 $algo = reset($tmparrays);
6625 if ($algo != '') {
6626 //global $action; // $action may be 'create', 'update', 'update_extras'...
6627 //var_dump($action);
6628 //var_dump($this->oldcopy);exit;
6629 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
6630 //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6631 if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6632 // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6633 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6634 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6635 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6636 } else {
6637 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6638 }
6639 } else {
6640 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6641 }
6642 } else {
6643 // If value has changed
6644 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6645 if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6646 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6647 } else {
6648 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6649 }
6650 } else {
6651 $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
6652 }
6653 }
6654 } else {
6655 //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6656 // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6657 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
6658 $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6659 } else {
6660 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6661 }
6662 }
6663 } else {
6664 // No encryption
6665 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6666 }
6667 } else { // Common usage
6668 $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6669 }
6670 break;
6671 case 'date':
6672 case 'datetime':
6673 // If data is a string instead of a timestamp, we convert it
6674 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6675 $this->array_options[$key] = strtotime($this->array_options[$key]);
6676 }
6677 $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6678 break;
6679 case 'datetimegmt':
6680 // If data is a string instead of a timestamp, we convert it
6681 if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6682 $this->array_options[$key] = strtotime($this->array_options[$key]);
6683 }
6684 $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
6685 break;
6686 case 'link':
6687 $param_list = array_keys($attributeParam['options']);
6688 // 0 : ObjectName
6689 // 1 : classPath
6690 $InfoFieldList = explode(":", $param_list[0]);
6691 dol_include_once($InfoFieldList[1]);
6692 if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
6693 if ($value == '-1') { // -1 is key for no defined in combo list of objects
6694 $new_array_options[$key] = '';
6695 } elseif ($value) {
6696 $object = new $InfoFieldList[0]($this->db);
6697 if (is_numeric($value)) {
6698 $res = $object->fetch($value); // Common case
6699 } else {
6700 $res = $object->fetch('', $value); // For compatibility
6701 }
6702
6703 if ($res > 0) {
6704 $new_array_options[$key] = $object->id;
6705 } else {
6706 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6707 return -1;
6708 }
6709 }
6710 } else {
6711 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6712 }
6713 break;
6714 case 'checkbox':
6715 case 'chkbxlst':
6716 if (is_array($this->array_options[$key])) {
6717 $new_array_options[$key] = implode(',', $this->array_options[$key]);
6718 } else {
6719 $new_array_options[$key] = $this->array_options[$key];
6720 }
6721 break;
6722 }
6723 }
6724
6725 $this->db->begin();
6726
6727 $table_element = $this->table_element;
6728 if ($table_element == 'categorie') {
6729 $table_element = 'categories'; // For compatibility
6730 }
6731
6732 dol_syslog(get_class($this)."::insertExtraFields delete then insert", LOG_DEBUG);
6733
6734 $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6735 $this->db->query($sql_del);
6736
6737 $sql = "INSERT INTO ".$this->db->prefix().$table_element."_extrafields (fk_object";
6738 foreach ($new_array_options as $key => $value) {
6739 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6740 // Add field of attribute
6741 if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator
6742 $sql .= ",".$attributeKey;
6743 }
6744 }
6745 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6746 if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6747 foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6748 if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) { // If field not already added previously
6749 $sql .= ",".$tmpkey;
6750 }
6751 }
6752 }
6753 $sql .= ") VALUES (".$this->id;
6754
6755 foreach ($new_array_options as $key => $value) {
6756 $attributeKey = substr($key, 8); // Remove 'options_' prefix
6757 // Add field of attribute
6758 if (!in_array($extrafields->attributes[$this->table_element]['type'][$attributeKey], ['separate', 'point', 'multipts', 'linestrg', 'polygon'])) { // Only for other type than separator)
6759 if ($new_array_options[$key] != '' || $new_array_options[$key] == '0') {
6760 $sql .= ",'".$this->db->escape($new_array_options[$key])."'";
6761 } else {
6762 $sql .= ",null";
6763 }
6764 }
6765 if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'point') { // for point type
6766 if (!empty($new_array_options[$key])) {
6767 $sql .= ",ST_PointFromText('".$this->db->escape($new_array_options[$key])."')";
6768 } else {
6769 $sql .= ",null";
6770 }
6771 }
6772 if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'multipts') { // for point type
6773 if (!empty($new_array_options[$key])) {
6774 $sql .= ",ST_MultiPointFromText('".$this->db->escape($new_array_options[$key])."')";
6775 } else {
6776 $sql .= ",null";
6777 }
6778 }
6779 if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'linestrg') { // for linestring type
6780 if (!empty($new_array_options[$key])) {
6781 $sql .= ",ST_LineFromText('".$this->db->escape($new_array_options[$key])."')";
6782 } else {
6783 $sql .= ",null";
6784 }
6785 }
6786 if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'polygon') { // for polygon type
6787 if (!empty($new_array_options[$key])) {
6788 $sql .= ",ST_PolyFromText('".$this->db->escape($new_array_options[$key])."')";
6789 } else {
6790 $sql .= ",null";
6791 }
6792 }
6793 }
6794 // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6795 if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6796 foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6797 if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) { // If field not already added previously
6798 if (in_array($tmpval, array('int', 'double', 'price'))) {
6799 $sql .= ", 0";
6800 } else {
6801 $sql .= ", ''";
6802 }
6803 }
6804 }
6805 }
6806
6807 $sql .= ")";
6808
6809 $resql = $this->db->query($sql);
6810 if (!$resql) {
6811 $this->error = $this->db->lasterror();
6812 $error++;
6813 }
6814
6815 if (!$error && $trigger) {
6816 // Call trigger
6817 $this->context = array('extrafieldaddupdate' => 1);
6818 $result = $this->call_trigger($trigger, $userused);
6819 if ($result < 0) {
6820 $error++;
6821 }
6822 // End call trigger
6823 }
6824
6825 if ($error) {
6826 $this->db->rollback();
6827 return -1;
6828 } else {
6829 $this->db->commit();
6830 return 1;
6831 }
6832 } else {
6833 return 0;
6834 }
6835 }
6836
6847 public function insertExtraLanguages($trigger = '', $userused = null)
6848 {
6849 global $conf, $langs, $user;
6850
6851 if (empty($userused)) {
6852 $userused = $user;
6853 }
6854
6855 $error = 0;
6856
6857 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
6858 return 0; // For avoid conflicts if trigger used
6859 }
6860
6861 if (is_array($this->array_languages)) {
6862 $new_array_languages = $this->array_languages;
6863
6864 foreach ($new_array_languages as $key => $value) {
6865 $attributeKey = $key;
6866 $attributeType = $this->fields[$attributeKey]['type'];
6867 $attributeLabel = $this->fields[$attributeKey]['label'];
6868
6869 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6870 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6871
6872 switch ($attributeType) {
6873 case 'int':
6874 if (is_array($value) || (!is_numeric($value) && $value != '')) {
6875 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6876 return -1;
6877 } elseif ($value == '') {
6878 $new_array_languages[$key] = null;
6879 }
6880 break;
6881 case 'double':
6882 $value = price2num($value);
6883 if (!is_numeric($value) && $value != '') {
6884 dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6885 $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6886 return -1;
6887 } elseif ($value == '') {
6888 $new_array_languages[$key] = null;
6889 } else {
6890 $new_array_languages[$key] = $value;
6891 }
6892 break;
6893 /*case 'select': // Not required, we chose value='0' for undefined values
6894 if ($value=='-1')
6895 {
6896 $this->array_options[$key] = null;
6897 }
6898 break;*/
6899 }
6900 }
6901
6902 $this->db->begin();
6903
6904 $table_element = $this->table_element;
6905 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
6906 $table_element = 'categories'; // For compatibility
6907 }
6908
6909 dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
6910
6911 foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
6912 foreach ($langcodearray as $langcode => $value) {
6913 $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
6914 $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
6915 $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
6916 $this->db->query($sql_del);
6917
6918 if ($value !== '') {
6919 $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
6920 $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
6921 $sql .= ")";
6922
6923 $resql = $this->db->query($sql);
6924 if (!$resql) {
6925 $this->error = $this->db->lasterror();
6926 $error++;
6927 break;
6928 }
6929 }
6930 }
6931 }
6932
6933 if (!$error && $trigger) {
6934 // Call trigger
6935 $this->context = array('extralanguagesaddupdate' => 1);
6936 $result = $this->call_trigger($trigger, $userused);
6937 if ($result < 0) {
6938 $error++;
6939 }
6940 // End call trigger
6941 }
6942
6943 if ($error) {
6944 $this->db->rollback();
6945 return -1;
6946 } else {
6947 $this->db->commit();
6948 return 1;
6949 }
6950 } else {
6951 return 0;
6952 }
6953 }
6954
6965 public function updateExtraField($key, $trigger = null, $userused = null)
6966 {
6967 global $conf, $langs, $user, $hookmanager;
6968
6969 if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6970 return 0;
6971 }
6972
6973 if (empty($userused)) {
6974 $userused = $user;
6975 }
6976
6977 $error = 0;
6978
6979 if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
6980 // Check parameters
6981 $langs->load('admin');
6982 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6983 $extrafields = new ExtraFields($this->db);
6984 $extrafields->fetch_name_optionals_label($this->table_element);
6985
6986 $value = $this->array_options["options_".$key];
6987
6988 $attributeKey = $key;
6989 $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
6990 $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
6991 $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
6992 $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
6993 $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6994 $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
6995
6996 // Similar code than into insertExtraFields
6997 if ($attributeRequired) {
6998 $mandatorypb = false;
6999 if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
7000 $mandatorypb = true;
7001 }
7002 if ($this->array_options["options_".$key] === '') {
7003 $mandatorypb = true;
7004 }
7005 if ($mandatorypb) {
7006 $langs->load("errors");
7007 dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
7008 $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7009 return -1;
7010 }
7011 }
7012
7013 // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7014 $new_array_options = $this->array_options;
7015
7016 //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7017 //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7018 if (!empty($attrfieldcomputed)) {
7019 if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7020 $value = dol_eval($attrfieldcomputed, 1, 0, '2');
7021 dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7022
7023 $new_array_options["options_".$key] = $value;
7024
7025 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7026 } else {
7027 $new_array_options["options_".$key] = null;
7028
7029 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7030 }
7031 }
7032
7033 switch ($attributeType) {
7034 case 'int':
7035 if (!is_numeric($value) && $value != '') {
7036 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7037 return -1;
7038 } elseif ($value === '') {
7039 $new_array_options["options_".$key] = null;
7040
7041 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7042 }
7043 break;
7044 case 'price':
7045 case 'double':
7046 $value = price2num($value);
7047 if (!is_numeric($value) && $value != '') {
7048 dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7049 $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7050 return -1;
7051 } elseif ($value === '') {
7052 $value = null;
7053 }
7054 //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7055 $new_array_options["options_".$key] = $value;
7056
7057 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7058 break;
7059 /*case 'select': // Not required, we chose value='0' for undefined values
7060 if ($value=='-1')
7061 {
7062 $new_array_options["options_".$key] = $value;
7063
7064 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7065 }
7066 break;*/
7067 case 'password':
7068 $algo = '';
7069 if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7070 // If there is an encryption choice, we use it to encrypt data before insert
7071 $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7072 $algo = reset($tmparrays);
7073 if ($algo != '') {
7074 //global $action; // $action may be 'create', 'update', 'update_extras'...
7075 //var_dump($action);
7076 //var_dump($this->oldcopy);exit;
7077 //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7078 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
7079 //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7080 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.
7081 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7082 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7083 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7084 } else {
7085 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7086 }
7087 } else {
7088 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7089 }
7090 } else {
7091 if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7092 if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7093 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7094 } else {
7095 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7096 }
7097 } else {
7098 $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7099 }
7100 }
7101 } else {
7102 if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7103 $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7104 } else {
7105 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7106 }
7107 }
7108 } else {
7109 // No encryption
7110 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7111 }
7112 } else { // Common usage
7113 $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7114 }
7115
7116 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7117 break;
7118 case 'date':
7119 case 'datetime':
7120 if (empty($this->array_options["options_".$key])) {
7121 $new_array_options["options_".$key] = null;
7122
7123 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7124 } else {
7125 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7126 }
7127 break;
7128 case 'datetimegmt':
7129 if (empty($this->array_options["options_".$key])) {
7130 $new_array_options["options_".$key] = null;
7131
7132 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7133 } else {
7134 $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7135 }
7136 break;
7137 case 'boolean':
7138 if (empty($this->array_options["options_".$key])) {
7139 $new_array_options["options_".$key] = null;
7140
7141 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7142 }
7143 break;
7144 case 'link':
7145 if ($this->array_options["options_".$key] === '') {
7146 $new_array_options["options_".$key] = null;
7147
7148 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7149 }
7150 break;
7151 /*
7152 case 'link':
7153 $param_list = array_keys($attributeParam['options']);
7154 // 0 : ObjectName
7155 // 1 : classPath
7156 $InfoFieldList = explode(":", $param_list[0]);
7157 dol_include_once($InfoFieldList[1]);
7158 if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7159 {
7160 if ($value == '-1') // -1 is key for no defined in combo list of objects
7161 {
7162 $new_array_options[$key] = '';
7163 } elseif ($value) {
7164 $object = new $InfoFieldList[0]($this->db);
7165 if (is_numeric($value)) $res = $object->fetch($value); // Common case
7166 else $res = $object->fetch('', $value); // For compatibility
7167
7168 if ($res > 0) $new_array_options[$key] = $object->id;
7169 else {
7170 $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7171 $this->db->rollback();
7172 return -1;
7173 }
7174 }
7175 } else {
7176 dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7177 }
7178 break;
7179 */
7180 case 'checkbox':
7181 case 'chkbxlst':
7182 $new_array_options = array();
7183 if (is_array($this->array_options["options_".$key])) {
7184 $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7185 } else {
7186 $new_array_options["options_".$key] = $this->array_options["options_".$key];
7187 }
7188
7189 $this->array_options["options_".$key] = $new_array_options["options_".$key];
7190 break;
7191 }
7192
7193 $this->db->begin();
7194
7195 $linealreadyfound = 0;
7196
7197 // 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)
7198 $table_element = $this->table_element;
7199 if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7200 $table_element = 'categories'; // For compatibility
7201 }
7202
7203 $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7204 $resql = $this->db->query($sql);
7205 if ($resql) {
7206 $tmpobj = $this->db->fetch_object($resql);
7207 if ($tmpobj) {
7208 $linealreadyfound = $tmpobj->nb;
7209 }
7210 }
7211
7212 //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7213 if ($linealreadyfound) {
7214 if ($this->array_options["options_".$key] === null) {
7215 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = null";
7216 } else {
7217 $sql = "UPDATE ".$this->db->prefix().$table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7218 }
7219 $sql .= " WHERE fk_object = ".((int) $this->id);
7220
7221 $resql = $this->db->query($sql);
7222 if (!$resql) {
7223 $error++;
7224 $this->error = $this->db->lasterror();
7225 }
7226 } else {
7227 $result = $this->insertExtraFields('', $user);
7228 if ($result < 0) {
7229 $error++;
7230 }
7231 }
7232
7233 if (!$error) {
7234 $parameters = array('key' => $key);
7235 global $action;
7236 $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7237 if ($reshook < 0) {
7238 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7239 }
7240 }
7241
7242 if (!$error && $trigger) {
7243 // Call trigger
7244 $this->context = array('extrafieldupdate' => 1);
7245 $result = $this->call_trigger($trigger, $userused);
7246 if ($result < 0) {
7247 $error++;
7248 }
7249 // End call trigger
7250 }
7251
7252 if ($error) {
7253 dol_syslog(__METHOD__.$this->error, LOG_ERR);
7254 $this->db->rollback();
7255 return -1;
7256 } else {
7257 $this->db->commit();
7258 return 1;
7259 }
7260 } else {
7261 return 0;
7262 }
7263 }
7264
7271 public function getExtraField($key)
7272 {
7273 return $this->array_options['options_'.$key] ?? null;
7274 }
7275
7283 public function setExtraField($key, $value)
7284 {
7285 $this->array_options['options_'.$key] = $value;
7286 }
7287
7298 public function updateExtraLanguages($key, $trigger = null, $userused = null)
7299 {
7300 global $conf, $langs, $user;
7301
7302 if (empty($userused)) {
7303 $userused = $user;
7304 }
7305
7306 $error = 0;
7307
7308 if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7309 return 0; // For avoid conflicts if trigger used
7310 }
7311
7312 return 0;
7313 }
7314
7315
7331 public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7332 {
7333 global $conf, $langs, $form;
7334
7335 if (!is_object($form)) {
7336 require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7337 $form = new Form($this->db);
7338 }
7339
7340 if (!empty($this->fields)) {
7341 $val = $this->fields[$key];
7342 }
7343
7344 // Validation tests and output
7345 $fieldValidationErrorMsg = '';
7346 $validationClass = '';
7347 $fieldValidationErrorMsg = $this->getFieldError($key);
7348 if (!empty($fieldValidationErrorMsg)) {
7349 $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
7350 } else {
7351 $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
7352 }
7353
7354 $valuemultiselectinput = array();
7355 $out = '';
7356 $type = '';
7357 $isDependList = 0;
7358 $param = array();
7359 $param['options'] = array();
7360 $reg = array();
7361 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7362 $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7363 // Because we work on extrafields
7364 if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7365 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7366 $type = 'link';
7367 } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7368 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7369 $type = 'link';
7370 } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7371 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7372 $type = 'link';
7373 } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7374 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7375 $type = 'sellist';
7376 } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7377 $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7378 $type = 'sellist';
7379 } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7380 $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7381 $type = 'sellist';
7382 } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7383 $param['options'] = array($reg[1] => 'N');
7384 $type = 'chkbxlst';
7385 } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7386 $param['options'] = array();
7387 $type = 'varchar';
7388 $size = $reg[1];
7389 } elseif (preg_match('/varchar/', $val['type'])) {
7390 $param['options'] = array();
7391 $type = 'varchar';
7392 } else {
7393 $param['options'] = array();
7394 $type = $this->fields[$key]['type'];
7395 }
7396 //var_dump($type); var_dump($param['options']);
7397
7398 // Special case that force options and type ($type can be integer, varchar, ...)
7399 if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7400 $param['options'] = $this->fields[$key]['arrayofkeyval'];
7401 // Special case that prevent to force $type to have multiple input
7402 if (empty($this->fields[$key]['multiinput'])) {
7403 $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7404 }
7405 }
7406
7407 $label = $this->fields[$key]['label'];
7408 //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7409 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7410 $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7411 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7412 $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7413 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7414 $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7415 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7416 $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7417 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7418 $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7419
7420 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7421 $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7422 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7423 $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7424 $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7425
7426 $objectid = $this->id;
7427
7428 if ($computed) {
7429 if (!preg_match('/^search_/', $keyprefix)) {
7430 return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7431 } else {
7432 return '';
7433 }
7434 }
7435
7436 // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7437 if (empty($morecss) && !empty($val['css'])) {
7438 $morecss = $val['css'];
7439 } elseif (empty($morecss)) {
7440 if ($type == 'date') {
7441 $morecss = 'minwidth100imp';
7442 } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7443 $morecss = 'minwidth200imp';
7444 } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7445 $morecss = 'maxwidth75';
7446 } elseif ($type == 'url') {
7447 $morecss = 'minwidth400';
7448 } elseif ($type == 'boolean') {
7449 $morecss = '';
7450 } else {
7451 if (is_numeric($size) && round((float) $size) < 12) {
7452 $morecss = 'minwidth100';
7453 } elseif (is_numeric($size) && round((float) $size) <= 48) {
7454 $morecss = 'minwidth200';
7455 } else {
7456 $morecss = 'minwidth400';
7457 }
7458 }
7459 }
7460
7461 // Add validation state class
7462 if (!empty($validationClass)) {
7463 $morecss .= $validationClass;
7464 }
7465
7466 if (in_array($type, array('date'))) {
7467 $tmp = explode(',', $size);
7468 $newsize = $tmp[0];
7469 $showtime = 0;
7470
7471 // Do not show current date when field not required (see selectDate() method)
7472 if (!$required && $value == '') {
7473 $value = '-1';
7474 }
7475
7476 // TODO Must also support $moreparam
7477 $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $show