dolibarr 25.0.0-alpha
blockedlog.class.php
1<?php
2/* Copyright (C) 2017 ATM Consulting <contact@atm-consulting.fr>
3 * Copyright (C) 2017-2020 Laurent Destailleur <eldy@destailleur.fr>
4 * Copyright (C) 2022 charlene benke <charlene@patas-monkey.com>
5 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 *
21 * See https://medium.com/@lhartikk/a-blockchain-in-200-lines-of-code-963cc1cc0e54
22 */
23
24include_once DOL_DOCUMENT_ROOT.'/blockedlog/versionmod.inc.php';
25include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
26include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
27
28
33{
37 public $db;
38
43 public $id;
44
49 public $entity;
50
55 public $picto = 'blockedlog';
56
60 public $error = '';
61
65 public $errors = array();
66
71 public $signature = '';
72
76 public $amounts = null;
77
81 public $amounts_taxexcl = null;
82
87 public $action = '';
88
92 public $module_source = '';
93
97 public $pos_source = '';
98
102 public $linktype = '';
103
107 public $linktoref = '';
108
113 public $element = '';
114
119 public $fk_object = 0;
120
125 public $certified = false;
126
131 public $fk_user = 0;
132
136 public $date_creation;
137
141 public $date_modification;
142
146 public $date_object = 0;
147
151 public $ref_object = '';
152
156 public $type_code = '';
157
161 public $object_data = null;
162
166 public $object_version = '';
167
171 public $object_format = '';
172
176 public $user_fullname = '';
177
181 public $debuginfo;
182
186 public $note;
187
192 public $trackedevents = array();
193
198 public $controlledevents = array();
199
204 public $trackedmodules = array();
205
206
207
213 public function __construct(DoliDB $db)
214 {
215 global $conf;
216
217 $this->db = $db;
218 $this->entity = $conf->entity;
219 }
220
221
227 public function loadTrackedEvents()
228 {
229 global $langs;
230
231 $this->controlledevents = array();
232 $this->trackedevents = array();
233 $this->trackedmodules = array();
234
235 $sep = 0;
236
237 $this->controlledevents['BILL_MODIFY'] = array('id' => 'BILL_MODIFY', 'label' => 'logBILL_MODIFY');
238
239 $this->trackedmodules[0] = 'None';
240 if (isModEnabled('takepos')) {
241 $this->trackedmodules['takepos'] = 'TakePOS';
242 }
243
244 // Customer Invoice/Facture / Payment (For most VAT antifraud laws)
245 if (isModEnabled('invoice')) {
246 $sep++;
247 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Invoices").' | '.$langs->trans("Payments").'</span>', 'disabled' => 1);
248
249 $this->trackedevents['BILL_VALIDATE'] = array('id' => 'BILL_VALIDATE', 'label' => 'logBILL_VALIDATE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_VALIDATE'));
250 //$this->trackedevents['BILL_UPDATE'] = array('id' => 'BILL_VALIDATE', 'label' => 'logBILL_UPDATE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_UPDATE'));
251 $this->trackedevents['BILL_SENTBYMAIL'] = array('id' => 'BILL_SENTBYMAIL', 'label' => 'logBILL_SENTBYMAIL', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_SENTBYMAIL'));
252 $this->trackedevents['DOC_DOWNLOAD'] = array('id' => 'DOC_DOWNLOAD', 'label' => 'BlockedLogBillDownload', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('BlockedLogBillDownload'));
253 $this->trackedevents['DOC_PREVIEW'] = array('id' => 'DOC_PREVIEW', 'label' => 'BlockedLogBillPreview', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('BlockedLogBillPreview'));
254 $this->trackedevents['PAYMENT_CUSTOMER_CREATE'] = array('id' => 'PAYMENT_CUSTOMER_CREATE', 'label' => 'logPAYMENT_CUSTOMER_CREATE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logPAYMENT_CUSTOMER_CREATE'));
255 $this->trackedevents['PAYMENT_CUSTOMER_DELETE'] = array('id' => 'PAYMENT_CUSTOMER_DELETE', 'label' => 'logPAYMENT_CUSTOMER_DELETE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logPAYMENT_CUSTOMER_DELETE'));
256 }
257
258 /* Supplier
259 // Supplier Invoice / Payment
260 if (isModEnabled("fournisseur")) {
261 $this->trackedevents['BILL_SUPPLIER_VALIDATE']='BlockedLogSupplierBillValidate';
262 $this->trackedevents['BILL_SUPPLIER_DELETE']='BlockedLogSupplierBillDelete';
263 $this->trackedevents['BILL_SUPPLIER_SENTBYMAIL']='BlockedLogSupplierBillSentByEmail'; // Trigger key does not exists, we want just into array to list it as done
264 $this->trackedevents['SUPPLIER_DOC_DOWNLOAD']='BlockedLogSupplierBillDownload'; // Trigger key does not exists, we want just into array to list it as done
265 $this->trackedevents['SUPPLIER_DOC_PREVIEW']='BlockedLogSupplierBillPreview'; // Trigger key does not exists, we want just into array to list it as done
266 $this->trackedevents['PAYMENT_SUPPLIER_CREATE']='BlockedLogSupplierBillPaymentCreate';
267 $this->trackedevents['PAYMENT_SUPPLIER_DELETE']='BlockedLogsupplierBillPaymentCreate';
268 }
269 */
270
271 // Donation
272 if (isModEnabled('don') && getDolGlobalString('BLOCKEDLOG_ENABLE_DONATION')) { // For countries that need unalterable logs for donations
273 if (!empty($this->trackedevents)) {
274 $sep++;
275 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Donations").' | '.$langs->trans("Payments").'</span>', 'disabled' => 1);
276 }
277
278 $this->trackedevents['DON_VALIDATE'] = array('id' => 'DON_VALIDATE', 'label' => 'logDON_VALIDATE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDON_VALIDATE'));
279 $this->trackedevents['DON_DELETE'] = array('id' => 'DON_DELETE', 'label' => 'logDON_DELETE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDON_DELETE'));
280 //$this->trackedevents['DON_SENTBYMAIL'] = array('id' => 'BILL_VALIDATE', img_picto('', 'don', 'class="pictofixedwidth").').$langs->trans('labelhtml' => 'logDON_SENTBYMAIL');
281 $this->trackedevents['DONATION_PAYMENT_CREATE'] = array('id' => 'DONATION_PAYMENT_CREATE', 'label' => 'logDONATION_PAYMENT_CREATE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDONATION_PAYMENT_CREATE'));
282 $this->trackedevents['DONATION_PAYMENT_DELETE'] = array('id' => 'DONATION_PAYMENT_DELETE', 'label' => 'logDONATION_PAYMENT_DELETE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDONATION_PAYMENT_DELETE'));
283 }
284
285 /*
286 // Salary
287 if (isModEnabled('salary')) {
288 $this->trackedevents['PAYMENT_SALARY_CREATE'] = 'BlockedLogSalaryPaymentCreate';
289 $this->trackedevents['PAYMENT_SALARY_MODIFY'] = 'BlockedLogSalaryPaymentCreate';
290 $this->trackedevents['PAYMENT_SALARY_DELETE'] = 'BlockedLogSalaryPaymentCreate';
291 }
292 */
293
294 // Members
295 if (isModEnabled('member') && getDolGlobalString('BLOCKEDLOG_ENABLE_MEMBER')) { // For countries that need unalterable logs for membership management
296 if (!empty($this->trackedevents)) {
297 $sep++;
298 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("MenuMembers").'</span>', 'disabled' => 1);
299 }
300
301 $this->trackedevents['MEMBER_SUBSCRIPTION_CREATE'] = array('id' => 'MEMBER_SUBSCRIPTION_CREATE', 'label' => 'logMEMBER_SUBSCRIPTION_CREATE', 'labelhtml' => img_picto('', 'member', 'class="pictofixedwidth").').$langs->trans('logMEMBER_SUBSCRIPTION_CREATE'));
302 $this->trackedevents['MEMBER_SUBSCRIPTION_MODIFY'] = array('id' => 'MEMBER_SUBSCRIPTION_MODIFY', 'label' => 'logMEMBER_SUBSCRIPTION_MODIFY', 'labelhtml' => img_picto('', 'member', 'class="pictofixedwidth").').$langs->trans('logMEMBER_SUBSCRIPTION_MODIFY'));
303 $this->trackedevents['MEMBER_SUBSCRIPTION_DELETE'] = array('id' => 'MEMBER_SUBSCRIPTION_DELETE', 'label' => 'logMEMBER_SUBSCRIPTION_DELETE', 'labelhtml' => img_picto('', 'member', 'class="pictofixedwidth").').$langs->trans('logMEMBER_SUBSCRIPTION_DELETE'));
304 }
305
306 // Bank
307 /*
308 if (isModEnabled("bank")) {
309 if (!empty($this->trackedevents)) {
310 $sep++;
311 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("VariousPayment").'</span>', 'disabled' => 1);
312 }
313
314 $this->trackedevents['PAYMENT_VARIOUS_CREATE'] = array('id' => 'PAYMENT_VARIOUS_CREATE', 'label' => 'logPAYMENT_VARIOUS_CREATE', 'labelhtml' => img_picto('', 'bank', 'class="pictofixedwidth").').$langs->trans('logPAYMENT_VARIOUS_CREATE'));
315 $this->trackedevents['PAYMENT_VARIOUS_MODIFY'] = array('id' => 'PAYMENT_VARIOUS_MODIFY', 'label' => 'logPAYMENT_VARIOUS_MODIFY', 'labelhtml' => img_picto('', 'bank', 'class="pictofixedwidth").').$langs->trans('logPAYMENT_VARIOUS_MODIFY'));
316 $this->trackedevents['PAYMENT_VARIOUS_DELETE'] = array('id' => 'PAYMENT_VARIOUS_DELETE', 'label' => 'logPAYMENT_VARIOUS_DELETE', 'labelhtml' => img_picto('', 'bank', 'class="pictofixedwidth").').$langs->trans('logPAYMENT_VARIOUS_DELETE'));
317 }
318 */
319
320 // Cash register closing
321 // $conf->global->BANK_ENABLE_POS_CASHCONTROL must be set to 1 by all external POS modules
322 $moduleposenabled = (isModEnabled('cashdesk') || isModEnabled('takepos') || getDolGlobalString('BANK_ENABLE_POS_CASHCONTROL'));
323 if ($moduleposenabled) {
324 if (!empty($this->trackedevents)) {
325 $sep++;
326 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("CashControl").'</span>', 'disabled' => 1);
327 }
328 if (getDolGlobalString('BLOCKEDLOG_ADD_OLD_CASHCONTROL_VALIDATE')) {
329 $this->trackedevents['CASHCONTROL_VALIDATE'] = array('id' => 'CASHCONTROL_VALIDATE', 'label' => 'logCASHCONTROL_VALIDATE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_VALIDATE'));
330 }
331 $this->trackedevents['CASHCONTROL_CLOSE'] = array('id' => 'CASHCONTROL_CLOSE', 'label' => 'logCASHCONTROL_CLOSE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_CLOSE'));
332 }
333
334 // Add more action to track from a conf variable. For the case we want to track other actions into the unalterable log.
335 // For example: STOCK_MOVEMENT, ...
336 if (getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED')) {
337 if (!empty($this->trackedevents)) {
338 $sep++;
339 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----------</span>', 'disabled' => 1);
340 }
341
342 $tmparrayofmoresupportedevents = explode(',', getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED'));
343 foreach ($tmparrayofmoresupportedevents as $val) {
344 $this->trackedevents[$val] = array('id' => $val, 'label' => 'log'.$val, 'labelhtml' => img_picto('', 'generic', 'class="pictofixedwidth").').$langs->trans('log'.$val));
345 }
346 }
347
348 if (!empty($this->trackedevents)) {
349 $sep++;
350 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Other").'</span>', 'disabled' => 1);
351 }
352 $this->trackedevents['BLOCKEDLOG_EXPORT'] = array('id' => 'BLOCKEDLOG_EXPORT', 'label' => 'logBLOCKEDLOG_EXPORT', 'labelhtml' => img_picto('', $this->picto, 'class="pictofixedwidth").').$langs->trans('logBLOCKEDLOG_EXPORT'));
353
354 return 1;
355 }
356
362 public function getObjectLink()
363 {
364 global $langs;
365
366 if ($this->element === 'facture') {
367 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
368
369 $object = new Facture($this->db);
370 if ($object->fetch($this->fk_object) > 0) {
371 return $object->getNomUrl(1);
372 } else {
373 $this->error = (string) (((int) $this->error) + 1);
374 }
375 }
376 if ($this->element === 'invoice_supplier') {
377 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
378
379 $object = new FactureFournisseur($this->db);
380 if ($object->fetch($this->fk_object) > 0) {
381 return $object->getNomUrl(1);
382 } else {
383 $this->error = (string) (((int) $this->error) + 1);
384 }
385 } elseif ($this->element === 'payment') {
386 require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
387
388 $object = new Paiement($this->db);
389 if ($object->fetch($this->fk_object) > 0) {
390 return $object->getNomUrl(1);
391 } else {
392 $this->error = (string) (((int) $this->error) + 1);
393 }
394 } elseif ($this->element === 'payment_supplier') {
395 require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';
396
397 $object = new PaiementFourn($this->db);
398 if ($object->fetch($this->fk_object) > 0) {
399 return $object->getNomUrl(1);
400 } else {
401 $this->error = (string) (((int) $this->error) + 1);
402 }
403 } elseif ($this->element === 'payment_donation') {
404 require_once DOL_DOCUMENT_ROOT.'/don/class/paymentdonation.class.php';
405
406 $object = new PaymentDonation($this->db);
407 if ($object->fetch($this->fk_object) > 0) {
408 return $object->getNomUrl(1);
409 } else {
410 $this->error = (string) (((int) $this->error) + 1);
411 }
412 } elseif ($this->element === 'payment_various') {
413 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
414
415 $object = new PaymentVarious($this->db);
416 if ($object->fetch($this->fk_object) > 0) {
417 return $object->getNomUrl(1);
418 } else {
419 $this->error = (string) (((int) $this->error) + 1);
420 }
421 } elseif ($this->element === 'don' || $this->element === 'donation') {
422 require_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php';
423
424 $object = new Don($this->db);
425 if ($object->fetch($this->fk_object) > 0) {
426 return $object->getNomUrl(1);
427 } else {
428 $this->error = (string) (((int) $this->error) + 1);
429 }
430 } elseif ($this->element === 'subscription') {
431 require_once DOL_DOCUMENT_ROOT.'/adherents/class/subscription.class.php';
432
433 $object = new Subscription($this->db);
434 if ($object->fetch($this->fk_object) > 0) {
435 return $object->getNomUrl(1);
436 } else {
437 $this->error = (string) (((int) $this->error) + 1);
438 }
439 } elseif ($this->element === 'cashcontrol') {
440 require_once DOL_DOCUMENT_ROOT.'/compta/cashcontrol/class/cashcontrol.class.php';
441
442 $object = new CashControl($this->db);
443 if ($object->fetch($this->fk_object) > 0) {
444 return $object->getNomUrl(1);
445 } else {
446 $this->error = (string) (((int) $this->error) + 1);
447 }
448 } elseif ($this->element === 'stockmouvement') {
449 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
450
451 $object = new MouvementStock($this->db);
452 if ($object->fetch($this->fk_object) > 0) {
453 return $object->getNomUrl(1);
454 } else {
455 $this->error = (string) (((int) $this->error) + 1);
456 }
457 } elseif ($this->element === 'project') {
458 require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
459
460 $object = new Project($this->db);
461 if ($object->fetch($this->fk_object) > 0) {
462 return $object->getNomUrl(1);
463 } else {
464 $this->error = (string) (((int) $this->error) + 1);
465 }
466 } elseif ($this->action == 'BLOCKEDLOG_EXPORT') {
467 return '<i class="opacitymedium">'.$langs->trans("logBLOCKEDLOG_EXPORT").'</i>';
468 } elseif ($this->action == 'MODULE_SET') {
469 return '<i class="opacitymedium">'.$langs->trans("BlockedLogEnabled").'</i>';
470 } elseif ($this->action == 'MODULE_RESET') { // This case should not happen. Paranoiac protection against possible bug that forces a record that will return a non valid entry.
471 if ($this->signature == '0000000000') {
472 return '<i class="opacitymedium">'.$langs->trans("BlockedLogDisabled").'</i>';
473 } else {
474 return '<i class="opacitymedium">'.$langs->trans("BlockedLogDisabledBis").'</i>';
475 }
476 }
477
478 return '<i class="opacitymedium">'.$langs->trans('ImpossibleToReloadObject', $this->element, $this->fk_object).'</i>';
479 }
480
486 public function getUser()
487 {
488 global $langs, $cachedUser;
489
490 if (empty($cachedUser)) {
491 $cachedUser = array();
492 }
493
494 if (empty($cachedUser[$this->fk_user])) {
495 $u = new User($this->db);
496 if ($u->fetch($this->fk_user) > 0) {
497 $cachedUser[$this->fk_user] = $u;
498 }
499 }
500
501 if (!empty($cachedUser[$this->fk_user])) {
502 return $cachedUser[$this->fk_user]->getNomUrl(1);
503 }
504
505 return $langs->trans('ImpossibleToRetrieveUser', $this->fk_user);
506 }
507
520 public function setObjectData(&$object, $action, $amounts, $fuser = null, $amounts_taxexcl = null)
521 {
522 global $langs, $user, $mysoc;
523
524 if (is_object($fuser)) {
525 $user = $fuser;
526 }
527
528 // Init object_data for JSON data
529 $this->object_data = new stdClass();
530
531 // Generic fields
532
533 // entity
534 $this->entity = $object->entity ?? getDolEntity();
535
536 // action
537 $this->action = $action;
538
539 // amount
540 $this->amounts_taxexcl = $amounts_taxexcl;
541 $this->amounts = $amounts;
542 if ($action === 'MEMBER_SUBSCRIPTION_DELETE' || $action === 'PAYMENT_CUSTOMER_DELETE' || $action === 'PAYMENT_SUPPLIER_DELETE' || $action === 'DONATION_PAYMENT_DELETE') {
543 $this->amounts_taxexcl = - $this->amounts_taxexcl;
544 $this->amounts = - $this->amounts;
545 }
546
547 // date
548 if ($object->element == 'payment' || $object->element == 'payment_supplier') {
549 '@phan-var-force Paiement|PaiementFourn $object';
550 $this->date_object = empty($object->datepaye) ? $object->date : $object->datepaye;
551 } elseif ($object->element == 'payment_salary') {
552 '@phan-var-force PaymentSalary $object';
553 $this->date_object = $object->datev;
554 } elseif ($object->element == 'payment_donation' || $object->element == 'payment_various') {
555 '@phan-var-force PaymentDonation $object';
556 $this->date_object = empty($object->datepaid) ? $object->datep : $object->datepaid;
557 } elseif ($object->element == 'subscription') {
558 '@phan-var-force Subscription $object';
559 $this->date_object = $object->dateh;
560 } elseif ($object->element == 'cashcontrol') {
562 '@phan-var-force CashControl $object';
563 $this->date_object = $object->date_creation;
564 $this->module_source = $object->posmodule;
565 $this->pos_source = $object->posnumber;
566 } elseif (property_exists($object, 'date')) {
567 // Generic case
568 $this->date_object = $object->date; // @phan-suppress-current-line PhanUndeclaredProperty
569 } elseif (property_exists($object, 'datem')) {
570 // Generic case (second chance, for example for stock movement)
571 $this->date_object = $object->datem; // @phan-suppress-current-line PhanUndeclaredProperty
572 }
573
574 // In case of credit note, we add link to source invoice to have more tracking info when doing tracking later
575 if ($object->element == 'invoice_supplier') {
576 '@phan-var-force FactureFournisseur $object';
578 $invoice = new FactureFournisseur($this->db);
579 $invoice->fetch($object->fk_facture_source);
580 if ($invoice->id > 0) {
581 $this->linktype = 'credit_note_of';
582 $this->linktoref = $invoice->ref;
583 }
584 //$this->module_source = (string) $invoice->module_source;
585 //$this->pos_source = (string) $invoice->pos_source;
586 }
587 }
588 if ($object->element == 'facture') {
589 '@phan-var-force Facture $object';
590 if ($object->type == Facture::TYPE_CREDIT_NOTE) {
591 $invoice = new Facture($this->db);
592 $invoice->fetch($object->fk_facture_source);
593 if ($invoice->id > 0) {
594 $this->linktype = 'credit_note_of';
595 $this->linktoref = $invoice->ref;
596
597 $this->object_data->link = $this->linktype.' '.$this->linktoref;
598 }
599 $this->module_source = (string) $invoice->module_source;
600 $this->pos_source = (string) $invoice->pos_source;
601 }
602 }
603
604 // ref object
605 $this->ref_object = ((!empty($object->newref)) ? $object->newref : $object->ref); // newref is set when validating a draft, ref is set in other cases
606 // type of object
607 $this->element = $object->element;
608 // id of object
609 $this->fk_object = $object->id;
610
611 // Add thirdparty info if not yet done
612 if (empty($object->thirdparty) && method_exists($object, 'fetch_thirdparty')) {
613 $object->fetch_thirdparty();
614 }
615
616
617 // Add fields to exclude (this has become useless because we now use a list fields to keep later).
618 $arrayoffieldstoexclude = array(
619 'table_element', 'fields',
620 'ref_ext',
621 'ref_previous', 'ref_next',
622 'origin', 'origin_id',
623 'oldcopy', 'picto', 'error', 'errors',
624 'model_pdf', 'modelpdf', 'last_main_doc', 'civility_id', 'contact', 'contact_id',
625 'table_element_line', 'ismultientitymanaged', 'isextrafieldmanaged',
626 'array_languages',
627 'childtables',
628 'contact_ids',
629 'context',
630 'element',
631 'labelStatus',
632 'labelStatusShort',
633 'linkedObjectsIds',
634 'linkedObjects',
635 'fk_delivery_address',
636 'projet', // There is already ->fk_project
637 'restrictiononfksoc',
638 'specimen',
639 );
640
641 // Add more fields to exclude depending on object type
642 if ($this->element == 'cashcontrol') {
643 $arrayoffieldstoexclude = array_merge(
644 $arrayoffieldstoexclude,
645 array(
646 'name', 'lastname', 'firstname', 'region', 'region_id', 'region_code', 'state', 'state_id', 'state_code', 'country', 'country_id', 'country_code',
647 'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
648 'barcode_type', 'barcode_type_code', 'barcode_type_label', 'barcode_type_coder', 'mode_reglement_id', 'cond_reglement_id', 'mode_reglement', 'cond_reglement', 'shipping_method_id',
649 'extraparams', 'fk_incoterms', 'fk_user_creat', 'fk_user_valid', 'label_incoterms', 'location_incoterms', 'lines', 'nb', 'tms', 'comments', 'array_options', 'warnings',
650 'opening', 'status', 'date_valid'
651 )
652 );
653 }
654
655 // For customer payment and supplier payment, the thirdparty can be added in payment detail
656 $addthirdpartyatpaymentlevel = 0;
657 if (!empty($object->thirdparty) && in_array($this->element, array('payment', 'payment_supplier'))) {
658 $addthirdpartyatpaymentlevel = 1;
659 }
660
661 if (!empty($object->thirdparty) && !$addthirdpartyatpaymentlevel) { // If $addthirdpartyatpaymentlevel is set, we will add thirdparty on payments later.
662 $this->object_data->thirdparty = new stdClass();
663
664 foreach ($object->thirdparty as $key => $value) {
665 if (in_array($key, $arrayoffieldstoexclude)) {
666 continue; // Discard some properties
667 }
668 // List of fields qualified
669 if (!in_array($key, array(
670 'name', 'name_alias', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
671 'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax2_assuj', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
672 ))) {
673 continue; // Discard if not into this dedicated list
674 }
675
676 $valuequalifiedforstorage = false;
677 if (!is_object($value)) {
678 if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
679 $valuequalifiedforstorage = true; // We accept '' value for some fields
680 $value = (string) $value;
681 }
682 if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
683 $valuequalifiedforstorage = true; // We accept zero value for amounts
684 }
685 if (!is_null($value) && (string) $value !== '') {
686 $valuequalifiedforstorage = true;
687 }
688 }
689
690 if ($valuequalifiedforstorage) {
691 $this->object_data->thirdparty->$key = $value;
692 }
693 }
694 }
695
696 // Add my company info (Only for customer invoice and payment)
697 if (!empty($mysoc) && in_array($object->element, array('facture', 'paiement'))) {
698 $this->object_data->mycompany = new stdClass();
699
700 foreach ($mysoc as $key => $value) {
701 if (in_array($key, $arrayoffieldstoexclude)) {
702 continue; // Discard some properties
703 }
704 // List of fields qualified to keep
705 if (!in_array($key, array(
706 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
707 'tva_assuj', 'tva_intra', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
708 ))) {
709 continue; // Discard if not into this dedicated list
710 }
711
712 $valuequalifiedforstorage = false;
713 if (!is_object($value)) {
714 if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
715 $valuequalifiedforstorage = true; // We accept '' value for some fields
716 $value = (string) $value;
717 }
718 if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
719 $valuequalifiedforstorage = true; // We accept zero value for amounts
720 }
721 if (!is_null($value) && (string) $value !== '') {
722 $valuequalifiedforstorage = true;
723 }
724 }
725
726 if ($valuequalifiedforstorage) {
727 $this->object_data->mycompany->$key = $value;
728 }
729 }
730 }
731
732 // Add user info
733 if (!empty($user)) {
734 $this->fk_user = $user->id;
735 $this->user_fullname = $user->getFullName($langs);
736 }
737
738 // Field specific to object
739 if ($this->element == 'facture') {
740 '@phan-var-force Facture $object';
741 $this->module_source = (string) $object->module_source;
742 $this->pos_source = (string) $object->pos_source;
743
744 foreach ($object as $key => $value) {
745 if (in_array($key, $arrayoffieldstoexclude)) {
746 continue; // Discard some properties
747 }
748 // List of fields qualified
749 if (!in_array($key, array(
750 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'datev', 'type',
751 //'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx', There is no rate at full doc level
752 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2',
753 'revenuestamp', 'datepointoftax', 'note_public',
754 'lines',
755 'module_source', 'pos_source', 'pos_print_counter', 'email_sent_counter'
756 ))) {
757 continue; // Discarded if not into the dedicated list
758 }
759 if ($key == 'lines') {
760 $lineid = 0;
761 foreach ($value as $tmpline) { // $tmpline is object FactureLine
762 $lineid++;
763 foreach ($tmpline as $keyline => $valueline) {
764 if (!in_array($keyline, array(
765 'ref', 'product_type', 'product_label',
766 'qty', 'subprice',
767 'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx',
768 'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
769 'multicurrency_code', 'multicurrency_total_ht', 'multicurrency_total_tva', 'multicurrency_total_ttc',
770 'info_bits', 'special_code', 'remise_percent'
771 ))) {
772 continue; // Discard if not into a dedicated list
773 }
774
775 if (empty($this->object_data->invoiceline[$lineid]) || !is_object($this->object_data->invoiceline[$lineid])) { // To avoid warning
776 $this->object_data->invoiceline[$lineid] = new stdClass();
777 }
778
779 $valuequalifiedforstorage = false;
780 if (!is_object($valueline)) {
781 if (!is_null($valueline) && empty($valueline) && in_array($key, array('tva_tx', 'localtax1_tx', 'localtax2_tx', 'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2'))) {
782 $valuequalifiedforstorage = true; // We accept zero value for amounts
783 }
784 if (!is_null($valueline) && (string) $valueline !== '') {
785 $valuequalifiedforstorage = true;
786 }
787 }
788 if ($keyline == 'product_label' && empty($valueline)) {
789 $valueline = dol_trunc(dolGetFirstLineOfText($tmpline->desc)); // Fallback on description if label is empty
790 $valuequalifiedforstorage = true;
791 }
792
793 if ($valuequalifiedforstorage) {
794 $this->object_data->invoiceline[$lineid]->$keyline = $valueline;
795 }
796 }
797 }
798 } else {
799 $valuequalifiedforstorage = false;
800 if (!is_object($value)) {
801 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
802 $valuequalifiedforstorage = true; // We accept '' value for some fields
803 $value = (string) $value;
804 }
805 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
806 $valuequalifiedforstorage = true; // We accept zero value for amounts
807 }
808 if (!is_null($value) && (string) $value !== '') {
809 $valuequalifiedforstorage = true;
810 }
811 }
812
813 if ($valuequalifiedforstorage) {
814 $this->object_data->$key = $value;
815 }
816 }
817 }
818
819 if (!empty($object->newref)) {
820 $this->object_data->ref = $object->newref;
821 }
822
823 // Add data for action emails
824 if ($action == 'BILL_SENTBYMAIL') {
825 $this->object_data->action_email_sent = array(
826 "email_from" => $object->context['email_from'],
827 "email_to" => $object->context['email_to'],
828 "email_msgid" => $object->context['email_msgid']
829 );
830 }
831 } elseif ($this->element == 'invoice_supplier') {
832 '@phan-var-force FactureFournisseur $object';
833 foreach ($object as $key => $value) {
834 if (in_array($key, $arrayoffieldstoexclude)) {
835 continue; // Discard some properties
836 }
837 // List of fields qualified
838 if (!in_array($key, array(
839 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public'
840 ))) {
841 continue; // Discard if not into a dedicated list
842 }
843
844 $valuequalifiedforstorage = false;
845 if (!is_object($value)) {
846 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
847 $valuequalifiedforstorage = true; // We accept '' value for some fields
848 $value = (string) $value;
849 }
850 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
851 $valuequalifiedforstorage = true; // We accept zero value for amounts
852 }
853 if (!is_null($value) && (string) $value !== '') {
854 $valuequalifiedforstorage = true;
855 }
856 }
857
858 if ($valuequalifiedforstorage) {
859 $this->object_data->$key = $value;
860 }
861 }
862
863 if (!empty($object->newref)) {
864 $this->object_data->ref = $object->newref;
865 }
866 } elseif ($this->element == 'payment' || $this->element == 'payment_supplier' || $this->element == 'payment_donation' || $this->element == 'payment_various') {
867 '@phan-var-force Paiement|PaiementFourn|PaymentDonation|PaymentVarious $object';
868 $datepayment = $object->datepaye ? $object->datepaye : ($object->datepaid ? $object->datepaid : $object->datep);
869 $paymenttypeid = $object->paiementid ? $object->paiementid : ($object->paymenttype ? $object->paymenttype : $object->type_payment);
870
871 $this->object_data->ref = $object->ref;
872 $this->object_data->date = $datepayment;
873 $this->object_data->type_code = dol_getIdFromCode($this->db, $paymenttypeid, 'c_paiement', 'id', 'code');
874
875 if (!empty($object->num_payment)) {
876 $this->object_data->payment_num = $object->num_payment;
877 }
878 if (!empty($object->note_private)) {
879 $this->object_data->note_private = $object->note_private;
880 }
881 //$this->object_data->fk_account = $object->fk_account;
882 //var_dump($this->object_data);exit;
883
884 $totalamount = 0;
885
886 $this->type_code = $this->object_data->type_code;
887 $this->linktype = $this->element;
888 $this->linktoref = '';
889
890 // If payment and $object->amounts is empty (for example when we delete), we complete the information
891 if ($this->element == 'payment' && empty($object->amounts) && $object instanceof Paiement) {
892 $amountsarray = $object->getAmountsArray();
893 $object->amounts = $amountsarray;
894 // Invert the sign of amount into the array ->amounts if it is a deletion
895 if ($action == 'PAYMENT_CUSTOMER_DELETE') {
896 foreach ($object->amounts as $amountkey => $amountval) {
897 $object->amounts[$amountkey] = - $amountval;
898 }
899 }
900 }
901
902 // Loop on each invoice payment amount (the payment_part)
903 if (is_array($object->amounts) && !empty($object->amounts)) {
904 // Loop on each invoice the payment is part of to set the linktoref and the module_source and pos_source
905 $originofpayment = null;
906 $terminalofpayment = null;
907 $paymentpartnumber = 0;
908 foreach ($object->amounts as $objid => $amount) {
909 if (empty($amount)) {
910 continue;
911 }
912
913 $totalamount += $amount;
914
915 $tmpobject = null;
916 if ($this->element == 'payment_supplier') {
917 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
918 $tmpobject = new FactureFournisseur($this->db);
919 } elseif ($this->element == 'payment') {
920 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
921 $tmpobject = new Facture($this->db);
922 } elseif ($this->element == 'payment_donation') {
923 include_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php';
924 $tmpobject = new Don($this->db);
925 } elseif ($this->element == 'payment_various') {
926 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
927 $tmpobject = new PaymentVarious($this->db);
928 }
929
930 if (!is_object($tmpobject)) {
931 continue;
932 }
933
934 $result = $tmpobject->fetch($objid);
935
936 if ($result <= 0) {
937 $this->error = $tmpobject->error;
938 $this->errors = $tmpobject->errors;
939 dol_syslog("Failed to fetch object with id ".$objid, LOG_ERR);
940 return -1;
941 }
942
943 $this->linktoref .= ($this->linktoref ? ',' : '').$tmpobject->ref;
944
945 // Set the ->module_source of payment from origin object if relevant
946 if (property_exists($tmpobject, 'module_source')) {
947 if (is_null($originofpayment)) {
948 $originofpayment = (string) $tmpobject->module_source;
949 } elseif ($originofpayment != $tmpobject->module_source) {
950 $originofpayment = 'mix'; // the payment is on several invoices with different origins of module
951 } else {
952 $originofpayment = (string) $tmpobject->module_source;
953 }
954 }
955 // Set the ->pos_source of payment from origin object if relevant
956 if (property_exists($tmpobject, 'pos_source')) {
957 if (is_null($terminalofpayment)) {
958 $terminalofpayment = (string) $tmpobject->pos_source;
959 } elseif ($terminalofpayment != $tmpobject->pos_source) {
960 $terminalofpayment = 'mix'; // the payment is on several invoices with same origin of module but different terminals
961 } else {
962 $terminalofpayment = (string) $tmpobject->pos_source;
963 }
964 }
965 $paymentpart = new stdClass();
966 $paymentpart->amount = $amount;
967
968 // If we want to add thirdparty on each payment level
969 // (seems not necessary as we have one thirdparty per payment on invoice level)
970 if ($addthirdpartyatpaymentlevel) {
971 $result = $tmpobject->fetch_thirdparty();
972 if ($result == 0) {
973 $this->error = 'Failed to fetch thirdparty for object with id '.$tmpobject->id;
974 $this->errors[] = $this->error;
975 dol_syslog("Failed to fetch thirdparty for object with id ".$tmpobject->id, LOG_ERR);
976 return -1;
977 } elseif ($result < 0) {
978 $this->error = $tmpobject->error;
979 $this->errors = $tmpobject->errors;
980 return -1;
981 }
982
983 $paymentpart->thirdparty = new stdClass();
984 foreach ($tmpobject->thirdparty as $key => $value) {
985 if (in_array($key, $arrayoffieldstoexclude)) {
986 continue; // Discard some properties
987 }
988 // List of thirdparty fields qualified
989 if (!in_array($key, array(
990 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
991 'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
992 ))) {
993 continue; // Discard if not into a dedicated list
994 }
995 if (!is_object($value) && !is_null($value) && $value !== '') {
996 $paymentpart->thirdparty->$key = $value;
997 }
998 }
999 }
1000
1001 // Init object to avoid warnings
1002 if ($this->element == 'payment_donation') {
1003 $paymentpart->donation = new stdClass();
1004 } elseif ($this->element == 'payment_various') {
1005 $paymentpart->various = new stdClass();
1006 } else {
1007 $paymentpart->invoice = new stdClass();
1008 }
1009
1010 if ($this->element != 'payment_various') {
1011 foreach ($tmpobject as $key => $value) {
1012 if (in_array($key, $arrayoffieldstoexclude)) {
1013 continue; // Discard some properties
1014 }
1015 // List of fields qualified
1016 if (!in_array($key, array(
1017 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public',
1018 'pos_source', 'module_source', 'pos_print_counter', 'email_sent_counter'
1019 ))) {
1020 continue; // Discard if not into a dedicated list
1021 }
1022
1023 $valuequalifiedforstorage = false;
1024 if (!is_object($value)) {
1025 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
1026 $valuequalifiedforstorage = true; // We accept '' value for some fields
1027 $value = (string) $value;
1028 }
1029 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
1030 $valuequalifiedforstorage = true; // We accept zero value for amounts
1031 }
1032 if (!is_null($value) && (string) $value !== '') {
1033 $valuequalifiedforstorage = true;
1034 }
1035 }
1036
1037 if ($valuequalifiedforstorage) {
1038 if ($this->element == 'payment_donation') {
1039 $paymentpart->donation->$key = $value;
1040 } elseif ($this->element == 'payment_various') {
1041 $paymentpart->various->$key = $value;
1042 } else {
1043 $paymentpart->invoice->$key = $value;
1044 }
1045 }
1046 }
1047
1048 $paymentpartnumber++; // first payment will be 1
1049 $this->object_data->payment_part[$paymentpartnumber] = $paymentpart;
1050 }
1051 }
1052
1053 $this->module_source = (string) $originofpayment;
1054 $this->pos_source = (string) $terminalofpayment;
1055 } elseif (!empty($object->amount)) {
1056 $totalamount = $object->amount;
1057 }
1058
1059 $this->object_data->amount = $totalamount;
1060
1061 if (!empty($object->newref)) {
1062 $this->object_data->ref = $object->newref;
1063 }
1064 } elseif ($this->element == 'payment_salary') {
1065 '@phan-var-force PaymentSalary $object';
1066 $this->object_data->amounts = array($object->amount);
1067
1068 if (!empty($object->newref)) {
1069 $this->object_data->ref = $object->newref;
1070 }
1071 } elseif ($this->element == 'subscription') {
1072 '@phan-var-force Subscription $object';
1073 foreach ($object as $key => $value) {
1074 if (in_array($key, $arrayoffieldstoexclude)) {
1075 continue; // Discard some properties
1076 }
1077 if (!in_array($key, array(
1078 'id', 'datec', 'dateh', 'datef', 'fk_adherent', 'amount', 'import_key', 'statut', 'note'
1079 ))) {
1080 continue; // Discard if not into a dedicated list
1081 }
1082 if (!is_object($value) && !is_null($value) && $value !== '') {
1083 $this->object_data->$key = $value;
1084 }
1085 }
1086
1087 if (!empty($object->newref)) {
1088 $this->object_data->ref = $object->newref;
1089 }
1090 } elseif ($this->element == 'stockmouvement') {
1091 '@phan-var-force StockTransfer $object';
1092 foreach ($object as $key => $value) {
1093 if (in_array($key, $arrayoffieldstoexclude)) {
1094 continue; // Discard some properties
1095 }
1096 if (!is_object($value) && !is_null($value) && $value !== '') {
1097 $this->object_data->$key = $value;
1098 }
1099 }
1100 } else {
1101 if ($object->element == 'cashcontrol') {
1102 $this->module_source = (string) $object->posmodule; // Module
1103 $this->pos_source = (string) $object->posnumber; // Terminal
1104 }
1105
1106 // Generic case
1107 foreach ($object as $key => $value) {
1108 if (in_array($key, $arrayoffieldstoexclude)) {
1109 continue; // Discard some properties
1110 }
1111 if (!is_object($value) && !is_null($value) && $value !== '') {
1112 $this->object_data->$key = $value;
1113 }
1114 }
1115
1116 if ($object->element == 'cashcontrol') {
1117 $period = $object->year_close;
1118 $period .= ($object->month_close ? "-".sprintf("%02d", $object->month_close) : "");
1119 $period .= ($object->day_close ? "-".sprintf("%02d", $object->day_close) : "");
1120
1121 $this->object_data->period = $period;
1122 }
1123
1124 if (!empty($object->newref)) {
1125 $this->object_data->ref = $object->newref;
1126 }
1127 }
1128
1129 // A trick to be sure all the object_data is an associative array
1130 // json_encode and json_decode are not able to manage mixed object (with array/object, only full arrays or full objects)
1131 $this->object_data = json_decode(json_encode($this->object_data, JSON_FORCE_OBJECT), false);
1132
1133 return 1;
1134 }
1135
1142 public function fetch($id)
1143 {
1144 global $langs;
1145
1146 if (empty($id)) {
1147 $this->error = 'BadParameter';
1148 return -1;
1149 }
1150
1151 $sql = "SELECT b.rowid, b.date_creation, b.action, b.module_source, b.pos_source, b.amounts_taxexcl, b.amounts, b.element, b.fk_object, b.entity,";
1152 $sql .= " b.certified, b.tms, b.fk_user, b.user_fullname, b.date_object, b.ref_object, b.type_code, b.linktoref, b.linktype, b.object_data, b.object_version, b.object_format, b.signature,";
1153 $sql .= " b.note";
1154 $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog as b";
1155 $sql .= " WHERE b.rowid = ".((int) $id); // $id is not empty because of test above
1156
1157
1158 $resql = $this->db->query($sql);
1159 if ($resql) {
1160 $obj = $this->db->fetch_object($resql);
1161 if ($obj) {
1162 $this->id = $obj->rowid;
1163 $this->entity = $obj->entity;
1164
1165 // Must be at top
1166 $tz = 'gmt';
1167 if (empty($obj->object_format) || $obj->object_format == 'V1') {
1168 $tz = 'tzserver';
1169 }
1170
1171 $this->date_creation = $this->db->jdate($obj->date_creation, $tz); // jdate(date_creation)is UTC
1172 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
1173 $this->date_modification = $this->db->jdate($obj->tms, $tz); // jdate(tms) is UTC
1174
1175
1176 $this->action = $obj->action;
1177 $this->module_source = $obj->module_source;
1178 $this->pos_source = $obj->pos_source;
1179
1180 $this->amounts_taxexcl = (is_null($obj->amounts_taxexcl) ? null : (float) $obj->amounts_taxexcl);
1181 $this->amounts = (float) $obj->amounts;
1182
1183 $this->fk_object = $obj->fk_object;
1184 $this->date_object = $this->db->jdate($obj->date_object, $tz); // jdate(date_object) is UTC
1185 //var_dump($obj->date_object, dol_print_date($this->date_object, 'dayhour' , $tz));
1186 //exit;
1187
1188 $this->ref_object = $obj->ref_object;
1189 $this->type_code = $obj->type_code;
1190 $this->linktoref = $obj->linktoref;
1191 $this->linktype = $obj->linktype;
1192
1193 $this->fk_user = $obj->fk_user;
1194 $this->user_fullname = $obj->user_fullname;
1195
1196 $this->object_data = $this->dolDecodeBlockedData($obj->object_data);
1197 $this->object_version = $obj->object_version;
1198 $this->object_format = $obj->object_format;
1199
1200 $this->element = $obj->element;
1201
1202 $this->signature = $obj->signature;
1203 $this->certified = ($obj->certified == 1);
1204
1205 $this->note = $obj->note;
1206 //$this->debuginfo = $obj->debuginfo; // We don't need this, sot we don't load it to save memory.
1207
1208 return 1;
1209 } else {
1210 $langs->load("errors");
1211 $this->error = $langs->trans("ErrorRecordNotFound");
1212 return 0;
1213 }
1214 } else {
1215 $this->error = $this->db->error();
1216 return -1;
1217 }
1218 }
1219
1220
1228 public function dolEncodeBlockedData($data, $mode = 1)
1229 {
1230 $aaa = json_encode($data);
1231
1232 return $aaa;
1233 }
1234
1235
1243 public function dolDecodeBlockedData($data, $mode = 0)
1244 {
1245 $aaa = null;
1246 try {
1247 $aaa = (object) jsonOrUnserialize($data, false);
1248 } catch (Exception $e) {
1249 // print $e->getErrs);
1250 }
1251
1252 return $aaa;
1253 }
1254
1255
1261 public function setCertified()
1262 {
1263 $res = $this->db->query("UPDATE ".MAIN_DB_PREFIX."blockedlog SET certified = 1 WHERE rowid = ".((int) $this->id));
1264 if (!$res) {
1265 return false;
1266 }
1267
1268 return true;
1269 }
1270
1278 public function create($user, $forcesignature = '')
1279 {
1280 global $conf, $langs, $mysoc;
1281
1282 $langs->load('blockedlog');
1283
1284 // Clean data
1285 $this->amounts = (float) $this->amounts;
1286
1287 dol_syslog(get_class($this).'::create action='.$this->action.' fk_user='.$this->fk_user.' user_fullname='.$this->user_fullname, LOG_DEBUG);
1288
1289 // Check parameters/properties
1290 if (!isset($this->amounts)) { // amount can be 0 for some events (like when module is disabled)
1291 $langs->load("errors");
1292 $this->error = $langs->trans("ErrorBlockLogNeedAmountsValue");
1293 dol_syslog($this->error, LOG_WARNING);
1294 return -1;
1295 }
1296
1297 if (empty($this->element)) {
1298 $langs->load("errors");
1299 $this->error = $langs->trans("ErrorBlockLogNeedElement");
1300 dol_syslog($this->error, LOG_WARNING);
1301 return -2;
1302 }
1303
1304 if (empty($this->object_data)) {
1305 $langs->load("errors");
1306 $this->error = $langs->trans("ErrorBlockLogNeedObject");
1307 dol_syslog($this->error, LOG_WARNING);
1308 return -2;
1309 }
1310
1311 if (empty($this->date_object)) { // date_object is a critical field, it is included into the line signature
1312 $langs->load("errors");
1313 $this->error = $langs->trans("ErrorBlockLogNeedDateObject");
1314 dol_syslog($this->error, LOG_WARNING);
1315 return -2;
1316 }
1317
1318 if (empty($this->action)) {
1319 $langs->load("errors");
1320 $this->error = $langs->trans("ErrorBadParameterWhenCallingCreateOfBlockedLog");
1321 dol_syslog($this->error, LOG_WARNING);
1322 return -3;
1323 }
1324 if (empty($this->fk_user)) {
1325 $this->user_fullname = '(Anonymous)';
1326 }
1327
1328 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
1329
1330 $this->db->begin();
1331
1332 $this->date_creation = dol_now();
1333
1334 $this->object_version = DOL_VERSION;
1335
1336 // The object_format defines the formatting rules and syntax into
1337 // buildKeyForSignature and buildFirstPartOfKeyForSignature and buildFinalSignatureHash
1338 // This may vary when the Immutable Log module version is modified, but only if algorithm has changed.
1339 $this->object_format = 'V2';
1340
1341 $tz = 'gmt';
1342
1343 $previoushash = '';
1344 $previousid = 0;
1345 $previousdatecreation = 0;
1346
1347 try {
1348 $tmparray = $this->getPreviousHash(1, 0); // This get last record and lock database until insert is done and transaction closed
1349
1350 $previoushash = $tmparray['previoushash'];
1351 $previousid = $tmparray['previousid'];
1352 $previousdatecreation = $tmparray['previousdatecreation'];
1353
1354 // The string of line to sign
1355 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1356
1357 // The new hash, including previous hash
1358 $this->signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1359
1360 // For debug info (we can clean this field later)
1361 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1362 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1363 }
1364 } catch (Exception $e) {
1365 $this->error = $e->getMessage();
1366
1367 dol_syslog($this->error, LOG_ERR);
1368
1369 $this->db->rollback();
1370 return -1;
1371 }
1372
1373 if ($forcesignature) {
1374 $this->signature = $forcesignature;
1375 }
1376 //var_dump($previoushash, $concatenateddata, $this->signature);
1377
1378 $sql = "INSERT INTO ".MAIN_DB_PREFIX."blockedlog (";
1379 $sql .= " date_creation,";
1380 $sql .= " action,";
1381 $sql .= " module_source,";
1382 $sql .= " pos_source,";
1383 $sql .= " amounts_taxexcl,";
1384 $sql .= " amounts,";
1385 $sql .= " signature,";
1386 $sql .= " element,";
1387 $sql .= " fk_object,";
1388 $sql .= " date_object,";
1389 $sql .= " ref_object,";
1390 $sql .= " type_code,";
1391 $sql .= " linktoref,";
1392 $sql .= " linktype,";
1393 $sql .= " object_data,";
1394 $sql .= " object_version,";
1395 $sql .= " object_format,";
1396 $sql .= " certified,";
1397 $sql .= " fk_user,";
1398 $sql .= " user_fullname,";
1399 $sql .= " entity,";
1400 $sql .= " debuginfo"; // Only stored
1401 $sql .= ") VALUES (";
1402 $sql .= "'".$this->db->idate($this->date_creation, $tz)."',";
1403 $sql .= "'".$this->db->escape($this->action)."',";
1404 $sql .= "'".$this->db->escape((string) $this->module_source)."',";
1405 $sql .= "'".$this->db->escape((string) $this->pos_source)."',";
1406 $sql .= (is_null($this->amounts_taxexcl) ? "null" : (float) $this->amounts_taxexcl).",";
1407 $sql .= (float) $this->amounts.",";
1408 $sql .= "'".$this->db->escape($this->signature)."',";
1409 $sql .= "'".$this->db->escape($this->element)."',";
1410 $sql .= (int) $this->fk_object.",";
1411 $sql .= "'".$this->db->idate($this->date_object, $tz)."',";
1412 $sql .= "'".$this->db->escape($this->ref_object)."',";
1413 $sql .= "'".$this->db->escape($this->type_code)."',";
1414 $sql .= ($this->linktoref ? "'".$this->db->escape($this->linktoref)."'" : "null").",";
1415 $sql .= ($this->linktype ? "'".$this->db->escape($this->linktype)."'" : "null").",";
1416 $sql .= "'".$this->db->escape($this->dolEncodeBlockedData($this->object_data))."',";
1417 $sql .= "'".$this->db->escape($this->object_version)."',";
1418 $sql .= "'".$this->db->escape($this->object_format)."',";
1419 $sql .= "0,";
1420 $sql .= ((int) $this->fk_user).",";
1421 $sql .= "'".$this->db->escape($this->user_fullname)."',";
1422 $sql .= ((int) ($this->entity ? ((int) $this->entity) : ((int) $conf->entity))).",";
1423 $sql .= "'".$this->db->escape($this->debuginfo)."'";
1424 $sql .= ")";
1425
1426 /*
1427 $a = serialize($this->object_data); $a2 = unserialize($a); $a4 = print_r($a2, true);
1428 $b = json_encode($this->object_data); $b2 = json_decode($b); $b4 = print_r($b2, true);
1429 var_dump($a4 == print_r($this->object_data, true) ? 'a=a' : 'a not = a');
1430 var_dump($b4 == print_r($this->object_data, true) ? 'b=b' : 'b not = b');
1431 exit;
1432 */
1433
1434 $res = $this->db->query($sql);
1435 if ($res) {
1436 $id = $this->db->last_insert_id(MAIN_DB_PREFIX."blockedlog");
1437
1438 if ($id > 0) {
1439 // The new ID
1440 $this->id = $id;
1441
1442 // Check and store the signature of this new line in the .end flag.
1443 try {
1444 $finalsignature = $this->signature;
1445 $finalnote = '';
1446
1447 $lockfile = $this->getEndOfChainFlagFile();
1448
1449 // Load the .end flag.
1450 // If not found (has been removed), we track the record as error.
1451 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1452 dol_mkdir(dirname($lockfile)); // Create at least directory for the lock file. Nothing if already exists.
1453
1454 if (!file_exists($lockfile)) {
1455 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1456
1457 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1458
1459 // The string of line to sign
1460 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1461
1462 // The new hash, including previous hash
1463 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1464 $finalnote = $this->note;
1465
1466 $line = '';
1467 } elseif (is_writable($lockfile)) {
1468 $line = file_get_contents($lockfile);
1469 } else {
1470 // Go to the catch()
1471 throw new Exception("Cannot write into the blockedlog .end flag ".$lockfile.' to update it. Is the file writable by the running user and not open by another process? Transaction aborted.');
1472 }
1473 } else {
1474 $sql = "SELECT value from ".MAIN_DB_PREFIX."const";
1475 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1476 $resql = $this->db->query($sql);
1477 if ($resql) {
1478 $obj = $this->db->fetch_object($resql);
1479 if ($obj) {
1480 $line = $obj->value;
1481 } else {
1482 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1483
1484 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1485
1486 // The string of line to sign
1487 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1488
1489 // The new hash, including previous hash
1490 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1491 $finalnote = $this->note;
1492
1493 $line = '';
1494 }
1495 } else {
1496 // Go to the catch()
1497 throw new Exception("Cannot read into the blockedlog .end flag ".basename($lockfile).' to update it. Transaction aborted.');
1498 }
1499 }
1500
1501 // Check the .end flag file.
1502 $headstring = '';
1503 $remoteobfuscationkey = '';
1504 if (preg_match('/^dolcrypt/', $line)) { // Old method (does not happen after migration to a certified version)
1505 $headstring = dolDecrypt($line);
1506 } elseif (preg_match('/^dolobfuscation/', $line)) {
1507 $remoteobfuscationkey = $this->getObfuscationKey();
1508 if (empty($remoteobfuscationkey)) {
1509 throw new Exception("Failed to get the remote obfuscation key. We can't read the end of chain flag file so we abort the transaction.");
1510 }
1511 $headstring = dolDecrypt($line, $remoteobfuscationkey);
1512 }
1513
1514 $reg = array();
1515 if (preg_match('/^BLOCKEDLOGHEAD (\d+) ([^\s]+) ([a-zA-Z0-9\-]+)/', $headstring, $reg)) {
1516 // We succeed in decypting the head
1517 $previousidheadflag = $reg[1];
1518 $previousdatecreationheadflag = $reg[2];
1519 $previoushashheadflag = $reg[3];
1520
1521 // Check the signature of the previous line
1522 if ($previousid < $previousidheadflag || $previoushash != $previoushashheadflag) {
1523 // We detect that old record were removed. We force a non valid signature on the new record.
1524 $this->note = 'EndOfChainDeletionDetected ['.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').' - '.dol_print_date($previousdatecreationheadflag, 'dayhourrfc', 'gmt').']';
1525
1526 // The string of line to sign
1527 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1528
1529 // The new hash, including previous hash
1530 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1531 $finalnote = $this->note;
1532 }
1533 } elseif ($headstring != '') {
1534 // Failed to decrypt the head
1535 throw new Exception("Failed to decode the content of the .end flag ".basename($lockfile).", content = ".$line." (remote obfuscation key = ".$remoteobfuscationkey."), so we can't record the head file so we abort the transaction.");
1536 }
1537
1538 // If a note has been added to track an anomaly (signature is also different in this case).
1539 if ($finalsignature != $this->signature) {
1540 // For debug info (we can clean this field later)
1541 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1542 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1543 }
1544
1545 // We update the record we have just inserted to record the new "anomaly" we have detected. Anomaly is also incrusted into the signature.
1546 $sql = "UPDATE ".MAIN_DB_PREFIX."blockedlog";
1547 $sql .= " SET signature = '".$this->db->escape($finalsignature)."',";
1548 $sql .= " note = '".$this->db->escape($finalnote)."',";
1549 $sql .= " debuginfo = '".$this->db->escape($this->debuginfo)."'";
1550 $sql .= " WHERE rowid = ".((int) $this->id);
1551 $resql = $this->db->query($sql);
1552 if (!$resql) {
1553 throw new Exception("End of chain deletion detected but we failed to update the signature of the record ".$this->id." to set the note and new signature ".$finalsignature." to track this.");
1554 }
1555 }
1556
1557
1558 // We can now write the new .end flag (Note: BLOCKEDLOGHEAD means end of chain)
1559 $stringtowrite = 'BLOCKEDLOGHEAD '.$this->id." ".dol_print_date($this->date_creation, 'dayhourrfc', 'gmt')." ".(string) $finalsignature;
1560
1561 if (isALNERunningVersion(1, ($this->action == 'MODULE_SET' ? 1 : 0)) && $mysoc->country_code == 'FR') {
1562 $remoteobfuscationkey = $this->getObfuscationKey();
1563 if (empty($remoteobfuscationkey)) {
1564 throw new Exception("Failed to get the remote obfuscation key. We can't record the end of chain flag file so we abort the transaction.");
1565 }
1566 $stringtowriteencoded = dolEncrypt($stringtowrite, $remoteobfuscationkey, '', '', 'dolobfuscationv1-'.$mysoc->idprof1.'-'.$this->id);
1567 } else {
1568 $stringtowriteencoded = dolEncrypt($stringtowrite, '', '', '', 'dolcrypt-'.$mysoc->idprof1.'-'.$this->id);
1569 }
1570
1571
1572 // Update or create the .end file.
1573 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1574 $lockhandle = fopen($lockfile, 'w+');
1575 if ($lockhandle) {
1576 if (fwrite($lockhandle, $stringtowriteencoded."\n") === false) {
1577 throw new Exception("Cannot write to the blockedlog .end file ".$lockfile);
1578 }
1579
1580 fclose($lockhandle); // Remove the lock
1581 dolChmod($lockfile);
1582 } else {
1583 throw new Exception("Cannot open for writing the blockedlog .end file ".$lockfile);
1584 }
1585 } else {
1586 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1587 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1588 $resql = $this->db->query($sql);
1589
1590 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1591 $sql .= " VALUES('".$this->db->escape(basename($lockfile))."', '".$this->db->escape($stringtowriteencoded)."', 'chaine', 0, 'Blockedlog end of chain flag', ".((int) $conf->entity).")";
1592 $resql = $this->db->query($sql);
1593 if (!$resql) {
1594 throw new Exception("Cannot update the blockedlog .end flag ".basename($lockfile));
1595 }
1596 }
1597 } catch (Exception $e) {
1598 $this->error = $e->getMessage();
1599
1600 dol_syslog($this->error, LOG_ERR);
1601
1602 $this->db->rollback();
1603 return -1;
1604 }
1605
1606 $this->db->commit();
1607
1608 // Call remote API service to record the last counter
1609 /*
1610 $error = 0;
1611
1612 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1613 try {
1614 $resultcall = callApiToPushCounter((int) $this->id, $this->signature, $this->date_creation, 0, (int) $previousid, $previoushash, $previousdatecreation);
1615 } catch (Exception $e) {
1616 $error++;
1617 $this->error = $e->getMessage();
1618 }
1619
1620 if (!$error) {
1621 return $this->id;
1622 } else {
1623 return -3;
1624 }
1625 */
1626 return $this->id;
1627 } else {
1628 $this->db->rollback();
1629 return -2;
1630 }
1631 } else {
1632 $this->error = $this->db->error();
1633 $this->db->rollback();
1634 return -1;
1635 }
1636
1637 // The commit or rollback will release the lock so app can insert other record now
1638 }
1639
1645 public function getEndOfChainFlagFile()
1646 {
1647 global $conf;
1648
1649 // Note: We must not use $conf->blockedlog->dir_output because we need this
1650 // function to work even when module not yet enabled.
1651 return DOL_DATA_ROOT.'/blockedlog/blockedlog-'.((int) $conf->entity).'.end';
1652 }
1653
1661 public function checkSignature($previoushash = '', $returnarray = 0)
1662 {
1663 if (empty($previoushash)) {
1664 $tmparray = $this->getPreviousHash(0, $this->id);
1665 $previoushash = $tmparray['previoushash'];
1666 }
1667
1668 $concatenateddata = '';
1669 $signature = '';
1670
1671 // Recalculate the signature
1672 try {
1673 // Build the string for the signature
1674 $concatenateddata = $this->buildKeyForSignature();
1675
1676 $signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata);
1677
1678 //var_dump($previoushash, $concatenateddata, $this->object_format, $signature);
1679 } catch (Exception $e) {
1680 $res = ($signature === $this->signature);
1681 $this->error = $e->getMessage();
1682
1683 dol_syslog($this->error, LOG_ERR);
1684
1685 if ($returnarray) {
1686 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'error' => $this->error);
1687 } else {
1688 return false;
1689 }
1690 }
1691
1692 $res = ($signature === $this->signature);
1693
1694 if (!$res) {
1695 $this->error = 'Signature KO';
1696 }
1697
1698 if ($returnarray) {
1699 if ($returnarray == 1) {
1700 unset($concatenateddata);
1701 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash);
1702 } else { // Consume much memory ($concatenateddata is a large var)
1703 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'keyforsignature' => $concatenateddata);
1704 }
1705 } else {
1706 unset($concatenateddata);
1707 return $res;
1708 }
1709 }
1710
1719 private function buildFirstPartOfKeyForSignature($format = '')
1720 {
1721 if (empty($format)) {
1722 $format = $this->object_format;
1723 }
1724
1725 // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1726 if ($format == '') {
1727 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1728 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1729 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1730 } elseif ($format == 'V2') {
1731 $s = $this->entity;
1732 $s .= '|'.$this->date_creation.'|'.$this->action.'|'.$this->module_source.'|'.$this->pos_source.'|'.$this->amounts_taxexcl;
1733 $s .= '|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1734 if ($this->type_code) {
1735 $s .= '|'.(string) $this->type_code;
1736 }
1737 $s .= '|'.(string) $this->linktoref;
1738 $s .= '|'.(string) $this->linktype;
1739 if ($this->note) {
1740 $s .= '|'.(string) $this->note;
1741 }
1742 return $s;
1743 } else {
1744 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1745 }
1746 }
1747
1754 public function buildKeyForSignature($format = '')
1755 {
1756 //print_r($this->object_data);
1757 if (empty($format)) {
1758 $format = $this->object_format;
1759 }
1760
1761 if ($format == '') {
1762 return $this->buildFirstPartOfKeyForSignature($format).'|'.print_r($this->object_data, true);
1763 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1764 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1765 } elseif ($format == 'V2') {
1766 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1767 } else {
1768 throw new Exception('Error bad value "'.$format.'" for object_format');
1769 }
1770 }
1771
1779 private function buildFinalSignatureHash($clearstring, $format = '')
1780 {
1781 global $mysoc;
1782
1783 if (empty($format)) {
1784 $format = $this->object_format;
1785 }
1786
1787 if ($format == '') {
1788 return dol_hash($clearstring, '5');
1789 } elseif ($format == 'V1') {
1790 return dol_hash($clearstring, '5');
1791 } elseif ($format == 'V2') {
1792 // BLOCKEDLOG_HMAC_KEY is a HMAC key starting with 'BLOCKEDLOGHMAC....'. It is not stored as a clear data but
1793 // is a string dolcrypt:... or dolobfuscationv1... It will be decrypted later.
1794 $hmac_encoded_secret_key = $this->getEncodedHMACSecretKey();
1795
1796 if (empty($hmac_encoded_secret_key)) {
1797 throw new Exception('Error: BLOCKEDLOG_HMAC_KEY was not found. It should have been initialized to a value "BLOCKEDLOG_HMAC_...." during initialization of module BlockedLog or during migration of an old version');
1798 }
1799
1800 // Here we have the crypted or obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
1801 $hmac_secret_key = '';
1802 try {
1803 $hmac_secret_key = $this->getClearHMACSecretKey($hmac_encoded_secret_key); // Note: On network trouble, an Exception is thrown to the caller
1804 } catch (Exception $e) {
1805 $firsterrormessage = $e->getMessage();
1806
1807 // Another chance to get HMAC when saved with old obfuscation method (dolcrypt)
1808 $hmac_encoded_secret_key_alt = $this->getEncodedHMACSecretKey(1, 1);
1809 if (!empty($hmac_encoded_secret_key_alt)) {
1810 try {
1811 $hmac_secret_key_alt = $this->getClearHMACSecretKey($hmac_encoded_secret_key_alt); // Note: On network trouble, an Exception is thrown to the caller
1812 if (preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key_alt)) { // Alternative is ok
1813 $hmac_secret_key = $hmac_secret_key_alt;
1814 }
1815 } catch (Exception $e) {
1816 throw new Exception($firsterrormessage);
1817 }
1818 } else {
1819 throw new Exception($firsterrormessage);
1820 }
1821 }
1822
1823 // Last check on validity of key
1824 if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
1825 //throw new Exception('Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY using the crypted or obfuscation key. A value was found but decoding failed. May be the database data were restored onto another environment and the coding/decoding key $dolibarr_main_dolcrypt_key or $dolibarr_main_instance_unique_id was not restored with the same value in conf.php file.');
1826 throw new Exception('buildFinalSignatureHash Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY '.$hmac_encoded_secret_key.' using the crypted or obfuscation key. A value was found in database but decoding failed. May be you modified the SIREN used to get the obfuscation key from ping.dolibarr.org (or old config key $dolibarr_main_instance_unique_id).');
1827 }
1828
1829 // Here the $hmac_secret_key is in memory with the correct value.
1830
1831 // On old versions, we must switch the data saving mode to use the new method.
1832 // Live migration of the way the key is stored.
1833 $needremoteobfuscation = (isALNERunningVersion(1) && $mysoc->country_code == 'FR');
1834
1835 if ($needremoteobfuscation && !preg_match('/^dolobfuscationv1/', $hmac_encoded_secret_key)) { // For old versions, we must switch the data saving mode to use the new method.
1836 $obfuscationkey = '';
1837 $errormsg = '';
1838 try {
1839 $obfuscationkey = $this->getObfuscationKey(); // Get obfuscation key providing $mysoc->idprof1 and $registrationnumber. Note: On network trouble, an Exception is thrown to the caller
1840 } catch (Exception $e) {
1841 $errormsg = $e->getMessage();
1842 }
1843 if (!$errormsg && $obfuscationkey) {
1844 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolobfuscationv1-'.$mysoc->idprof1, $obfuscationkey); // gitleaks:allow
1845 }
1846 } elseif (!$needremoteobfuscation && !preg_match('/^dolcrypt/', $hmac_encoded_secret_key)) {
1847 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolcrypt'); // gitleaks:allow
1848 }
1849
1850 // Here HMAC secret key is a long string starting with BLOCKEDLOGHMAC..., we can use it to sign the data.
1851 return hash_hmac('sha256', $clearstring, $hmac_secret_key);
1852 } else {
1853 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1854 }
1855 }
1856
1857
1867 public function saveHMACSecretKey($hmac_secret_key, $obfuscationmode, $obfuscationkey = '')
1868 {
1869 global $conf;
1870
1871 //var_dump($hmac_secret_key, $obfuscationmode, $obfuscationkey);exit;
1872
1873 if (preg_match('/^dolobfuscationv1/', $obfuscationmode) && empty($obfuscationkey)) {
1874 return -1;
1875 }
1876
1877 $name = 'BLOCKEDLOG_HMAC_KEY'; // The name of the key to save in database. $hmac_secret_key is the value to save and $obfuscationkey the key to obfuscate the value.
1878
1879 $this->db->begin();
1880
1881 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1882 $sql .= " WHERE name = '".$this->db->escape($name)."'";
1883 if ($this->entity >= 0) {
1884 $sql .= " AND entity = ".((int) $this->entity);
1885 }
1886
1887 dol_syslog("saveHMACSecretKey", LOG_DEBUG);
1888
1889 $resql = $this->db->query($sql);
1890
1891 if (preg_match('/^dolobfuscationv1/', $obfuscationmode)) {
1892 $newvalue = dolEncrypt($hmac_secret_key, $obfuscationkey, '', '', $obfuscationmode); // AES-256
1893 } else {
1894 // if ($obfuscationmode == 'dolcrypt')
1895 $newvalue = dolEncrypt($hmac_secret_key); // AES-256
1896 }
1897
1898 // Clear cache
1899 unset($conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)]);
1900
1901 // Save in database
1902 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1903 $sql .= " VALUES (";
1904 $sql .= "'".$this->db->escape($name)."'";
1905 $sql .= ", '".$this->db->escape($newvalue)."'";
1906 $sql .= ", 'chaine', '0', '', ".((int) $this->entity).")";
1907
1908 //print "xx".$db->escape($value);
1909 $resql = $this->db->query($sql);
1910
1911 if ($resql) {
1912 // Now reload it to check it was saved correctly for a paranoiac control.
1913 $saved = $this->getEncodedHMACSecretKey(); // This also reload the cache
1914
1915 if ($saved == $newvalue) {
1916 $this->db->commit();
1917
1918 return 1;
1919 } else {
1920 $this->error = 'Data read of HMAC key is not same than the one we expect to save.';
1921 $this->db->rollback();
1922
1923 return -1;
1924 }
1925 } else {
1926 $this->error = $this->db->lasterror();
1927 $this->db->rollback();
1928
1929 return -1;
1930 }
1931 }
1932
1933
1954 public function getObfuscationKey()
1955 {
1956 global $conf, $mysoc;
1957
1958
1959 // Uncomment the next line to emulate a network error to get the remote obfuscation key
1960 //throw new Exception('Failed to get the remote obfuscation key - error emulated');
1961
1962 // If key found into the user session memory cache, we use it
1963 if (!empty($_SESSION['obfuscationkey_'.((int) $this->entity)])) {
1964 dol_syslog("getObfuscationKey remote obfuscation key found into session cache", LOG_DEBUG);
1965 return (string) $_SESSION['obfuscationkey_'.((int) $this->entity)];
1966 }
1967 // If key found into the page memory cache, we use it
1968 if (!empty($conf->cache['obfuscationkey_'.((int) $this->entity)])) {
1969 dol_syslog("getObfuscationKey remote obfuscation key found into conf->cache", LOG_DEBUG);
1970 return (string) $conf->cache['obfuscationkey_'.((int) $this->entity)];
1971 }
1972
1973 $obfuscationkey = '';
1974
1975 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1976 $registrationnumber = getHashUniqueIdOfRegistration();
1977
1978 // Value is not into cache, we must get it from ping.dolibarr.org
1979 $obfuscationkey = callApiToGetObfuscationKey((string) $mysoc->idprof1, $registrationnumber);
1980 if (empty($obfuscationkey)) {
1981 dol_syslog("getObfuscationKey Failed to get the obfuscation key from ping.dolibarr.org (country='.$mysoc->country_code.', SIREN='.$mysoc->idprof1.'). May be the SIREN is not valid, the ping.dolibarr.org server is down or registration was not done (empty value returned). Re-try later.", LOG_DEBUG);
1982 throw new Exception('Error: Failed to get the obfuscation key from ping.dolibarr.org (country='.$mysoc->country_code.', SIREN='.$mysoc->idprof1.'). May be the SIREN is not valid, the ping.dolibarr.org server is down or registration was not done (empty value returned). Re-try later.');
1983 }
1984 if (strpos($obfuscationkey, 'ERROR') === 0) {
1985 dol_syslog('getObfuscationKey Error: Failed to get the obfuscation key from ping.dolibarr.org. May be the SIREN is not valid, the ping.dolibarr.org server is down or registration was not done (bad value returned). Re-try later. '.$obfuscationkey, LOG_DEBUG);
1986 throw new Exception('Error: Failed to get the obfuscation key from ping.dolibarr.org. May be the SIREN is not valid, the ping.dolibarr.org server is down or registration was not done (bad value returned). Re-try later. '.$obfuscationkey);
1987 }
1988
1989 // Now store value in cache ($obfuscationkey is not empty because of empty/throw above).
1990 $_SESSION['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1991 $conf->cache['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1992
1993 return (string) $obfuscationkey;
1994 }
1995
2004 public function getEncodedHMACSecretKey($nocache = 0, $noentity = 0)
2005 {
2006 global $conf;
2007
2008 $hmac_encoded_secret_key = '';
2009
2010 // Get value of the $hmac_encoded_secret_key from the database
2011 if ($nocache || empty($conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)])) {
2012 $sql = "SELECT value FROM ".MAIN_DB_PREFIX."const WHERE name = 'BLOCKEDLOG_HMAC_KEY'";
2013 if ($noentity) {
2014 $sql .= " AND entity IN (0)"; // To force to get value on old instances that may have been saved with entity = 0
2015 } else {
2016 $sql .= " AND entity IN (0, ".((int) $this->entity).")";
2017 }
2018 $sql .= " ORDER BY entity DESC LIMIT 1";
2019
2020 $resql = $this->db->query($sql);
2021 if ($resql) {
2022 $obj = $this->db->fetch_object($resql);
2023 if ($obj) {
2024 $hmac_encoded_secret_key = $obj->value;
2025
2026 // Save value in memory page cache (if we recall the same function in same page transaction, we will avoid db access).
2027 if (empty($nocache)) {
2028 $conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)] = $hmac_encoded_secret_key;
2029 }
2030 }
2031 } else {
2032 return 'ERROR '.$this->db->lasterror();
2033 }
2034 } else {
2035 $hmac_encoded_secret_key = $conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)];
2036 }
2037
2038 return $hmac_encoded_secret_key;
2039 }
2040
2048 public function getClearHMACSecretKey($hmac_encoded_secret_key)
2049 {
2050 // Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
2051 $hmac_secret_key = '';
2052 $errormsg = '';
2053
2054 // Get the obfuscation key from ping.dolibarr.org (to be used just after to decode HMAC secret key)
2055 if (preg_match('/^dolobfuscation/', $hmac_encoded_secret_key)) {
2056 $obfuscationkey = '';
2057 try {
2058 $obfuscationkey = $this->getObfuscationKey(); // Get obfuscation key providing $mysoc->idprof1 and $registrationnumber. Note: On network trouble, an Exception is thrown to the caller
2059 } catch (Exception $e) {
2060 $errormsg = $e->getMessage();
2061 }
2062 if (!$errormsg && $obfuscationkey) {
2063 // Decode the encrypted parameter using the obfuscation key to get the HMAC key in memory.
2064 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key, $obfuscationkey);
2065 }
2066 }
2067
2068 if (preg_match('/^dolcrypt/', $hmac_encoded_secret_key)) {
2069 // Failed to get the clear HMAC value. May be we are using an old obfuscated HMAC key, so we retry with the old method (used by webhosting providers using the attestation with old versions).
2070 // We test this. Note: we force a migration of data to use the new storage if this is the case in method buildFinalSignatureHash().
2071 // Example with the old demo sample database:
2072 // dolcrypt:AES-256-CTR:46cb611f00c4cff8:XVfEh15vX/JOYmpiw2QPNamcTQwdbBZJTcXBh9rMpzYJOpVPZubIWcgA8wHMXA==
2073 // instance_unique_id=11f3c81e86fc9e3b3fd11d81c9a31bd0
2074 // HMAC key=BLOCKEDLOGHMACY3Ewx37RXbSd8gL9JV8p7Wqw7qvq2K2A
2075
2076 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key); // Decode the encrypted parameter using the obfuscation key from ping.dolibarr.org to decode HMAC key
2077 }
2078
2079 if (preg_match('/^BLOCKEDLOGHMAC/', $hmac_encoded_secret_key)) {
2080 // If key was not encoded in db yet (old version before migration)
2081 $hmac_secret_key = $hmac_encoded_secret_key;
2082 }
2083
2084 if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
2085 throw new Exception('getClearHMACSecretKey Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY '.$hmac_encoded_secret_key.' using the decrypt or obfuscation key. A value was found in database but decoding failed. May be you modified the SIREN used to get the obfuscation key from ping.dolibarr.org (or old config key $dolibarr_main_instance_unique_id).'.($errormsg ? ' Additional message: '.$errormsg : ''));
2086 }
2087
2088 return $hmac_secret_key;
2089 }
2090
2098 public function getPreviousHash($withlock = 0, $beforeid = 0)
2099 {
2100 global $conf;
2101
2102 $previousid = 0;
2103 $previoussignature = '';
2104 $previousdatecreation = 0;
2105
2106 // Fast search of previous record by searching with beforeid - 1. This is very fast and will work 99% of time.
2107 if ($beforeid) {
2108 $sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
2109 $sql .= " WHERE entity = ".((int) $conf->entity);
2110 $sql .= " AND rowid = ".((int) $beforeid - 1);
2111 $sql .= ($withlock ? " FOR UPDATE " : ""); // To be sure transaction the get last hash to generate the next one will be unlocked once transaction to create new record is finished
2112
2113 $resql = $this->db->query($sql);
2114 if ($resql) {
2115 $obj = $this->db->fetch_object($resql);
2116 if ($obj) {
2117 $previousid = $obj->rowid;
2118 $previoussignature = $obj->signature;
2119 $tz = 'gmt';
2120 if (empty($obj->object_format) || $obj->object_format == 'V1') {
2121 $tz = 'tzserver';
2122 }
2123 $previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
2124 }
2125 } else {
2126 dol_print_error($this->db);
2127 exit;
2128 }
2129 }
2130
2131 if (empty($previoussignature)) {
2132 dol_syslog("getPreviousHash: We did not found previous record with fast mode so we search with a select max", LOG_DEBUG);
2133
2134 // Note: a select max rowid and then a select to get signature seems not faster due to filter on entity
2135 $sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
2136 if ($beforeid) {
2137 $sql .= $this->db->hintindex('entity_rowid', 1);
2138 }
2139 $sql .= " WHERE entity = ".((int) $conf->entity);
2140 if ($beforeid) {
2141 $sql .= " AND rowid < ".(int) $beforeid;
2142 }
2143 $sql .= " ORDER BY rowid DESC LIMIT 1";
2144 $sql .= ($withlock ? " FOR UPDATE " : "");
2145
2146 $resql = $this->db->query($sql);
2147 if ($resql) {
2148 $obj = $this->db->fetch_object($resql);
2149 if ($obj) {
2150 $previousid = $obj->rowid;
2151 $previoussignature = $obj->signature;
2152 $tz = 'gmt';
2153 if (empty($obj->object_format) || $obj->object_format == 'V1') {
2154 $tz = 'tzserver';
2155 }
2156 $previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
2157 }
2158 } else {
2159 dol_print_error($this->db); // can happen after a deadlock when too many requests do create into blocked log happen at the same time.
2160 http_response_code(503);
2161 exit;
2162 }
2163 }
2164
2165 if (empty($previoussignature)) {
2166 // First signature line (line 0)
2167 $previousid = 0;
2168 $previoussignature = $this->getOrInitFirstSignature();
2169 }
2170
2171 return array('previousid' => $previousid, 'previoushash' => $previoussignature, 'previousdatecreation' => $previousdatecreation);
2172 }
2173
2180 public function getNextRecord($rowidafter = 0)
2181 {
2182 global $conf;
2183
2184 $nextrecord = array('id' => 0, 'date' => 0, 'signature' => '');
2185
2186 // Get next record
2187 $sql = "SELECT rowid, date_creation, signature FROM ".MAIN_DB_PREFIX."blockedlog";
2188 $sql .= " WHERE entity = ".((int) $conf->entity);
2189 $sql .= " AND rowid > ".((int) $rowidafter);
2190 $sql .= " ORDER BY rowid ASC LIMIT 1";
2191
2192 $resql = $this->db->query($sql);
2193 $obj = $this->db->fetch_object($resql);
2194 if ($obj) {
2195 $nextrecord['id'] = $obj->rowid;
2196 $nextrecord['date'] = $this->db->jdate($obj->date_creation, 'gmt');
2197 $nextrecord['signature'] = $obj->signature;
2198 }
2199
2200 return $nextrecord;
2201 }
2202
2208 public function getLastRecord()
2209 {
2210 global $conf;
2211
2212 $lastrecord = array('id' => 0, 'date' => 0, 'signature' => '');
2213
2214 // Get last line
2215 $sql = "SELECT rowid, date_creation, signature FROM ".MAIN_DB_PREFIX."blockedlog";
2216 $sql .= " WHERE entity = ".((int) $conf->entity);
2217 $sql .= " ORDER BY rowid DESC LIMIT 1";
2218 $resql = $this->db->query($sql);
2219 $obj = $this->db->fetch_object($resql);
2220 if ($obj) {
2221 $lastrecord['id'] = $obj->rowid;
2222 $lastrecord['date'] = $this->db->jdate($obj->date_creation, 'gmt');
2223 $lastrecord['signature'] = $obj->signature;
2224 }
2225
2226 return $lastrecord;
2227 }
2228
2249 public function getLog($element, $fk_object, $limit = 0, $sortfield = '', $sortorder = '', $search_fk_user = -1, $search_start = -1, $search_end = -1, $search_ref = '', $search_amount = '', $search_code = '', $search_signature = '', $search_module_source = '', $search_pos_source = '', $search_type_code = '')
2250 {
2251 global $conf;
2252 //global $cachedlogs;
2253
2254 /* $cachedlogs allow fastest search */
2255 //if (empty($cachedlogs)) $cachedlogs = array();
2256
2257 if ($element == 'all') {
2258 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2259 WHERE entity = ".((int) $conf->entity);
2260 } elseif ($element == 'not_certified') {
2261 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2262 WHERE entity = ".((int) $conf->entity)." AND certified = 0";
2263 } elseif ($element == 'just_certified') {
2264 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2265 WHERE entity = ".((int) $conf->entity)." AND certified = 1";
2266 } else {
2267 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2268 WHERE entity = ".((int) $conf->entity)." AND element = '".$this->db->escape($element)."'";
2269 }
2270
2271 if ($fk_object) {
2272 $sql .= natural_search("rowid", (string) $fk_object, 1);
2273 }
2274 if ($search_fk_user > 0) {
2275 $sql .= natural_search("fk_user", (string) $search_fk_user, 2);
2276 }
2277 if ($search_start > 0) {
2278 $sql .= " AND date_creation >= '".$this->db->idate($search_start, 'gmt')."'";
2279 }
2280 if ($search_end > 0) {
2281 $sql .= " AND date_creation <= '".$this->db->idate($search_end, 'gmt')."'";
2282 }
2283 if ($search_type_code) {
2284 $sql .= natural_search("type_code", (string) $search_type_code);
2285 }
2286 if ($search_ref != '') {
2287 $sql .= " AND (".natural_search("ref_object", $search_ref, 0, 1);
2288 $sql .= " OR ".natural_search("linktoref", $search_ref, 0, 1).")";
2289 }
2290 if ($search_amount != '') {
2291 $sql .= natural_search("amounts", $search_amount, 1);
2292 }
2293 if ($search_signature != '') {
2294 $sql .= natural_search("signature", $search_signature, 0);
2295 }
2296 if (is_array($search_code)) {
2297 if (!empty($search_code)) {
2298 if (in_array('PAYMENT_CUSTOMER', $search_code)) { // If we ask codes PAYMENT_CUSTOMER, it means both PAYMENT_CUSTOMER_CREATE and PAYMENT_CUSTOMER_DELETE
2299 $search_code[] = 'PAYMENT_CUSTOMER_CREATE';
2300 $search_code[] = 'PAYMENT_CUSTOMER_DELETE';
2301 }
2302
2303 $sql .= natural_search("action", implode(',', $search_code), 3);
2304 }
2305 } else {
2306 if ($search_code != '' && $search_code != '-1') {
2307 $sql .= natural_search("action", $search_code, 3);
2308 }
2309 }
2310 if (is_array($search_module_source)) {
2311 if (!empty($search_module_source)) {
2312 $sql .= " AND (";
2313 if (in_array('0', $search_module_source)) {
2314 $sql .= "module_source = ''";
2315 unset($search_module_source[0]);
2316 if (!empty($search_module_source)) {
2317 $sql .= " OR ";
2318 }
2319 }
2320 if (!empty($search_module_source)) {
2321 $sqlTmp = natural_search("module_source", implode(',', $search_module_source), 0, 1);
2322 $sqlTmp = str_replace('%backoffice%', '', $sqlTmp);
2323 $sql .= $sqlTmp;
2324 }
2325 $sql .= " OR module_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different origins (pos and not pos)
2326 $sql .= ")";
2327 }
2328 } else {
2329 if ($search_module_source != '' && $search_module_source != '-1') {
2330 $sql .= natural_search("module_source", $search_module_source, 3);
2331 }
2332 }
2333 if ($search_pos_source != '') {
2334 $sql .= " AND (";
2335 $sql .= natural_search("pos_source", $search_pos_source, 0, 1);
2336 $sql .= " OR pos_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different terminal (pos and not pos)
2337 $sql .= ")";
2338 }
2339
2340 $sql .= $this->db->order($sortfield, $sortorder);
2341 $sql .= $this->db->plimit($limit + 1); // We want more, because we will stop into loop later with error if we reach max
2342
2343 $res = $this->db->query($sql);
2344 if ($res) {
2345 $results = array();
2346
2347 $i = 0;
2348 while ($obj = $this->db->fetch_object($res)) {
2349 $i++;
2350 if ($i > $limit) {
2351 // Too many record, we will consume too much memory
2352 return -2;
2353 }
2354
2355 //if (!isset($cachedlogs[$obj->rowid]))
2356 //{
2357 $b = new BlockedLog($this->db);
2358 $result = $b->fetch($obj->rowid);
2359 //$b->loadTrackedEvents();
2360 //$cachedlogs[$obj->rowid] = $b;
2361 //}
2362
2363 //$results[] = $cachedlogs[$obj->rowid];
2364 if ($result < 0) {
2365 $this->error = $b->error;
2366 $this->errors = $b->errors;
2367 return -1;
2368 }
2369
2370 $results[] = $b;
2371 }
2372
2373 return $results;
2374 }
2375
2376 return -1;
2377 }
2378
2384 public function getOrInitFirstSignature()
2385 {
2386 global $db, $conf;
2387
2388 if (!getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT')) { // creation of a unique fingerprint
2389 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
2390 require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
2391 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
2392
2393 $fingerprint = bin2hex(random_bytes(32)); // 64 char hex
2394
2395 dolibarr_set_const($db, 'BLOCKEDLOG_ENTITY_FINGERPRINT', $fingerprint, 'chaine', 0, 'Initial signature fingerprint', $conf->entity);
2396
2397 $conf->global->BLOCKEDLOG_ENTITY_FINGERPRINT = $fingerprint;
2398 }
2399
2400 return getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT');
2401 }
2402
2403
2410 public function alreadyUsed($ignoresystem = 0)
2411 {
2412 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2413 return isBlockedLogUsed($ignoresystem);
2414 }
2415
2416
2422 public function canBeEnabled()
2423 {
2424 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2425 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
2426
2427 $isqualified = isALNEQualifiedVersion(0, 1);
2428
2429 if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && !isHTTPS()) {
2430 return 'Error: The HTTPS must be enabled to allow the use of this module in France.';
2431 }
2432
2433 return '';
2434 }
2435
2436
2442 public function canBeDisabled()
2443 {
2444 global $mysoc;
2445
2446 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2447
2448 $isqualified = isALNEQualifiedVersion();
2449
2450 $canbedisabled = 1;
2451 // For france, we can never disable the module (except in debug mode)
2452 if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && $mysoc->country_code == 'FR') {
2453 $canbedisabled = 0;
2454 }
2455
2456 return $canbedisabled;
2457 }
2458
2459
2465 public function countRecord()
2466 {
2467 $nb = 0;
2468
2469 $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."blockedlog";
2470 $resql = $this->db->query($sql);
2471 if ($resql) {
2472 $obj = $this->db->fetch_object($resql);
2473 $nb = $obj->nb;
2474 } else {
2475 dol_print_error($this->db);
2476 }
2477 $this->db->free($resql);
2478
2479 return $nb;
2480 }
2481}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
dolibarr_set_const($db, $name, $value, $type='chaine', $visible=0, $note='', $entity=1)
Insert a parameter (key,value) into database (delete old key then insert it again).
isALNEQualifiedVersion($ignoredev=0, $ignoremodule=0)
Return if the version is a candidate version to get the LNE certification and if the prerequisites ar...
isBlockedLogUsed($ignoresystem=0)
Return if the blocked log was already used to block some events.
getHashUniqueIdOfRegistration($algo='sha256')
Return a hash unique identifier of the registration (used to identify the registration of instance wi...
isALNERunningVersion($blockedlogusagealreadychecked=0, $blockedlogmoduleonalreadychecked=0)
Return if the application is executed with the LNE requirements on.
callApiToGetObfuscationKey($idprof1, $registrationnumber, $force=false)
Call remote API service to get the obfuscation key.
Class to manage Blocked Log.
canBeEnabled()
Check if module can be enabled.
getObjectLink()
Try to retrieve source object (it it still exists).
getNextRecord($rowidafter=0)
Return the last record in blocked log.
getOrInitFirstSignature()
Return the signature (hash) of the "genesis-block" (Block 0).
alreadyUsed($ignoresystem=0)
Check if module was already used or not for at least one recording.
canBeDisabled()
Check if module can be disabled.
create($user, $forcesignature='')
Create blocked log in database.
getClearHMACSecretKey($hmac_encoded_secret_key)
Get the HMAC secret key.
countRecord()
Return current number of records.
loadTrackedEvents()
Load list of tracked and controlled events into $this->controlled, $this->trackedevents and $this->tr...
getLog($element, $fk_object, $limit=0, $sortfield='', $sortorder='', $search_fk_user=-1, $search_start=-1, $search_end=-1, $search_ref='', $search_amount='', $search_code='', $search_signature='', $search_module_source='', $search_pos_source='', $search_type_code='')
Return array of unalterable log objects (filtered with criteria)
saveHMACSecretKey($hmac_secret_key, $obfuscationmode, $obfuscationkey='')
Save the HMAC secret key into database.
setObjectData(&$object, $action, $amounts, $fuser=null, $amounts_taxexcl=null)
Populate properties of an unalterable log entry from object data.
__construct(DoliDB $db)
Constructor.
getEncodedHMACSecretKey($nocache=0, $noentity=0)
Get the encoded HMAC secret key.
buildKeyForSignature($format='')
Return the string for signature (clear data).
buildFirstPartOfKeyForSignature($format='')
Return first part of string for signature (clear data) Note: rowid of line not included as it is not ...
getEndOfChainFlagFile()
Return path of end of chain flag file.
getPreviousHash($withlock=0, $beforeid=0)
Get previous signature/hash in chain.
dolEncodeBlockedData($data, $mode=1)
Encode data.
checkSignature($previoushash='', $returnarray=0)
Check if calculated signature still correct compared to the value in the chain.
getObfuscationKey()
Return the remote obfuscation key from ping.dolibarr.org (used later to decode HMAC secret key).
dolDecodeBlockedData($data, $mode=0)
Decode data.
getLastRecord()
Return the last record in blocked log.
fetch($id)
Get object from database.
getUser()
Try to retrieve user author.
setCertified()
Set block certified by an external authority.
buildFinalSignatureHash($clearstring, $format='')
Return a hash that is the signature of a line data $clearstring (hash_hmac SHA256 of data + secret ke...
Class to manage cash fence.
Class to manage Dolibarr database access.
Class to manage donations.
Definition don.class.php:41
Class to manage suppliers invoices.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage invoices.
const TYPE_CREDIT_NOTE
Credit note invoice.
Class to manage stock movements.
Class to manage payments for supplier invoices.
Class to manage payments of customer invoices.
Class to manage payments of donations.
Class to manage various payments.
Class to manage projects.
Class to manage subscriptions of foundation members.
Class to manage Dolibarr users.
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_now($mode='gmt')
Return date for now.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
getDolEntity()
Return the current entity.
jsonOrUnserialize($stringtodecode, $assoc=true)
Decode an encoded string.
dolChmod($filepath, $newmask='')
Change mod of a file.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.
isHTTPS()
Return if we are using a HTTPS connection Check HTTPS (no way to be modified by user but may be empty...
dolEncrypt($chain, $key='', $ciphering='', $forceseed='', $obfuscationmode='dolcrypt')
Encode a string with a symmetric encryption.