dolibarr 24.0.1
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_previous', 'ref_next',
621 'origin', 'origin_id',
622 'oldcopy', 'picto', 'error', 'errors',
623 'model_pdf', 'modelpdf', 'last_main_doc', 'civility_id', 'contact', 'contact_id',
624 'table_element_line', 'ismultientitymanaged', 'isextrafieldmanaged',
625 'array_languages',
626 'childtables',
627 'contact_ids',
628 'context',
629 'element',
630 'labelStatus',
631 'labelStatusShort',
632 'linkedObjectsIds',
633 'linkedObjects',
634 'fk_delivery_address',
635 'projet', // There is already ->fk_project
636 'restrictiononfksoc',
637 'specimen',
638 );
639
640 // Add more fields to exclude depending on object type
641 if ($this->element == 'cashcontrol') {
642 $arrayoffieldstoexclude = array_merge(
643 $arrayoffieldstoexclude,
644 array(
645 'name', 'lastname', 'firstname', 'region', 'region_id', 'region_code', 'state', 'state_id', 'state_code', 'country', 'country_id', 'country_code',
646 'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
647 'barcode_type', 'barcode_type_code', 'barcode_type_label', 'barcode_type_coder', 'mode_reglement_id', 'cond_reglement_id', 'mode_reglement', 'cond_reglement', 'shipping_method_id',
648 'extraparams', 'fk_incoterms', 'fk_user_creat', 'fk_user_valid', 'label_incoterms', 'location_incoterms', 'lines', 'nb', 'tms', 'comments', 'array_options', 'warnings',
649 'opening', 'status', 'date_valid'
650 )
651 );
652 }
653
654 // For customer payment and supplier payment, the thirdparty can be added in payment detail
655 $addthirdpartyatpaymentlevel = 0;
656 if (!empty($object->thirdparty) && in_array($this->element, array('payment', 'payment_supplier'))) {
657 $addthirdpartyatpaymentlevel = 1;
658 }
659
660 if (!empty($object->thirdparty) && !$addthirdpartyatpaymentlevel) { // If $addthirdpartyatpaymentlevel is set, we will add thirdparty on payments later.
661 $this->object_data->thirdparty = new stdClass();
662
663 foreach ($object->thirdparty as $key => $value) {
664 if (in_array($key, $arrayoffieldstoexclude)) {
665 continue; // Discard some properties
666 }
667 // List of fields qualified
668 if (!in_array($key, array(
669 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
670 'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax2_assuj', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
671 ))) {
672 continue; // Discard if not into this dedicated list
673 }
674
675 $valuequalifiedforstorage = false;
676 if (!is_object($value)) {
677 if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
678 $valuequalifiedforstorage = true; // We accept '' value for some fields
679 $value = (string) $value;
680 }
681 if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
682 $valuequalifiedforstorage = true; // We accept zero value for amounts
683 }
684 if (!is_null($value) && (string) $value !== '') {
685 $valuequalifiedforstorage = true;
686 }
687 }
688
689 if ($valuequalifiedforstorage) {
690 $this->object_data->thirdparty->$key = $value;
691 }
692 }
693 }
694
695 // Add my company info (Only for customer invoice and payment)
696 if (!empty($mysoc) && in_array($object->element, array('facture', 'paiement'))) {
697 $this->object_data->mycompany = new stdClass();
698
699 foreach ($mysoc as $key => $value) {
700 if (in_array($key, $arrayoffieldstoexclude)) {
701 continue; // Discard some properties
702 }
703 // List of fields qualified to keep
704 if (!in_array($key, array(
705 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
706 'tva_assuj', 'tva_intra', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
707 ))) {
708 continue; // Discard if not into this dedicated list
709 }
710
711 $valuequalifiedforstorage = false;
712 if (!is_object($value)) {
713 if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
714 $valuequalifiedforstorage = true; // We accept '' value for some fields
715 $value = (string) $value;
716 }
717 if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
718 $valuequalifiedforstorage = true; // We accept zero value for amounts
719 }
720 if (!is_null($value) && (string) $value !== '') {
721 $valuequalifiedforstorage = true;
722 }
723 }
724
725 if ($valuequalifiedforstorage) {
726 $this->object_data->mycompany->$key = $value;
727 }
728 }
729 }
730
731 // Add user info
732 if (!empty($user)) {
733 $this->fk_user = $user->id;
734 $this->user_fullname = $user->getFullName($langs);
735 }
736
737 // Field specific to object
738 if ($this->element == 'facture') {
739 '@phan-var-force Facture $object';
740 $this->module_source = (string) $object->module_source;
741 $this->pos_source = (string) $object->pos_source;
742
743 foreach ($object as $key => $value) {
744 if (in_array($key, $arrayoffieldstoexclude)) {
745 continue; // Discard some properties
746 }
747 // List of fields qualified
748 if (!in_array($key, array(
749 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'datev', 'type',
750 //'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx', There is no rate at full doc level
751 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2',
752 'revenuestamp', 'datepointoftax', 'note_public',
753 'lines',
754 'module_source', 'pos_source', 'pos_print_counter', 'email_sent_counter'
755 ))) {
756 continue; // Discarded if not into the dedicated list
757 }
758 if ($key == 'lines') {
759 $lineid = 0;
760 foreach ($value as $tmpline) { // $tmpline is object FactureLine
761 $lineid++;
762 foreach ($tmpline as $keyline => $valueline) {
763 if (!in_array($keyline, array(
764 'ref', 'product_type', 'product_label',
765 'qty', 'subprice',
766 'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx',
767 'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
768 'multicurrency_code', 'multicurrency_total_ht', 'multicurrency_total_tva', 'multicurrency_total_ttc',
769 'info_bits', 'special_code', 'remise_percent'
770 ))) {
771 continue; // Discard if not into a dedicated list
772 }
773
774 if (empty($this->object_data->invoiceline[$lineid]) || !is_object($this->object_data->invoiceline[$lineid])) { // To avoid warning
775 $this->object_data->invoiceline[$lineid] = new stdClass();
776 }
777
778 $valuequalifiedforstorage = false;
779 if (!is_object($valueline)) {
780 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'))) {
781 $valuequalifiedforstorage = true; // We accept zero value for amounts
782 }
783 if (!is_null($valueline) && (string) $valueline !== '') {
784 $valuequalifiedforstorage = true;
785 }
786 }
787 if ($keyline == 'product_label' && empty($valueline)) {
788 $valueline = dol_trunc(dolGetFirstLineOfText($tmpline->desc)); // Fallback on description if label is empty
789 $valuequalifiedforstorage = true;
790 }
791
792 if ($valuequalifiedforstorage) {
793 $this->object_data->invoiceline[$lineid]->$keyline = $valueline;
794 }
795 }
796 }
797 } else {
798 $valuequalifiedforstorage = false;
799 if (!is_object($value)) {
800 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
801 $valuequalifiedforstorage = true; // We accept '' value for some fields
802 $value = (string) $value;
803 }
804 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
805 $valuequalifiedforstorage = true; // We accept zero value for amounts
806 }
807 if (!is_null($value) && (string) $value !== '') {
808 $valuequalifiedforstorage = true;
809 }
810 }
811
812 if ($valuequalifiedforstorage) {
813 $this->object_data->$key = $value;
814 }
815 }
816 }
817
818 if (!empty($object->newref)) {
819 $this->object_data->ref = $object->newref;
820 }
821
822 // Add data for action emails
823 if ($action == 'BILL_SENTBYMAIL') {
824 $this->object_data->action_email_sent = array(
825 "email_from" => $object->context['email_from'],
826 "email_to" => $object->context['email_to'],
827 "email_msgid" => $object->context['email_msgid']
828 );
829 }
830 } elseif ($this->element == 'invoice_supplier') {
831 '@phan-var-force FactureFournisseur $object';
832 foreach ($object as $key => $value) {
833 if (in_array($key, $arrayoffieldstoexclude)) {
834 continue; // Discard some properties
835 }
836 // List of fields qualified
837 if (!in_array($key, array(
838 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public'
839 ))) {
840 continue; // Discard if not into a dedicated list
841 }
842
843 $valuequalifiedforstorage = false;
844 if (!is_object($value)) {
845 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
846 $valuequalifiedforstorage = true; // We accept '' value for some fields
847 $value = (string) $value;
848 }
849 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
850 $valuequalifiedforstorage = true; // We accept zero value for amounts
851 }
852 if (!is_null($value) && (string) $value !== '') {
853 $valuequalifiedforstorage = true;
854 }
855 }
856
857 if ($valuequalifiedforstorage) {
858 $this->object_data->$key = $value;
859 }
860 }
861
862 if (!empty($object->newref)) {
863 $this->object_data->ref = $object->newref;
864 }
865 } elseif ($this->element == 'payment' || $this->element == 'payment_supplier' || $this->element == 'payment_donation' || $this->element == 'payment_various') {
866 '@phan-var-force Paiement|PaiementFourn|PaymentDonation|PaymentVarious $object';
867 $datepayment = $object->datepaye ? $object->datepaye : ($object->datepaid ? $object->datepaid : $object->datep);
868 $paymenttypeid = $object->paiementid ? $object->paiementid : ($object->paymenttype ? $object->paymenttype : $object->type_payment);
869
870 $this->object_data->ref = $object->ref;
871 $this->object_data->date = $datepayment;
872 $this->object_data->type_code = dol_getIdFromCode($this->db, $paymenttypeid, 'c_paiement', 'id', 'code');
873
874 if (!empty($object->num_payment)) {
875 $this->object_data->payment_num = $object->num_payment;
876 }
877 if (!empty($object->note_private)) {
878 $this->object_data->note_private = $object->note_private;
879 }
880 //$this->object_data->fk_account = $object->fk_account;
881 //var_dump($this->object_data);exit;
882
883 $totalamount = 0;
884
885 $this->type_code = $this->object_data->type_code;
886 $this->linktype = $this->element;
887 $this->linktoref = '';
888
889 // If payment and $object->amounts is empty (for example when we delete), we complete the information
890 if ($this->element == 'payment' && empty($object->amounts) && $object instanceof Paiement) {
891 $amountsarray = $object->getAmountsArray();
892 $object->amounts = $amountsarray;
893 // Invert the sign of amount into the array ->amounts if it is a deletion
894 if ($action == 'PAYMENT_CUSTOMER_DELETE') {
895 foreach ($object->amounts as $amountkey => $amountval) {
896 $object->amounts[$amountkey] = - $amountval;
897 }
898 }
899 }
900
901 // Loop on each invoice payment amount (the payment_part)
902 if (is_array($object->amounts) && !empty($object->amounts)) {
903 // Loop on each invoice the payment is part of to set the linktoref and the module_source and pos_source
904 $originofpayment = null;
905 $terminalofpayment = null;
906 $paymentpartnumber = 0;
907 foreach ($object->amounts as $objid => $amount) {
908 if (empty($amount)) {
909 continue;
910 }
911
912 $totalamount += $amount;
913
914 $tmpobject = null;
915 if ($this->element == 'payment_supplier') {
916 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
917 $tmpobject = new FactureFournisseur($this->db);
918 } elseif ($this->element == 'payment') {
919 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
920 $tmpobject = new Facture($this->db);
921 } elseif ($this->element == 'payment_donation') {
922 include_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php';
923 $tmpobject = new Don($this->db);
924 } elseif ($this->element == 'payment_various') {
925 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
926 $tmpobject = new PaymentVarious($this->db);
927 }
928
929 if (!is_object($tmpobject)) {
930 continue;
931 }
932
933 $result = $tmpobject->fetch($objid);
934
935 if ($result <= 0) {
936 $this->error = $tmpobject->error;
937 $this->errors = $tmpobject->errors;
938 dol_syslog("Failed to fetch object with id ".$objid, LOG_ERR);
939 return -1;
940 }
941
942 $this->linktoref .= ($this->linktoref ? ',' : '').$tmpobject->ref;
943
944 // Set the ->module_source of payment from origin object if relevant
945 if (property_exists($tmpobject, 'module_source')) {
946 if (is_null($originofpayment)) {
947 $originofpayment = (string) $tmpobject->module_source;
948 } elseif ($originofpayment != $tmpobject->module_source) {
949 $originofpayment = 'mix'; // the payment is on several invoices with different origins of module
950 } else {
951 $originofpayment = (string) $tmpobject->module_source;
952 }
953 }
954 // Set the ->pos_source of payment from origin object if relevant
955 if (property_exists($tmpobject, 'pos_source')) {
956 if (is_null($terminalofpayment)) {
957 $terminalofpayment = (string) $tmpobject->pos_source;
958 } elseif ($terminalofpayment != $tmpobject->pos_source) {
959 $terminalofpayment = 'mix'; // the payment is on several invoices with same origin of module but different terminals
960 } else {
961 $terminalofpayment = (string) $tmpobject->pos_source;
962 }
963 }
964 $paymentpart = new stdClass();
965 $paymentpart->amount = $amount;
966
967 // If we want to add thirdparty on each payment level
968 // (seems not necessary as we have one thirdparty per payment on invoice level)
969 if ($addthirdpartyatpaymentlevel) {
970 $result = $tmpobject->fetch_thirdparty();
971 if ($result == 0) {
972 $this->error = 'Failed to fetch thirdparty for object with id '.$tmpobject->id;
973 $this->errors[] = $this->error;
974 dol_syslog("Failed to fetch thirdparty for object with id ".$tmpobject->id, LOG_ERR);
975 return -1;
976 } elseif ($result < 0) {
977 $this->error = $tmpobject->error;
978 $this->errors = $tmpobject->errors;
979 return -1;
980 }
981
982 $paymentpart->thirdparty = new stdClass();
983 foreach ($tmpobject->thirdparty as $key => $value) {
984 if (in_array($key, $arrayoffieldstoexclude)) {
985 continue; // Discard some properties
986 }
987 // List of thirdparty fields qualified
988 if (!in_array($key, array(
989 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
990 'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
991 ))) {
992 continue; // Discard if not into a dedicated list
993 }
994 if (!is_object($value) && !is_null($value) && $value !== '') {
995 $paymentpart->thirdparty->$key = $value;
996 }
997 }
998 }
999
1000 // Init object to avoid warnings
1001 if ($this->element == 'payment_donation') {
1002 $paymentpart->donation = new stdClass();
1003 } elseif ($this->element == 'payment_various') {
1004 $paymentpart->various = new stdClass();
1005 } else {
1006 $paymentpart->invoice = new stdClass();
1007 }
1008
1009 if ($this->element != 'payment_various') {
1010 foreach ($tmpobject as $key => $value) {
1011 if (in_array($key, $arrayoffieldstoexclude)) {
1012 continue; // Discard some properties
1013 }
1014 // List of fields qualified
1015 if (!in_array($key, array(
1016 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public',
1017 'pos_source', 'module_source', 'pos_print_counter', 'email_sent_counter'
1018 ))) {
1019 continue; // Discard if not into a dedicated list
1020 }
1021
1022 $valuequalifiedforstorage = false;
1023 if (!is_object($value)) {
1024 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
1025 $valuequalifiedforstorage = true; // We accept '' value for some fields
1026 $value = (string) $value;
1027 }
1028 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
1029 $valuequalifiedforstorage = true; // We accept zero value for amounts
1030 }
1031 if (!is_null($value) && (string) $value !== '') {
1032 $valuequalifiedforstorage = true;
1033 }
1034 }
1035
1036 if ($valuequalifiedforstorage) {
1037 if ($this->element == 'payment_donation') {
1038 $paymentpart->donation->$key = $value;
1039 } elseif ($this->element == 'payment_various') {
1040 $paymentpart->various->$key = $value;
1041 } else {
1042 $paymentpart->invoice->$key = $value;
1043 }
1044 }
1045 }
1046
1047 $paymentpartnumber++; // first payment will be 1
1048 $this->object_data->payment_part[$paymentpartnumber] = $paymentpart;
1049 }
1050 }
1051
1052 $this->module_source = (string) $originofpayment;
1053 $this->pos_source = (string) $terminalofpayment;
1054 } elseif (!empty($object->amount)) {
1055 $totalamount = $object->amount;
1056 }
1057
1058 $this->object_data->amount = $totalamount;
1059
1060 if (!empty($object->newref)) {
1061 $this->object_data->ref = $object->newref;
1062 }
1063 } elseif ($this->element == 'payment_salary') {
1064 '@phan-var-force PaymentSalary $object';
1065 $this->object_data->amounts = array($object->amount);
1066
1067 if (!empty($object->newref)) {
1068 $this->object_data->ref = $object->newref;
1069 }
1070 } elseif ($this->element == 'subscription') {
1071 '@phan-var-force Subscription $object';
1072 foreach ($object as $key => $value) {
1073 if (in_array($key, $arrayoffieldstoexclude)) {
1074 continue; // Discard some properties
1075 }
1076 if (!in_array($key, array(
1077 'id', 'datec', 'dateh', 'datef', 'fk_adherent', 'amount', 'import_key', 'statut', 'note'
1078 ))) {
1079 continue; // Discard if not into a dedicated list
1080 }
1081 if (!is_object($value) && !is_null($value) && $value !== '') {
1082 $this->object_data->$key = $value;
1083 }
1084 }
1085
1086 if (!empty($object->newref)) {
1087 $this->object_data->ref = $object->newref;
1088 }
1089 } elseif ($this->element == 'stockmouvement') {
1090 '@phan-var-force StockTransfer $object';
1091 foreach ($object as $key => $value) {
1092 if (in_array($key, $arrayoffieldstoexclude)) {
1093 continue; // Discard some properties
1094 }
1095 if (!is_object($value) && !is_null($value) && $value !== '') {
1096 $this->object_data->$key = $value;
1097 }
1098 }
1099 } else {
1100 if ($object->element == 'cashcontrol') {
1101 $this->module_source = (string) $object->posmodule; // Module
1102 $this->pos_source = (string) $object->posnumber; // Terminal
1103 }
1104
1105 // Generic case
1106 foreach ($object as $key => $value) {
1107 if (in_array($key, $arrayoffieldstoexclude)) {
1108 continue; // Discard some properties
1109 }
1110 if (!is_object($value) && !is_null($value) && $value !== '') {
1111 $this->object_data->$key = $value;
1112 }
1113 }
1114
1115 if ($object->element == 'cashcontrol') {
1116 $period = $object->year_close;
1117 $period .= ($object->month_close ? "-".sprintf("%02d", $object->month_close) : "");
1118 $period .= ($object->day_close ? "-".sprintf("%02d", $object->day_close) : "");
1119
1120 $this->object_data->period = $period;
1121 }
1122
1123 if (!empty($object->newref)) {
1124 $this->object_data->ref = $object->newref;
1125 }
1126 }
1127
1128 // A trick to be sure all the object_data is an associative array
1129 // json_encode and json_decode are not able to manage mixed object (with array/object, only full arrays or full objects)
1130 $this->object_data = json_decode(json_encode($this->object_data, JSON_FORCE_OBJECT), false);
1131
1132 return 1;
1133 }
1134
1141 public function fetch($id)
1142 {
1143 global $langs;
1144
1145 if (empty($id)) {
1146 $this->error = 'BadParameter';
1147 return -1;
1148 }
1149
1150 $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,";
1151 $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,";
1152 $sql .= " b.note";
1153 $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog as b";
1154 $sql .= " WHERE b.rowid = ".((int) $id); // $id is not empty because of test above
1155
1156
1157 $resql = $this->db->query($sql);
1158 if ($resql) {
1159 $obj = $this->db->fetch_object($resql);
1160 if ($obj) {
1161 $this->id = $obj->rowid;
1162 $this->entity = $obj->entity;
1163
1164 // Must be at top
1165 $tz = 'gmt';
1166 if (empty($obj->object_format) || $obj->object_format == 'V1') {
1167 $tz = 'tzserver';
1168 }
1169
1170 $this->date_creation = $this->db->jdate($obj->date_creation, $tz); // jdate(date_creation)is UTC
1171 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
1172 $this->date_modification = $this->db->jdate($obj->tms, $tz); // jdate(tms) is UTC
1173
1174
1175 $this->action = $obj->action;
1176 $this->module_source = $obj->module_source;
1177 $this->pos_source = $obj->pos_source;
1178
1179 $this->amounts_taxexcl = (is_null($obj->amounts_taxexcl) ? null : (float) $obj->amounts_taxexcl);
1180 $this->amounts = (float) $obj->amounts;
1181
1182 $this->fk_object = $obj->fk_object;
1183 $this->date_object = $this->db->jdate($obj->date_object, $tz); // jdate(date_object) is UTC
1184 //var_dump($obj->date_object, dol_print_date($this->date_object, 'dayhour' , $tz));
1185 //exit;
1186
1187 $this->ref_object = $obj->ref_object;
1188 $this->type_code = $obj->type_code;
1189 $this->linktoref = $obj->linktoref;
1190 $this->linktype = $obj->linktype;
1191
1192 $this->fk_user = $obj->fk_user;
1193 $this->user_fullname = $obj->user_fullname;
1194
1195 $this->object_data = $this->dolDecodeBlockedData($obj->object_data);
1196 $this->object_version = $obj->object_version;
1197 $this->object_format = $obj->object_format;
1198
1199 $this->element = $obj->element;
1200
1201 $this->signature = $obj->signature;
1202 $this->certified = ($obj->certified == 1);
1203
1204 $this->note = $obj->note;
1205 //$this->debuginfo = $obj->debuginfo; // We don't need this, sot we don't load it to save memory.
1206
1207 return 1;
1208 } else {
1209 $langs->load("errors");
1210 $this->error = $langs->trans("ErrorRecordNotFound");
1211 return 0;
1212 }
1213 } else {
1214 $this->error = $this->db->error();
1215 return -1;
1216 }
1217 }
1218
1219
1227 public function dolEncodeBlockedData($data, $mode = 1)
1228 {
1229 $aaa = json_encode($data);
1230
1231 return $aaa;
1232 }
1233
1234
1242 public function dolDecodeBlockedData($data, $mode = 0)
1243 {
1244 $aaa = null;
1245 try {
1246 $aaa = (object) jsonOrUnserialize($data, false);
1247 } catch (Exception $e) {
1248 // print $e->getErrs);
1249 }
1250
1251 return $aaa;
1252 }
1253
1254
1260 public function setCertified()
1261 {
1262 $res = $this->db->query("UPDATE ".MAIN_DB_PREFIX."blockedlog SET certified = 1 WHERE rowid = ".((int) $this->id));
1263 if (!$res) {
1264 return false;
1265 }
1266
1267 return true;
1268 }
1269
1277 public function create($user, $forcesignature = '')
1278 {
1279 global $conf, $langs, $mysoc;
1280
1281 $langs->load('blockedlog');
1282
1283 // Clean data
1284 $this->amounts = (float) $this->amounts;
1285
1286 dol_syslog(get_class($this).'::create action='.$this->action.' fk_user='.$this->fk_user.' user_fullname='.$this->user_fullname, LOG_DEBUG);
1287
1288 // Check parameters/properties
1289 if (!isset($this->amounts)) { // amount can be 0 for some events (like when module is disabled)
1290 $langs->load("errors");
1291 $this->error = $langs->trans("ErrorBlockLogNeedAmountsValue");
1292 dol_syslog($this->error, LOG_WARNING);
1293 return -1;
1294 }
1295
1296 if (empty($this->element)) {
1297 $langs->load("errors");
1298 $this->error = $langs->trans("ErrorBlockLogNeedElement");
1299 dol_syslog($this->error, LOG_WARNING);
1300 return -2;
1301 }
1302
1303 if (empty($this->object_data)) {
1304 $langs->load("errors");
1305 $this->error = $langs->trans("ErrorBlockLogNeedObject");
1306 dol_syslog($this->error, LOG_WARNING);
1307 return -2;
1308 }
1309
1310 if (empty($this->action)) {
1311 $langs->load("errors");
1312 $this->error = $langs->trans("ErrorBadParameterWhenCallingCreateOfBlockedLog");
1313 dol_syslog($this->error, LOG_WARNING);
1314 return -3;
1315 }
1316 if (empty($this->fk_user)) {
1317 $this->user_fullname = '(Anonymous)';
1318 }
1319
1320 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
1321
1322 $this->db->begin();
1323
1324 $this->date_creation = dol_now();
1325
1326 $this->object_version = DOL_VERSION;
1327
1328 // The object_format defines the formatting rules and syntax into
1329 // buildKeyForSignature and buildFirstPartOfKeyForSignature and buildFinalSignatureHash
1330 // This may vary when the Immutable Log module version is modified, but only if algorithm has changed.
1331 $this->object_format = 'V2';
1332
1333 $tz = 'gmt';
1334
1335 $previoushash = '';
1336 $previousid = 0;
1337 $previousdatecreation = 0;
1338
1339 try {
1340 $tmparray = $this->getPreviousHash(1, 0); // This get last record and lock database until insert is done and transaction closed
1341
1342 $previoushash = $tmparray['previoushash'];
1343 $previousid = $tmparray['previousid'];
1344 $previousdatecreation = $tmparray['previousdatecreation'];
1345
1346 // The string of line to sign
1347 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1348
1349 // The new hash, including previous hash
1350 $this->signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1351
1352 // For debug info (we can clean this field later)
1353 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1354 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1355 }
1356 } catch (Exception $e) {
1357 $this->error = $e->getMessage();
1358
1359 dol_syslog($this->error, LOG_ERR);
1360
1361 $this->db->rollback();
1362 return -1;
1363 }
1364
1365 if ($forcesignature) {
1366 $this->signature = $forcesignature;
1367 }
1368 //var_dump($previoushash, $concatenateddata, $this->signature);
1369
1370 $sql = "INSERT INTO ".MAIN_DB_PREFIX."blockedlog (";
1371 $sql .= " date_creation,";
1372 $sql .= " action,";
1373 $sql .= " module_source,";
1374 $sql .= " pos_source,";
1375 $sql .= " amounts_taxexcl,";
1376 $sql .= " amounts,";
1377 $sql .= " signature,";
1378 $sql .= " element,";
1379 $sql .= " fk_object,";
1380 $sql .= " date_object,";
1381 $sql .= " ref_object,";
1382 $sql .= " type_code,";
1383 $sql .= " linktoref,";
1384 $sql .= " linktype,";
1385 $sql .= " object_data,";
1386 $sql .= " object_version,";
1387 $sql .= " object_format,";
1388 $sql .= " certified,";
1389 $sql .= " fk_user,";
1390 $sql .= " user_fullname,";
1391 $sql .= " entity,";
1392 $sql .= " debuginfo"; // Only stored
1393 $sql .= ") VALUES (";
1394 $sql .= "'".$this->db->idate($this->date_creation, $tz)."',";
1395 $sql .= "'".$this->db->escape($this->action)."',";
1396 $sql .= "'".$this->db->escape((string) $this->module_source)."',";
1397 $sql .= "'".$this->db->escape((string) $this->pos_source)."',";
1398 $sql .= (is_null($this->amounts_taxexcl) ? "null" : (float) $this->amounts_taxexcl).",";
1399 $sql .= (float) $this->amounts.",";
1400 $sql .= "'".$this->db->escape($this->signature)."',";
1401 $sql .= "'".$this->db->escape($this->element)."',";
1402 $sql .= (int) $this->fk_object.",";
1403 $sql .= "'".$this->db->idate($this->date_object, $tz)."',";
1404 $sql .= "'".$this->db->escape($this->ref_object)."',";
1405 $sql .= "'".$this->db->escape($this->type_code)."',";
1406 $sql .= ($this->linktoref ? "'".$this->db->escape($this->linktoref)."'" : "null").",";
1407 $sql .= ($this->linktype ? "'".$this->db->escape($this->linktype)."'" : "null").",";
1408 $sql .= "'".$this->db->escape($this->dolEncodeBlockedData($this->object_data))."',";
1409 $sql .= "'".$this->db->escape($this->object_version)."',";
1410 $sql .= "'".$this->db->escape($this->object_format)."',";
1411 $sql .= "0,";
1412 $sql .= ((int) $this->fk_user).",";
1413 $sql .= "'".$this->db->escape($this->user_fullname)."',";
1414 $sql .= ((int) ($this->entity ? ((int) $this->entity) : ((int) $conf->entity))).",";
1415 $sql .= "'".$this->db->escape($this->debuginfo)."'";
1416 $sql .= ")";
1417
1418 /*
1419 $a = serialize($this->object_data); $a2 = unserialize($a); $a4 = print_r($a2, true);
1420 $b = json_encode($this->object_data); $b2 = json_decode($b); $b4 = print_r($b2, true);
1421 var_dump($a4 == print_r($this->object_data, true) ? 'a=a' : 'a not = a');
1422 var_dump($b4 == print_r($this->object_data, true) ? 'b=b' : 'b not = b');
1423 exit;
1424 */
1425
1426 $res = $this->db->query($sql);
1427 if ($res) {
1428 $id = $this->db->last_insert_id(MAIN_DB_PREFIX."blockedlog");
1429
1430 if ($id > 0) {
1431 // The new ID
1432 $this->id = $id;
1433
1434 // Check and store the signature of this new line in the .end flag.
1435 try {
1436 $finalsignature = $this->signature;
1437 $finalnote = '';
1438
1439 $lockfile = $this->getEndOfChainFlagFile();
1440
1441 // Load the .end flag.
1442 // If not found (has been removed), we track the record as error.
1443 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1444 dol_mkdir(dirname($lockfile)); // Create at least directory for the lock file. Nothing if already exists.
1445
1446 if (!file_exists($lockfile)) {
1447 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1448
1449 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1450
1451 // The string of line to sign
1452 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1453
1454 // The new hash, including previous hash
1455 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1456 $finalnote = $this->note;
1457
1458 $line = '';
1459 } elseif (is_writable($lockfile)) {
1460 $line = file_get_contents($lockfile);
1461 } else {
1462 // Go to the catch()
1463 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.');
1464 }
1465 } else {
1466 $sql = "SELECT value from ".MAIN_DB_PREFIX."const";
1467 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1468 $resql = $this->db->query($sql);
1469 if ($resql) {
1470 $obj = $this->db->fetch_object($resql);
1471 if ($obj) {
1472 $line = $obj->value;
1473 } else {
1474 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1475
1476 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1477
1478 // The string of line to sign
1479 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1480
1481 // The new hash, including previous hash
1482 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1483 $finalnote = $this->note;
1484
1485 $line = '';
1486 }
1487 } else {
1488 // Go to the catch()
1489 throw new Exception("Cannot read into the blockedlog .end flag ".basename($lockfile).' to update it. Transaction aborted.');
1490 }
1491 }
1492
1493 // Check the .end flag file.
1494 $headstring = '';
1495 $remoteobfuscationkey = '';
1496 if (preg_match('/^dolcrypt/', $line)) { // Old method (does not happen after migration)
1497 $headstring = dolDecrypt($line);
1498 } elseif (preg_match('/^dolobfuscation/', $line)) {
1499 $remoteobfuscationkey = $this->getObfuscationKey();
1500 if (empty($remoteobfuscationkey)) {
1501 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.");
1502 }
1503 $headstring = dolDecrypt($line, $remoteobfuscationkey);
1504 }
1505
1506 $reg = array();
1507 if (preg_match('/^BLOCKEDLOGHEAD (\d+) ([^\s]+) ([a-zA-Z0-9\-]+)/', $headstring, $reg)) {
1508 // We succeed in decypting the head
1509 $previousidheadflag = $reg[1];
1510 $previousdatecreationheadflag = $reg[2];
1511 $previoushashheadflag = $reg[3];
1512
1513 // Check the signature of the previous line
1514 if ($previousid < $previousidheadflag || $previoushash != $previoushashheadflag) {
1515 // We detect that old record were removed. We force a non valid signature on the new record.
1516 $this->note = 'EndOfChainDeletionDetected ['.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').' - '.dol_print_date($previousdatecreationheadflag, 'dayhourrfc', 'gmt').']';
1517
1518 // The string of line to sign
1519 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1520
1521 // The new hash, including previous hash
1522 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1523 $finalnote = $this->note;
1524 }
1525 } elseif ($headstring != '') {
1526 // Failed to decrypt the head
1527 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.");
1528 }
1529
1530 // If a note has been added to track an anomaly (signature is also different in this case).
1531 if ($finalsignature != $this->signature) {
1532 // For debug info (we can clean this field later)
1533 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1534 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1535 }
1536
1537 // We update the record we have just inserted to record the new "anomaly" we have detected. Anomaly is also incrusted into the signature.
1538 $sql = "UPDATE ".MAIN_DB_PREFIX."blockedlog";
1539 $sql .= " SET signature = '".$this->db->escape($finalsignature)."',";
1540 $sql .= " note = '".$this->db->escape($finalnote)."',";
1541 $sql .= " debuginfo = '".$this->db->escape($this->debuginfo)."'";
1542 $sql .= " WHERE rowid = ".((int) $this->id);
1543 $resql = $this->db->query($sql);
1544 if (!$resql) {
1545 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.");
1546 }
1547 }
1548
1549
1550 // We can now write the new .end file
1551 $stringtowrite = 'BLOCKEDLOGHEAD '.$this->id." ".dol_print_date($this->date_creation, 'dayhourrfc', 'gmt')." ".(string) $finalsignature;
1552
1553 if (isALNERunningVersion(1, ($this->action == 'MODULE_SET' ? 1 : 0)) && $mysoc->country_code == 'FR') {
1554 $remoteobfuscationkey = $this->getObfuscationKey();
1555 if (empty($remoteobfuscationkey)) {
1556 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.");
1557 }
1558 $stringtowriteencoded = dolEncrypt($stringtowrite, $remoteobfuscationkey, '', '', 'dolobfuscationv1-'.$mysoc->idprof1.'-'.$this->id);
1559 } else {
1560 $stringtowriteencoded = dolEncrypt($stringtowrite, '', '', '', 'dolcrypt-'.$mysoc->idprof1.'-'.$this->id);
1561 }
1562
1563
1564 // Update or create the .end file.
1565 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1566 $lockhandle = fopen($lockfile, 'w+');
1567 if ($lockhandle) {
1568 if (fwrite($lockhandle, $stringtowriteencoded."\n") === false) {
1569 throw new Exception("Cannot write to the blockedlog .end file ".$lockfile);
1570 }
1571
1572 fclose($lockhandle); // Remove the lock
1573 dolChmod($lockfile);
1574 } else {
1575 throw new Exception("Cannot open for writing the blockedlog .end file ".$lockfile);
1576 }
1577 } else {
1578 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1579 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1580 $resql = $this->db->query($sql);
1581
1582 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1583 $sql .= " VALUES('".$this->db->escape(basename($lockfile))."', '".$this->db->escape($stringtowriteencoded)."', 'chaine', 0, 'Blockedlog end of chain flag', ".((int) $conf->entity).")";
1584 $resql = $this->db->query($sql);
1585 if (!$resql) {
1586 throw new Exception("Cannot update the blockedlog .end flag ".basename($lockfile));
1587 }
1588 }
1589 } catch (Exception $e) {
1590 $this->error = $e->getMessage();
1591
1592 dol_syslog($this->error, LOG_ERR);
1593
1594 $this->db->rollback();
1595 return -1;
1596 }
1597
1598 $this->db->commit();
1599
1600 // Call remote API service to record the last counter
1601 /*
1602 $error = 0;
1603
1604 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1605 try {
1606 $resultcall = callApiToPushCounter((int) $this->id, $this->signature, $this->date_creation, 0, (int) $previousid, $previoushash, $previousdatecreation);
1607 } catch (Exception $e) {
1608 $error++;
1609 $this->error = $e->getMessage();
1610 }
1611
1612 if (!$error) {
1613 return $this->id;
1614 } else {
1615 return -3;
1616 }
1617 */
1618 return $this->id;
1619 } else {
1620 $this->db->rollback();
1621 return -2;
1622 }
1623 } else {
1624 $this->error = $this->db->error();
1625 $this->db->rollback();
1626 return -1;
1627 }
1628
1629 // The commit or rollback will release the lock so app can insert other record now
1630 }
1631
1637 public function getEndOfChainFlagFile()
1638 {
1639 global $conf;
1640
1641 // Note: We must not use $conf->blockedlog->dir_output because we need this
1642 // function to work even when module not yet enabled.
1643 return DOL_DATA_ROOT.'/blockedlog/blockedlog-'.((int) $conf->entity).'.end';
1644 }
1645
1653 public function checkSignature($previoushash = '', $returnarray = 0)
1654 {
1655 if (empty($previoushash)) {
1656 $tmparray = $this->getPreviousHash(0, $this->id);
1657 $previoushash = $tmparray['previoushash'];
1658 }
1659
1660 $concatenateddata = '';
1661 $signature = '';
1662
1663 // Recalculate the signature
1664 try {
1665 // Build the string for the signature
1666 $concatenateddata = $this->buildKeyForSignature();
1667
1668 $signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata);
1669
1670 //var_dump($previoushash, $concatenateddata, $this->object_format, $signature);
1671 } catch (Exception $e) {
1672 $res = ($signature === $this->signature);
1673 $this->error = $e->getMessage();
1674
1675 dol_syslog($this->error, LOG_ERR);
1676
1677 if ($returnarray) {
1678 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'error' => $this->error);
1679 } else {
1680 return false;
1681 }
1682 }
1683
1684 $res = ($signature === $this->signature);
1685
1686 if (!$res) {
1687 $this->error = 'Signature KO';
1688 }
1689
1690 if ($returnarray) {
1691 if ($returnarray == 1) {
1692 unset($concatenateddata);
1693 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash);
1694 } else { // Consume much memory ($concatenateddata is a large var)
1695 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'keyforsignature' => $concatenateddata);
1696 }
1697 } else {
1698 unset($concatenateddata);
1699 return $res;
1700 }
1701 }
1702
1711 private function buildFirstPartOfKeyForSignature($format = '')
1712 {
1713 if (empty($format)) {
1714 $format = $this->object_format;
1715 }
1716
1717 // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1718 if ($format == '') {
1719 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1720 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1721 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1722 } elseif ($format == 'V2') {
1723 $s = $this->entity;
1724 $s .= '|'.$this->date_creation.'|'.$this->action.'|'.$this->module_source.'|'.$this->pos_source.'|'.$this->amounts_taxexcl;
1725 $s .= '|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1726 if ($this->type_code) {
1727 $s .= '|'.(string) $this->type_code;
1728 }
1729 $s .= '|'.(string) $this->linktoref;
1730 $s .= '|'.(string) $this->linktype;
1731 if ($this->note) {
1732 $s .= '|'.(string) $this->note;
1733 }
1734 return $s;
1735 } else {
1736 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1737 }
1738 }
1739
1746 public function buildKeyForSignature($format = '')
1747 {
1748 //print_r($this->object_data);
1749 if (empty($format)) {
1750 $format = $this->object_format;
1751 }
1752
1753 if ($format == '') {
1754 return $this->buildFirstPartOfKeyForSignature($format).'|'.print_r($this->object_data, true);
1755 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1756 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1757 } elseif ($format == 'V2') {
1758 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1759 } else {
1760 throw new Exception('Error bad value "'.$format.'" for object_format');
1761 }
1762 }
1763
1771 private function buildFinalSignatureHash($clearstring, $format = '')
1772 {
1773 global $mysoc;
1774
1775 if (empty($format)) {
1776 $format = $this->object_format;
1777 }
1778
1779 if ($format == '') {
1780 return dol_hash($clearstring, '5');
1781 } elseif ($format == 'V1') {
1782 return dol_hash($clearstring, '5');
1783 } elseif ($format == 'V2') {
1784 // BLOCKEDLOG_HMAC_KEY is a HMAC key starting with 'BLOCKEDLOGHMAC....'. It is not stored as a clear data but
1785 // is a string dolcrypt:... or dolobfuscationv1... It will be decrypted later.
1786 $hmac_encoded_secret_key = $this->getEncodedHMACSecretKey();
1787
1788 if (empty($hmac_encoded_secret_key)) {
1789 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');
1790 }
1791
1792 // Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
1793 $hmac_secret_key = '';
1794 try {
1795 $hmac_secret_key = $this->getClearHMACSecretKey($hmac_encoded_secret_key); // Note: On network trouble, an Exception is thrown to the caller
1796 } catch (Exception $e) {
1797 $firsterrormessage = $e->getMessage();
1798
1799 // Another chance to get HMAC when saved with old obfuscation method (dolcrypt)
1800 $hmac_encoded_secret_key_alt = $this->getEncodedHMACSecretKey(1, 1);
1801 if (!empty($hmac_encoded_secret_key_alt)) {
1802 try {
1803 $hmac_secret_key_alt = $this->getClearHMACSecretKey($hmac_encoded_secret_key_alt); // Note: On network trouble, an Exception is thrown to the caller
1804 if (preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key_alt)) { // Alternative is ok
1805 $hmac_secret_key = $hmac_secret_key_alt;
1806 }
1807 } catch (Exception $e) {
1808 throw new Exception($firsterrormessage);
1809 }
1810 } else {
1811 throw new Exception($firsterrormessage);
1812 }
1813 }
1814
1815 // Last check on validity of key
1816 if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
1817 //throw new Exception('Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY using the 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.');
1818 throw new Exception('buildFinalSignatureHash Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY '.$hmac_encoded_secret_key.' using the 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).');
1819 }
1820
1821 // Here the $hmac_secret_key is in memory with the correct value.
1822
1823 // On old versions, we must switch the data saving mode to use the new method.
1824 // Live migration of the way the key is stored.
1825 $needremoteobfuscation = (isALNERunningVersion(1) && $mysoc->country_code == 'FR');
1826
1827 if ($needremoteobfuscation && !preg_match('/^dolobfuscationv1/', $hmac_encoded_secret_key)) { // For old versions, we must switch the data saving mode to use the new method.
1828 $obfuscationkey = '';
1829 $errormsg = '';
1830 try {
1831 $obfuscationkey = $this->getObfuscationKey(); // Get obfuscation key providing $mysoc->idprof1 and $registrationnumber. Note: On network trouble, an Exception is thrown to the caller
1832 } catch (Exception $e) {
1833 $errormsg = $e->getMessage();
1834 }
1835 if (!$errormsg && $obfuscationkey) {
1836 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolobfuscationv1-'.$mysoc->idprof1, $obfuscationkey); // gitleaks:allow
1837 }
1838 } elseif (!$needremoteobfuscation && !preg_match('/^dolcrypt/', $hmac_encoded_secret_key)) {
1839 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolcrypt'); // gitleaks:allow
1840 }
1841
1842 // Here HMAC secret key is a long string starting with BLOCKEDLOGHMAC..., we can use it to sign the data.
1843 return hash_hmac('sha256', $clearstring, $hmac_secret_key);
1844 } else {
1845 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1846 }
1847 }
1848
1849
1858 public function saveHMACSecretKey($hmac_secret_key, $obfuscationmode, $obfuscationkey = '')
1859 {
1860 global $conf;
1861
1862 //var_dump($hmac_secret_key, $obfuscationmode, $obfuscationkey);exit;
1863
1864 if (preg_match('/^dolobfuscationv1/', $obfuscationmode) && empty($obfuscationkey)) {
1865 return -1;
1866 }
1867
1868 $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.
1869
1870 $this->db->begin();
1871
1872 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1873 $sql .= " WHERE name = '".$this->db->escape($name)."'";
1874 if ($this->entity >= 0) {
1875 $sql .= " AND entity = ".((int) $this->entity);
1876 }
1877
1878 dol_syslog("saveHMACSecretKey", LOG_DEBUG);
1879
1880 $resql = $this->db->query($sql);
1881
1882 if (preg_match('/^dolobfuscationv1/', $obfuscationmode)) {
1883 $newvalue = dolEncrypt($hmac_secret_key, $obfuscationkey, '', '', $obfuscationmode); // AES-256
1884 } else {
1885 // if ($obfuscationmode == 'dolcrypt')
1886 $newvalue = dolEncrypt($hmac_secret_key); // AES-256
1887 }
1888
1889 // Clear cache
1890 unset($conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)]);
1891
1892 // Save in database
1893 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1894 $sql .= " VALUES (";
1895 $sql .= "'".$this->db->escape($name)."'";
1896 $sql .= ", '".$this->db->escape($newvalue)."'";
1897 $sql .= ", 'chaine', '0', '', ".((int) $this->entity).")";
1898
1899 //print "xx".$db->escape($value);
1900 $resql = $this->db->query($sql);
1901
1902 if ($resql) {
1903 // Now reload it to check it was saved correctly for a paranoiac control.
1904 $saved = $this->getEncodedHMACSecretKey(); // This also reload the cache
1905
1906 if ($saved == $newvalue) {
1907 $this->db->commit();
1908
1909 return 1;
1910 } else {
1911 $this->error = 'Data read of HMAC key is not same than the one we expect to save.';
1912 $this->db->rollback();
1913
1914 return -1;
1915 }
1916 } else {
1917 $this->error = $this->db->lasterror();
1918 $this->db->rollback();
1919
1920 return -1;
1921 }
1922 }
1923
1924
1933 public function getObfuscationKey()
1934 {
1935 global $conf, $mysoc;
1936
1937
1938 // Uncomment the next line to emulate a network error to get the remote obfuscation key
1939 //throw new Exception('Failed to get the remote obfuscation key - error emulated');
1940
1941 // If key found into the user session memory cache, we use it
1942 if (!empty($_SESSION['obfuscationkey_'.((int) $this->entity)])) {
1943 dol_syslog("getObfuscationKey remote obfuscation key found into session cache", LOG_DEBUG);
1944 return (string) $_SESSION['obfuscationkey_'.((int) $this->entity)];
1945 }
1946 // If key found into the page memory cache, we use it
1947 if (!empty($conf->cache['obfuscationkey_'.((int) $this->entity)])) {
1948 dol_syslog("getObfuscationKey remote obfuscation key found into conf->cache", LOG_DEBUG);
1949 return (string) $conf->cache['obfuscationkey_'.((int) $this->entity)];
1950 }
1951
1952 $obfuscationkey = '';
1953
1954 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1955 $registrationnumber = getHashUniqueIdOfRegistration();
1956
1957 // Value is not into cache, we must get it from ping.dolibarr.org
1958 $obfuscationkey = callApiToGetObfuscationKey((string) $mysoc->idprof1, $registrationnumber);
1959 if (empty($obfuscationkey)) {
1960 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);
1961 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.');
1962 }
1963 if (strpos($obfuscationkey, 'ERROR') === 0) {
1964 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);
1965 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);
1966 }
1967
1968 // Now store value in cache ($obfuscationkey is not empty because of empty/throw above).
1969 $_SESSION['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1970 $conf->cache['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1971
1972 return (string) $obfuscationkey;
1973 }
1974
1983 public function getEncodedHMACSecretKey($nocache = 0, $noentity = 0)
1984 {
1985 global $conf;
1986
1987 $hmac_encoded_secret_key = '';
1988
1989 // Get value of the $hmac_encoded_secret_key from the database
1990 if ($nocache || empty($conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)])) {
1991 $sql = "SELECT value FROM ".MAIN_DB_PREFIX."const WHERE name = 'BLOCKEDLOG_HMAC_KEY'";
1992 if ($noentity) {
1993 $sql .= " AND entity IN (0)"; // To force to get value on old instances that may have been saved with entity = 0
1994 } else {
1995 $sql .= " AND entity IN (0, ".((int) $this->entity).")";
1996 }
1997 $sql .= " ORDER BY entity DESC LIMIT 1";
1998
1999 $resql = $this->db->query($sql);
2000 if ($resql) {
2001 $obj = $this->db->fetch_object($resql);
2002 if ($obj) {
2003 $hmac_encoded_secret_key = $obj->value;
2004
2005 // Save value in memory page cache (if we recall the same function in same page transaction, we will avoid db access).
2006 if (empty($nocache)) {
2007 $conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)] = $hmac_encoded_secret_key;
2008 }
2009 }
2010 } else {
2011 return 'ERROR '.$this->db->lasterror();
2012 }
2013 } else {
2014 $hmac_encoded_secret_key = $conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)];
2015 }
2016
2017 return $hmac_encoded_secret_key;
2018 }
2019
2026 public function getClearHMACSecretKey($hmac_encoded_secret_key)
2027 {
2028 // Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
2029 $hmac_secret_key = '';
2030 $errormsg = '';
2031
2032 // Get the obfuscation key from ping.dolibarr.org (to be used just after to decode HMAC secret key)
2033 if (preg_match('/^dolobfuscation/', $hmac_encoded_secret_key)) {
2034 $obfuscationkey = '';
2035 try {
2036 $obfuscationkey = $this->getObfuscationKey(); // Get obfuscation key providing $mysoc->idprof1 and $registrationnumber. Note: On network trouble, an Exception is thrown to the caller
2037 } catch (Exception $e) {
2038 $errormsg = $e->getMessage();
2039 }
2040 if (!$errormsg && $obfuscationkey) {
2041 // Decode the encrypted parameter using the obfuscation key to get the HMAC key in memory.
2042 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key, $obfuscationkey);
2043 }
2044 }
2045
2046 if (preg_match('/^dolcrypt/', $hmac_encoded_secret_key)) {
2047 // 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).
2048 // We test this. Note: we force a migration of data to use the new storage if this is the case in method buildFinalSignatureHash().
2049 // Example with the old demo sample database:
2050 // dolcrypt:AES-256-CTR:46cb611f00c4cff8:XVfEh15vX/JOYmpiw2QPNamcTQwdbBZJTcXBh9rMpzYJOpVPZubIWcgA8wHMXA==
2051 // instance_unique_id=11f3c81e86fc9e3b3fd11d81c9a31bd0
2052 // HMAC key=BLOCKEDLOGHMACY3Ewx37RXbSd8gL9JV8p7Wqw7qvq2K2A
2053
2054 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key); // Decode the encrypted parameter using the obfuscation key from ping.dolibarr.org to decode HMAC key
2055 }
2056
2057 if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
2058 throw new Exception('getClearHMACSecretKey Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY '.$hmac_encoded_secret_key.' using the 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 : ''));
2059 }
2060
2061 return $hmac_secret_key;
2062 }
2063
2071 public function getPreviousHash($withlock = 0, $beforeid = 0)
2072 {
2073 global $conf;
2074
2075 $previousid = 0;
2076 $previoussignature = '';
2077 $previousdatecreation = 0;
2078
2079 // Fast search of previous record by searching with beforeid - 1. This is very fast and will work 99% of time.
2080 if ($beforeid) {
2081 $sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
2082 $sql .= " WHERE entity = ".((int) $conf->entity);
2083 $sql .= " AND rowid = ".((int) $beforeid - 1);
2084 $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
2085
2086 $resql = $this->db->query($sql);
2087 if ($resql) {
2088 $obj = $this->db->fetch_object($resql);
2089 if ($obj) {
2090 $previousid = $obj->rowid;
2091 $previoussignature = $obj->signature;
2092 $tz = 'gmt';
2093 if (empty($obj->object_format) || $obj->object_format == 'V1') {
2094 $tz = 'tzserver';
2095 }
2096 $previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
2097 }
2098 } else {
2099 dol_print_error($this->db);
2100 exit;
2101 }
2102 }
2103
2104 if (empty($previoussignature)) {
2105 dol_syslog("getPreviousHash: We did not found previous record with fast mode so we search with a select max", LOG_DEBUG);
2106
2107 // Note: a select max rowid and then a select to get signature seems not faster due to filter on entity
2108 $sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
2109 if ($beforeid) {
2110 $sql .= $this->db->hintindex('entity_rowid', 1);
2111 }
2112 $sql .= " WHERE entity = ".((int) $conf->entity);
2113 if ($beforeid) {
2114 $sql .= " AND rowid < ".(int) $beforeid;
2115 }
2116 $sql .= " ORDER BY rowid DESC LIMIT 1";
2117 $sql .= ($withlock ? " FOR UPDATE " : "");
2118
2119 $resql = $this->db->query($sql);
2120 if ($resql) {
2121 $obj = $this->db->fetch_object($resql);
2122 if ($obj) {
2123 $previousid = $obj->rowid;
2124 $previoussignature = $obj->signature;
2125 $tz = 'gmt';
2126 if (empty($obj->object_format) || $obj->object_format == 'V1') {
2127 $tz = 'tzserver';
2128 }
2129 $previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
2130 }
2131 } else {
2132 dol_print_error($this->db); // can happen after a deadlock when too many requests do create into blocked log happen at the same time.
2133 http_response_code(503);
2134 exit;
2135 }
2136 }
2137
2138 if (empty($previoussignature)) {
2139 // First signature line (line 0)
2140 $previousid = 0;
2141 $previoussignature = $this->getOrInitFirstSignature();
2142 }
2143
2144 return array('previousid' => $previousid, 'previoushash' => $previoussignature, 'previousdatecreation' => $previousdatecreation);
2145 }
2146
2153 public function getNextRecord($rowidafter = 0)
2154 {
2155 global $conf;
2156
2157 $nextrecord = array('id' => 0, 'date' => 0, 'signature' => '');
2158
2159 // Get next record
2160 $sql = "SELECT rowid, date_creation, signature FROM ".MAIN_DB_PREFIX."blockedlog";
2161 $sql .= " WHERE entity = ".((int) $conf->entity);
2162 $sql .= " AND rowid > ".((int) $rowidafter);
2163 $sql .= " ORDER BY rowid ASC LIMIT 1";
2164
2165 $resql = $this->db->query($sql);
2166 $obj = $this->db->fetch_object($resql);
2167 if ($obj) {
2168 $nextrecord['id'] = $obj->rowid;
2169 $nextrecord['date'] = $this->db->jdate($obj->date_creation, 'gmt');
2170 $nextrecord['signature'] = $obj->signature;
2171 }
2172
2173 return $nextrecord;
2174 }
2175
2181 public function getLastRecord()
2182 {
2183 global $conf;
2184
2185 $lastrecord = array('id' => 0, 'date' => 0, 'signature' => '');
2186
2187 // Get last line
2188 $sql = "SELECT rowid, date_creation, signature FROM ".MAIN_DB_PREFIX."blockedlog";
2189 $sql .= " WHERE entity = ".((int) $conf->entity);
2190 $sql .= " ORDER BY rowid DESC LIMIT 1";
2191 $resql = $this->db->query($sql);
2192 $obj = $this->db->fetch_object($resql);
2193 if ($obj) {
2194 $lastrecord['id'] = $obj->rowid;
2195 $lastrecord['date'] = $this->db->jdate($obj->date_creation, 'gmt');
2196 $lastrecord['signature'] = $obj->signature;
2197 }
2198
2199 return $lastrecord;
2200 }
2201
2222 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 = '')
2223 {
2224 global $conf;
2225 //global $cachedlogs;
2226
2227 /* $cachedlogs allow fastest search */
2228 //if (empty($cachedlogs)) $cachedlogs = array();
2229
2230 if ($element == 'all') {
2231 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2232 WHERE entity = ".((int) $conf->entity);
2233 } elseif ($element == 'not_certified') {
2234 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2235 WHERE entity = ".((int) $conf->entity)." AND certified = 0";
2236 } elseif ($element == 'just_certified') {
2237 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2238 WHERE entity = ".((int) $conf->entity)." AND certified = 1";
2239 } else {
2240 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2241 WHERE entity = ".((int) $conf->entity)." AND element = '".$this->db->escape($element)."'";
2242 }
2243
2244 if ($fk_object) {
2245 $sql .= natural_search("rowid", (string) $fk_object, 1);
2246 }
2247 if ($search_fk_user > 0) {
2248 $sql .= natural_search("fk_user", (string) $search_fk_user, 2);
2249 }
2250 if ($search_start > 0) {
2251 $sql .= " AND date_creation >= '".$this->db->idate($search_start, 'gmt')."'";
2252 }
2253 if ($search_end > 0) {
2254 $sql .= " AND date_creation <= '".$this->db->idate($search_end, 'gmt')."'";
2255 }
2256 if ($search_type_code) {
2257 $sql .= natural_search("type_code", (string) $search_type_code);
2258 }
2259 if ($search_ref != '') {
2260 $sql .= " AND (".natural_search("ref_object", $search_ref, 0, 1);
2261 $sql .= " OR ".natural_search("linktoref", $search_ref, 0, 1).")";
2262 }
2263 if ($search_amount != '') {
2264 $sql .= natural_search("amounts", $search_amount, 1);
2265 }
2266 if ($search_signature != '') {
2267 $sql .= natural_search("signature", $search_signature, 0);
2268 }
2269 if (is_array($search_code)) {
2270 if (!empty($search_code)) {
2271 if (in_array('PAYMENT_CUSTOMER', $search_code)) { // If we ask codes PAYMENT_CUSTOMER, it means both PAYMENT_CUSTOMER_CREATE and PAYMENT_CUSTOMER_DELETE
2272 $search_code[] = 'PAYMENT_CUSTOMER_CREATE';
2273 $search_code[] = 'PAYMENT_CUSTOMER_DELETE';
2274 }
2275
2276 $sql .= natural_search("action", implode(',', $search_code), 3);
2277 }
2278 } else {
2279 if ($search_code != '' && $search_code != '-1') {
2280 $sql .= natural_search("action", $search_code, 3);
2281 }
2282 }
2283 if (is_array($search_module_source)) {
2284 if (!empty($search_module_source)) {
2285 $sql .= " AND (";
2286 if (in_array('0', $search_module_source)) {
2287 $sql .= "module_source = ''";
2288 unset($search_module_source[0]);
2289 if (!empty($search_module_source)) {
2290 $sql .= " OR ";
2291 }
2292 }
2293 if (!empty($search_module_source)) {
2294 $sqlTmp = natural_search("module_source", implode(',', $search_module_source), 0, 1);
2295 $sqlTmp = str_replace('%backoffice%', '', $sqlTmp);
2296 $sql .= $sqlTmp;
2297 }
2298 $sql .= " OR module_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different origins (pos and not pos)
2299 $sql .= ")";
2300 }
2301 } else {
2302 if ($search_module_source != '' && $search_module_source != '-1') {
2303 $sql .= natural_search("module_source", $search_module_source, 3);
2304 }
2305 }
2306 if ($search_pos_source != '') {
2307 $sql .= " AND (";
2308 $sql .= natural_search("pos_source", $search_pos_source, 0, 1);
2309 $sql .= " OR pos_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different terminal (pos and not pos)
2310 $sql .= ")";
2311 }
2312
2313 $sql .= $this->db->order($sortfield, $sortorder);
2314 $sql .= $this->db->plimit($limit + 1); // We want more, because we will stop into loop later with error if we reach max
2315
2316 $res = $this->db->query($sql);
2317 if ($res) {
2318 $results = array();
2319
2320 $i = 0;
2321 while ($obj = $this->db->fetch_object($res)) {
2322 $i++;
2323 if ($i > $limit) {
2324 // Too many record, we will consume too much memory
2325 return -2;
2326 }
2327
2328 //if (!isset($cachedlogs[$obj->rowid]))
2329 //{
2330 $b = new BlockedLog($this->db);
2331 $result = $b->fetch($obj->rowid);
2332 //$b->loadTrackedEvents();
2333 //$cachedlogs[$obj->rowid] = $b;
2334 //}
2335
2336 //$results[] = $cachedlogs[$obj->rowid];
2337 if ($result < 0) {
2338 $this->error = $b->error;
2339 $this->errors = $b->errors;
2340 return -1;
2341 }
2342
2343 $results[] = $b;
2344 }
2345
2346 return $results;
2347 }
2348
2349 return -1;
2350 }
2351
2357 public function getOrInitFirstSignature()
2358 {
2359 global $db, $conf;
2360
2361 if (!getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT')) { // creation of a unique fingerprint
2362 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
2363 require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
2364 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
2365
2366 $fingerprint = bin2hex(random_bytes(32)); // 64 char hex
2367
2368 dolibarr_set_const($db, 'BLOCKEDLOG_ENTITY_FINGERPRINT', $fingerprint, 'chaine', 0, 'Initial signature fingerprint', $conf->entity);
2369
2370 $conf->global->BLOCKEDLOG_ENTITY_FINGERPRINT = $fingerprint;
2371 }
2372
2373 return getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT');
2374 }
2375
2376
2383 public function alreadyUsed($ignoresystem = 0)
2384 {
2385 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2386 return isBlockedLogUsed($ignoresystem);
2387 }
2388
2389
2395 public function canBeEnabled()
2396 {
2397 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2398 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
2399
2400 $isqualified = isALNEQualifiedVersion(0, 1);
2401
2402 if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && !isHTTPS()) {
2403 return 'Error: The HTTPS must be enabled to allow the use of this module in France.';
2404 }
2405
2406 return '';
2407 }
2408
2409
2415 public function canBeDisabled()
2416 {
2417 global $mysoc;
2418
2419 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2420
2421 $isqualified = isALNEQualifiedVersion();
2422
2423 $canbedisabled = 1;
2424 // For france, we can never disable the module (except in debug mode)
2425 if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && $mysoc->country_code == 'FR') {
2426 $canbedisabled = 0;
2427 }
2428
2429 return $canbedisabled;
2430 }
2431
2432
2438 public function countRecord()
2439 {
2440 $nb = 0;
2441
2442 $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."blockedlog";
2443 $resql = $this->db->query($sql);
2444 if ($resql) {
2445 $obj = $this->db->fetch_object($resql);
2446 $nb = $obj->nb;
2447 } else {
2448 dol_print_error($this->db);
2449 }
2450 $this->db->free($resql);
2451
2452 return $nb;
2453 }
2454}
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($blockedlogtestalreadydone=0, $blockedlogmodulealreadydone=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:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
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_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
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)
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
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.