dolibarr 24.0.0-beta
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-2025 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2024-2025 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';
26
27
32{
36 public $db;
37
42 public $id;
43
48 public $entity;
49
54 public $picto = 'blockedlog';
55
59 public $error = '';
60
64 public $errors = array();
65
70 public $signature = '';
71
75 public $amounts = null;
76
80 public $amounts_taxexcl = null;
81
86 public $action = '';
87
91 public $module_source = '';
92
96 public $pos_source = '';
97
101 public $linktype = '';
102
106 public $linktoref = '';
107
112 public $element = '';
113
118 public $fk_object = 0;
119
124 public $certified = false;
125
130 public $fk_user = 0;
131
135 public $date_creation;
136
140 public $date_modification;
141
145 public $date_object = 0;
146
150 public $ref_object = '';
151
155 public $type_code = '';
156
160 public $object_data = null;
161
165 public $object_version = '';
166
170 public $object_format = '';
171
175 public $user_fullname = '';
176
180 public $debuginfo;
181
185 public $note;
186
191 public $trackedevents = array();
192
197 public $controlledevents = array();
198
203 public $trackedmodules = array();
204
205
206
212 public function __construct(DoliDB $db)
213 {
214 global $conf;
215
216 $this->db = $db;
217 $this->entity = $conf->entity;
218 }
219
220
226 public function loadTrackedEvents()
227 {
228 global $langs;
229
230 $this->controlledevents = array();
231 $this->trackedevents = array();
232 $this->trackedmodules = array();
233
234 $sep = 0;
235
236 $this->controlledevents['BILL_MODIFY'] = array('id' => 'BILL_MODIFY', 'label' => 'logBILL_MODIFY');
237
238 $this->trackedmodules[0] = 'None';
239 if (isModEnabled('takepos')) {
240 $this->trackedmodules['takepos'] = 'TakePOS';
241 }
242
243 // Customer Invoice/Facture / Payment (For most VAT antifraud laws)
244 if (isModEnabled('invoice')) {
245 $sep++;
246 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Invoices").' | '.$langs->trans("Payments").'</span>', 'disabled' => 1);
247
248 $this->trackedevents['BILL_VALIDATE'] = array('id' => 'BILL_VALIDATE', 'label' => 'logBILL_VALIDATE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_VALIDATE'));
249 //$this->trackedevents['BILL_UPDATE'] = array('id' => 'BILL_VALIDATE', 'label' => 'logBILL_UPDATE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_UPDATE'));
250 $this->trackedevents['BILL_SENTBYMAIL'] = array('id' => 'BILL_SENTBYMAIL', 'label' => 'logBILL_SENTBYMAIL', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_SENTBYMAIL'));
251 $this->trackedevents['DOC_DOWNLOAD'] = array('id' => 'DOC_DOWNLOAD', 'label' => 'BlockedLogBillDownload', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('BlockedLogBillDownload'));
252 $this->trackedevents['DOC_PREVIEW'] = array('id' => 'DOC_PREVIEW', 'label' => 'BlockedLogBillPreview', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('BlockedLogBillPreview'));
253 $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'));
254 $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'));
255 }
256
257 /* Supplier
258 // Supplier Invoice / Payment
259 if (isModEnabled("fournisseur")) {
260 $this->trackedevents['BILL_SUPPLIER_VALIDATE']='BlockedLogSupplierBillValidate';
261 $this->trackedevents['BILL_SUPPLIER_DELETE']='BlockedLogSupplierBillDelete';
262 $this->trackedevents['BILL_SUPPLIER_SENTBYMAIL']='BlockedLogSupplierBillSentByEmail'; // Trigger key does not exists, we want just into array to list it as done
263 $this->trackedevents['SUPPLIER_DOC_DOWNLOAD']='BlockedLogSupplierBillDownload'; // Trigger key does not exists, we want just into array to list it as done
264 $this->trackedevents['SUPPLIER_DOC_PREVIEW']='BlockedLogSupplierBillPreview'; // Trigger key does not exists, we want just into array to list it as done
265 $this->trackedevents['PAYMENT_SUPPLIER_CREATE']='BlockedLogSupplierBillPaymentCreate';
266 $this->trackedevents['PAYMENT_SUPPLIER_DELETE']='BlockedLogsupplierBillPaymentCreate';
267 }
268 */
269
270 // Donation
271 if (isModEnabled('don') && getDolGlobalString('BLOCKEDLOG_ENABLE_DONATION')) { // For countries that need unalterable logs for donations
272 if (!empty($this->trackedevents)) {
273 $sep++;
274 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Donations").' | '.$langs->trans("Payments").'</span>', 'disabled' => 1);
275 }
276
277 $this->trackedevents['DON_VALIDATE'] = array('id' => 'DON_VALIDATE', 'label' => 'logDON_VALIDATE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDON_VALIDATE'));
278 $this->trackedevents['DON_DELETE'] = array('id' => 'DON_DELETE', 'label' => 'logDON_DELETE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDON_DELETE'));
279 //$this->trackedevents['DON_SENTBYMAIL'] = array('id' => 'BILL_VALIDATE', img_picto('', 'don', 'class="pictofixedwidth").').$langs->trans('labelhtml' => 'logDON_SENTBYMAIL');
280 $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'));
281 $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'));
282 }
283
284 /*
285 // Salary
286 if (isModEnabled('salary')) {
287 $this->trackedevents['PAYMENT_SALARY_CREATE'] = 'BlockedLogSalaryPaymentCreate';
288 $this->trackedevents['PAYMENT_SALARY_MODIFY'] = 'BlockedLogSalaryPaymentCreate';
289 $this->trackedevents['PAYMENT_SALARY_DELETE'] = 'BlockedLogSalaryPaymentCreate';
290 }
291 */
292
293 // Members
294 if (isModEnabled('member') && getDolGlobalString('BLOCKEDLOG_ENABLE_MEMBER')) { // For countries that need unalterable logs for membership management
295 if (!empty($this->trackedevents)) {
296 $sep++;
297 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("MenuMembers").'</span>', 'disabled' => 1);
298 }
299
300 $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'));
301 $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'));
302 $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'));
303 }
304
305 // Bank
306 /*
307 if (isModEnabled("bank")) {
308 if (!empty($this->trackedevents)) {
309 $sep++;
310 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("VariousPayment").'</span>', 'disabled' => 1);
311 }
312
313 $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'));
314 $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'));
315 $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'));
316 }
317 */
318
319 // Cash register closing
320 // $conf->global->BANK_ENABLE_POS_CASHCONTROL must be set to 1 by all external POS modules
321 $moduleposenabled = (isModEnabled('cashdesk') || isModEnabled('takepos') || getDolGlobalString('BANK_ENABLE_POS_CASHCONTROL'));
322 if ($moduleposenabled) {
323 if (!empty($this->trackedevents)) {
324 $sep++;
325 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("CashControl").'</span>', 'disabled' => 1);
326 }
327 if (getDolGlobalString('BLOCKEDLOG_ADD_OLD_CASHCONTROL_VALIDATE')) {
328 $this->trackedevents['CASHCONTROL_VALIDATE'] = array('id' => 'CASHCONTROL_VALIDATE', 'label' => 'logCASHCONTROL_VALIDATE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_VALIDATE'));
329 }
330 $this->trackedevents['CASHCONTROL_CLOSE'] = array('id' => 'CASHCONTROL_CLOSE', 'label' => 'logCASHCONTROL_CLOSE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_CLOSE'));
331 }
332
333 // Add more action to track from a conf variable. For the case we want to track other actions into the unalterable log.
334 // For example: STOCK_MOVEMENT, ...
335 if (getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED')) {
336 if (!empty($this->trackedevents)) {
337 $sep++;
338 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----------</span>', 'disabled' => 1);
339 }
340
341 $tmparrayofmoresupportedevents = explode(',', getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED'));
342 foreach ($tmparrayofmoresupportedevents as $val) {
343 $this->trackedevents[$val] = array('id' => $val, 'label' => 'log'.$val, 'labelhtml' => img_picto('', 'generic', 'class="pictofixedwidth").').$langs->trans('log'.$val));
344 }
345 }
346
347 if (!empty($this->trackedevents)) {
348 $sep++;
349 $this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Other").'</span>', 'disabled' => 1);
350 }
351 $this->trackedevents['BLOCKEDLOG_EXPORT'] = array('id' => 'BLOCKEDLOG_EXPORT', 'label' => 'logBLOCKEDLOG_EXPORT', 'labelhtml' => img_picto('', $this->picto, 'class="pictofixedwidth").').$langs->trans('logBLOCKEDLOG_EXPORT'));
352
353 return 1;
354 }
355
361 public function getObjectLink()
362 {
363 global $langs;
364
365 if ($this->element === 'facture') {
366 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
367
368 $object = new Facture($this->db);
369 if ($object->fetch($this->fk_object) > 0) {
370 return $object->getNomUrl(1);
371 } else {
372 $this->error = (string) (((int) $this->error) + 1);
373 }
374 }
375 if ($this->element === 'invoice_supplier') {
376 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
377
378 $object = new FactureFournisseur($this->db);
379 if ($object->fetch($this->fk_object) > 0) {
380 return $object->getNomUrl(1);
381 } else {
382 $this->error = (string) (((int) $this->error) + 1);
383 }
384 } elseif ($this->element === 'payment') {
385 require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
386
387 $object = new Paiement($this->db);
388 if ($object->fetch($this->fk_object) > 0) {
389 return $object->getNomUrl(1);
390 } else {
391 $this->error = (string) (((int) $this->error) + 1);
392 }
393 } elseif ($this->element === 'payment_supplier') {
394 require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';
395
396 $object = new PaiementFourn($this->db);
397 if ($object->fetch($this->fk_object) > 0) {
398 return $object->getNomUrl(1);
399 } else {
400 $this->error = (string) (((int) $this->error) + 1);
401 }
402 } elseif ($this->element === 'payment_donation') {
403 require_once DOL_DOCUMENT_ROOT.'/don/class/paymentdonation.class.php';
404
405 $object = new PaymentDonation($this->db);
406 if ($object->fetch($this->fk_object) > 0) {
407 return $object->getNomUrl(1);
408 } else {
409 $this->error = (string) (((int) $this->error) + 1);
410 }
411 } elseif ($this->element === 'payment_various') {
412 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
413
414 $object = new PaymentVarious($this->db);
415 if ($object->fetch($this->fk_object) > 0) {
416 return $object->getNomUrl(1);
417 } else {
418 $this->error = (string) (((int) $this->error) + 1);
419 }
420 } elseif ($this->element === 'don' || $this->element === 'donation') {
421 require_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php';
422
423 $object = new Don($this->db);
424 if ($object->fetch($this->fk_object) > 0) {
425 return $object->getNomUrl(1);
426 } else {
427 $this->error = (string) (((int) $this->error) + 1);
428 }
429 } elseif ($this->element === 'subscription') {
430 require_once DOL_DOCUMENT_ROOT.'/adherents/class/subscription.class.php';
431
432 $object = new Subscription($this->db);
433 if ($object->fetch($this->fk_object) > 0) {
434 return $object->getNomUrl(1);
435 } else {
436 $this->error = (string) (((int) $this->error) + 1);
437 }
438 } elseif ($this->element === 'cashcontrol') {
439 require_once DOL_DOCUMENT_ROOT.'/compta/cashcontrol/class/cashcontrol.class.php';
440
441 $object = new CashControl($this->db);
442 if ($object->fetch($this->fk_object) > 0) {
443 return $object->getNomUrl(1);
444 } else {
445 $this->error = (string) (((int) $this->error) + 1);
446 }
447 } elseif ($this->element === 'stockmouvement') {
448 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
449
450 $object = new MouvementStock($this->db);
451 if ($object->fetch($this->fk_object) > 0) {
452 return $object->getNomUrl(1);
453 } else {
454 $this->error = (string) (((int) $this->error) + 1);
455 }
456 } elseif ($this->element === 'project') {
457 require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
458
459 $object = new Project($this->db);
460 if ($object->fetch($this->fk_object) > 0) {
461 return $object->getNomUrl(1);
462 } else {
463 $this->error = (string) (((int) $this->error) + 1);
464 }
465 } elseif ($this->action == 'BLOCKEDLOG_EXPORT') {
466 return '<i class="opacitymedium">'.$langs->trans("logBLOCKEDLOG_EXPORT").'</i>';
467 } elseif ($this->action == 'MODULE_SET') {
468 return '<i class="opacitymedium">'.$langs->trans("BlockedLogEnabled").'</i>';
469 } 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.
470 if ($this->signature == '0000000000') {
471 return '<i class="opacitymedium">'.$langs->trans("BlockedLogDisabled").'</i>';
472 } else {
473 return '<i class="opacitymedium">'.$langs->trans("BlockedLogDisabledBis").'</i>';
474 }
475 }
476
477 return '<i class="opacitymedium">'.$langs->trans('ImpossibleToReloadObject', $this->element, $this->fk_object).'</i>';
478 }
479
485 public function getUser()
486 {
487 global $langs, $cachedUser;
488
489 if (empty($cachedUser)) {
490 $cachedUser = array();
491 }
492
493 if (empty($cachedUser[$this->fk_user])) {
494 $u = new User($this->db);
495 if ($u->fetch($this->fk_user) > 0) {
496 $cachedUser[$this->fk_user] = $u;
497 }
498 }
499
500 if (!empty($cachedUser[$this->fk_user])) {
501 return $cachedUser[$this->fk_user]->getNomUrl(1);
502 }
503
504 return $langs->trans('ImpossibleToRetrieveUser', $this->fk_user);
505 }
506
519 public function setObjectData(&$object, $action, $amounts, $fuser = null, $amounts_taxexcl = null)
520 {
521 global $langs, $user, $mysoc;
522
523 if (is_object($fuser)) {
524 $user = $fuser;
525 }
526
527 // Init object_data for JSON data
528 $this->object_data = new stdClass();
529
530 // Generic fields
531
532 // entity
533 $this->entity = $object->entity ?? getDolEntity();
534
535 // action
536 $this->action = $action;
537
538 // amount
539 $this->amounts_taxexcl = $amounts_taxexcl;
540 $this->amounts = $amounts;
541 if ($action === 'MEMBER_SUBSCRIPTION_DELETE' || $action === 'PAYMENT_CUSTOMER_DELETE' || $action === 'PAYMENT_SUPPLIER_DELETE' || $action === 'DONATION_PAYMENT_DELETE') {
542 $this->amounts_taxexcl = - $this->amounts_taxexcl;
543 $this->amounts = - $this->amounts;
544 }
545
546 // date
547 if ($object->element == 'payment' || $object->element == 'payment_supplier') {
548 '@phan-var-force Paiement|PaiementFourn $object';
549 $this->date_object = empty($object->datepaye) ? $object->date : $object->datepaye;
550 } elseif ($object->element == 'payment_salary') {
551 '@phan-var-force PaymentSalary $object';
552 $this->date_object = $object->datev;
553 } elseif ($object->element == 'payment_donation' || $object->element == 'payment_various') {
554 '@phan-var-force PaymentDonation $object';
555 $this->date_object = empty($object->datepaid) ? $object->datep : $object->datepaid;
556 } elseif ($object->element == 'subscription') {
557 '@phan-var-force Subscription $object';
558 $this->date_object = $object->dateh;
559 } elseif ($object->element == 'cashcontrol') {
561 '@phan-var-force CashControl $object';
562 $this->date_object = $object->date_creation;
563 $this->module_source = $object->posmodule;
564 $this->pos_source = $object->posnumber;
565 } elseif (property_exists($object, 'date')) {
566 // Generic case
567 $this->date_object = $object->date; // @phan-suppress-current-line PhanUndeclaredProperty
568 } elseif (property_exists($object, 'datem')) {
569 // Generic case (second chance, for example for stock movement)
570 $this->date_object = $object->datem; // @phan-suppress-current-line PhanUndeclaredProperty
571 }
572
573 // In case of credit note, we add link to source invoice to have more tracking info when doing tracking later
574 if ($object->element == 'invoice_supplier') {
575 '@phan-var-force FactureFournisseur $object';
577 $invoice = new FactureFournisseur($this->db);
578 $invoice->fetch($object->fk_facture_source);
579 if ($invoice->id > 0) {
580 $this->linktype = 'credit_note_of';
581 $this->linktoref = $invoice->ref;
582 }
583 //$this->module_source = (string) $invoice->module_source;
584 //$this->pos_source = (string) $invoice->pos_source;
585 }
586 }
587 if ($object->element == 'facture') {
588 '@phan-var-force Facture $object';
589 if ($object->type == Facture::TYPE_CREDIT_NOTE) {
590 $invoice = new Facture($this->db);
591 $invoice->fetch($object->fk_facture_source);
592 if ($invoice->id > 0) {
593 $this->linktype = 'credit_note_of';
594 $this->linktoref = $invoice->ref;
595
596 $this->object_data->link = $this->linktype.' '.$this->linktoref;
597 }
598 $this->module_source = (string) $invoice->module_source;
599 $this->pos_source = (string) $invoice->pos_source;
600 }
601 }
602
603 // ref object
604 $this->ref_object = ((!empty($object->newref)) ? $object->newref : $object->ref); // newref is set when validating a draft, ref is set in other cases
605 // type of object
606 $this->element = $object->element;
607 // id of object
608 $this->fk_object = $object->id;
609
610 // Add thirdparty info if not yet done
611 if (empty($object->thirdparty) && method_exists($object, 'fetch_thirdparty')) {
612 $object->fetch_thirdparty();
613 }
614
615
616 // Add fields to exclude (this has become useless because we now use a list fields to keep later).
617 $arrayoffieldstoexclude = array(
618 'table_element', 'fields',
619 'ref_previous', 'ref_next',
620 'origin', 'origin_id',
621 'oldcopy', 'picto', 'error', 'errors',
622 'model_pdf', 'modelpdf', 'last_main_doc', 'civility_id', 'contact', 'contact_id',
623 'table_element_line', 'ismultientitymanaged', 'isextrafieldmanaged',
624 'array_languages',
625 'childtables',
626 'contact_ids',
627 'context',
628 'element',
629 'labelStatus',
630 'labelStatusShort',
631 'linkedObjectsIds',
632 'linkedObjects',
633 'fk_delivery_address',
634 'projet', // There is already ->fk_project
635 'restrictiononfksoc',
636 'specimen',
637 );
638
639 // Add more fields to exclude depending on object type
640 if ($this->element == 'cashcontrol') {
641 $arrayoffieldstoexclude = array_merge($arrayoffieldstoexclude, array(
642 'name', 'lastname', 'firstname', 'region', 'region_id', 'region_code', 'state', 'state_id', 'state_code', 'country', 'country_id', 'country_code',
643 'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
644 'barcode_type', 'barcode_type_code', 'barcode_type_label', 'barcode_type_coder', 'mode_reglement_id', 'cond_reglement_id', 'mode_reglement', 'cond_reglement', 'shipping_method_id',
645 'extraparams', 'fk_incoterms', 'fk_user_creat', 'fk_user_valid', 'label_incoterms', 'location_incoterms', 'lines', 'nb', 'tms', 'comments', 'array_options', 'warnings',
646 'opening', 'status', 'date_valid'
647 )
648 );
649 }
650
651 // For customer payment and supplier payment, the thirdparty can be added in payment detail
652 $addthirdpartyatpaymentlevel = 0;
653 if (!empty($object->thirdparty) && in_array($this->element, array('payment', 'payment_supplier'))) {
654 $addthirdpartyatpaymentlevel = 1;
655 }
656
657 if (!empty($object->thirdparty) && !$addthirdpartyatpaymentlevel) { // If $addthirdpartyatpaymentlevel is set, we will add thirdparty on payments later.
658 $this->object_data->thirdparty = new stdClass();
659
660 foreach ($object->thirdparty as $key => $value) {
661 if (in_array($key, $arrayoffieldstoexclude)) {
662 continue; // Discard some properties
663 }
664 // List of fields qualified
665 if (!in_array($key, array(
666 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
667 'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax2_assuj', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
668 ))) {
669 continue; // Discard if not into this dedicated list
670 }
671
672 $valuequalifiedforstorage = false;
673 if (!is_object($value)) {
674 if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
675 $valuequalifiedforstorage = true; // We accept '' value for some fields
676 $value = (string) $value;
677 }
678 if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
679 $valuequalifiedforstorage = true; // We accept zero value for amounts
680 }
681 if (!is_null($value) && (string) $value !== '') {
682 $valuequalifiedforstorage = true;
683 }
684 }
685
686 if ($valuequalifiedforstorage) {
687 $this->object_data->thirdparty->$key = $value;
688 }
689 }
690 }
691
692 // Add my company info (Only for customer invoice and payment)
693 if (!empty($mysoc) && in_array($object->element, array('facture', 'paiement'))) {
694 $this->object_data->mycompany = new stdClass();
695
696 foreach ($mysoc as $key => $value) {
697 if (in_array($key, $arrayoffieldstoexclude)) {
698 continue; // Discard some properties
699 }
700 // List of fields qualified to keep
701 if (!in_array($key, array(
702 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
703 'tva_assuj', 'tva_intra', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
704 ))) {
705 continue; // Discard if not into this dedicated list
706 }
707
708 $valuequalifiedforstorage = false;
709 if (!is_object($value)) {
710 if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
711 $valuequalifiedforstorage = true; // We accept '' value for some fields
712 $value = (string) $value;
713 }
714 if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
715 $valuequalifiedforstorage = true; // We accept zero value for amounts
716 }
717 if (!is_null($value) && (string) $value !== '') {
718 $valuequalifiedforstorage = true;
719 }
720 }
721
722 if ($valuequalifiedforstorage) {
723 $this->object_data->mycompany->$key = $value;
724 }
725 }
726 }
727
728 // Add user info
729 if (!empty($user)) {
730 $this->fk_user = $user->id;
731 $this->user_fullname = $user->getFullName($langs);
732 }
733
734 // Field specific to object
735 if ($this->element == 'facture') {
736 '@phan-var-force Facture $object';
737 $this->module_source = (string) $object->module_source;
738 $this->pos_source = (string) $object->pos_source;
739
740 foreach ($object as $key => $value) {
741 if (in_array($key, $arrayoffieldstoexclude)) {
742 continue; // Discard some properties
743 }
744 // List of fields qualified
745 if (!in_array($key, array(
746 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'datev', 'type',
747 //'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx', There is no rate at full doc level
748 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2',
749 'revenuestamp', 'datepointoftax', 'note_public',
750 'lines',
751 'module_source', 'pos_source', 'pos_print_counter', 'email_sent_counter'
752 ))) {
753 continue; // Discarded if not into the dedicated list
754 }
755 if ($key == 'lines') {
756 $lineid = 0;
757 foreach ($value as $tmpline) { // $tmpline is object FactureLine
758 $lineid++;
759 foreach ($tmpline as $keyline => $valueline) {
760 if (!in_array($keyline, array(
761 'ref', 'product_type', 'product_label',
762 'qty', 'subprice',
763 'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx',
764 'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
765 'multicurrency_code', 'multicurrency_total_ht', 'multicurrency_total_tva', 'multicurrency_total_ttc',
766 'info_bits', 'special_code', 'remise_percent'
767 ))) {
768 continue; // Discard if not into a dedicated list
769 }
770
771 if (empty($this->object_data->invoiceline[$lineid]) || !is_object($this->object_data->invoiceline[$lineid])) { // To avoid warning
772 $this->object_data->invoiceline[$lineid] = new stdClass();
773 }
774
775 $valuequalifiedforstorage = false;
776 if (!is_object($valueline)) {
777 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'))) {
778 $valuequalifiedforstorage = true; // We accept zero value for amounts
779 }
780 if (!is_null($valueline) && (string) $valueline !== '') {
781 $valuequalifiedforstorage = true;
782 }
783 }
784 if ($keyline == 'product_label' && empty($valueline)) {
785 $valueline = dol_trunc(dolGetFirstLineOfText($tmpline->desc)); // Fallback on description if label is empty
786 $valuequalifiedforstorage = true;
787 }
788
789 if ($valuequalifiedforstorage) {
790 $this->object_data->invoiceline[$lineid]->$keyline = $valueline;
791 }
792 }
793 }
794 } else {
795 $valuequalifiedforstorage = false;
796 if (!is_object($value)) {
797 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
798 $valuequalifiedforstorage = true; // We accept '' value for some fields
799 $value = (string) $value;
800 }
801 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
802 $valuequalifiedforstorage = true; // We accept zero value for amounts
803 }
804 if (!is_null($value) && (string) $value !== '') {
805 $valuequalifiedforstorage = true;
806 }
807 }
808
809 if ($valuequalifiedforstorage) {
810 $this->object_data->$key = $value;
811 }
812 }
813 }
814
815 if (!empty($object->newref)) {
816 $this->object_data->ref = $object->newref;
817 }
818
819 // Add data for action emails
820 if ($action == 'BILL_SENTBYMAIL') {
821 $this->object_data->action_email_sent = array(
822 "email_from" => $object->context['email_from'],
823 "email_to" => $object->context['email_to'],
824 "email_msgid" => $object->context['email_msgid']
825 );
826 }
827 } elseif ($this->element == 'invoice_supplier') {
828 '@phan-var-force FactureFournisseur $object';
829 foreach ($object as $key => $value) {
830 if (in_array($key, $arrayoffieldstoexclude)) {
831 continue; // Discard some properties
832 }
833 // List of fields qualified
834 if (!in_array($key, array(
835 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public'
836 ))) {
837 continue; // Discard if not into a dedicated list
838 }
839
840 $valuequalifiedforstorage = false;
841 if (!is_object($value)) {
842 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
843 $valuequalifiedforstorage = true; // We accept '' value for some fields
844 $value = (string) $value;
845 }
846 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
847 $valuequalifiedforstorage = true; // We accept zero value for amounts
848 }
849 if (!is_null($value) && (string) $value !== '') {
850 $valuequalifiedforstorage = true;
851 }
852 }
853
854 if ($valuequalifiedforstorage) {
855 $this->object_data->$key = $value;
856 }
857 }
858
859 if (!empty($object->newref)) {
860 $this->object_data->ref = $object->newref;
861 }
862 } elseif ($this->element == 'payment' || $this->element == 'payment_supplier' || $this->element == 'payment_donation' || $this->element == 'payment_various') {
863 '@phan-var-force Paiement|PaiementFourn|PaymentDonation|PaymentVarious $object';
864 $datepayment = $object->datepaye ? $object->datepaye : ($object->datepaid ? $object->datepaid : $object->datep);
865 $paymenttypeid = $object->paiementid ? $object->paiementid : ($object->paymenttype ? $object->paymenttype : $object->type_payment);
866
867 $this->object_data->ref = $object->ref;
868 $this->object_data->date = $datepayment;
869 $this->object_data->type_code = dol_getIdFromCode($this->db, $paymenttypeid, 'c_paiement', 'id', 'code');
870
871 if (!empty($object->num_payment)) {
872 $this->object_data->payment_num = $object->num_payment;
873 }
874 if (!empty($object->note_private)) {
875 $this->object_data->note_private = $object->note_private;
876 }
877 //$this->object_data->fk_account = $object->fk_account;
878 //var_dump($this->object_data);exit;
879
880 $totalamount = 0;
881
882 $this->type_code = $this->object_data->type_code;
883 $this->linktype = $this->element;
884 $this->linktoref = '';
885
886 // If payment and $object->amounts is empty (for example when we delete), we complete the information
887 if ($this->element == 'payment' && empty($object->amounts) && $object instanceOf Paiement) {
888 $amountsarray = $object->getAmountsArray();
889 $object->amounts = $amountsarray;
890 // Invert the sign of amount into the array ->amounts if it is a deletion
891 if ($action == 'PAYMENT_CUSTOMER_DELETE') {
892 foreach ($object->amounts as $amountkey => $amountval) {
893 $object->amounts[$amountkey] = - $amountval;
894 }
895 }
896 }
897
898 // Loop on each invoice payment amount (the payment_part)
899 if (is_array($object->amounts) && !empty($object->amounts)) {
900 // Loop on each invoice the payment is part of to set the linktoref and the module_source and pos_source
901 $originofpayment = null;
902 $terminalofpayment = null;
903 $paymentpartnumber = 0;
904 foreach ($object->amounts as $objid => $amount) {
905 if (empty($amount)) {
906 continue;
907 }
908
909 $totalamount += $amount;
910
911 $tmpobject = null;
912 if ($this->element == 'payment_supplier') {
913 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
914 $tmpobject = new FactureFournisseur($this->db);
915 } elseif ($this->element == 'payment') {
916 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
917 $tmpobject = new Facture($this->db);
918 } elseif ($this->element == 'payment_donation') {
919 include_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php';
920 $tmpobject = new Don($this->db);
921 } elseif ($this->element == 'payment_various') {
922 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
923 $tmpobject = new PaymentVarious($this->db);
924 }
925
926 if (!is_object($tmpobject)) {
927 continue;
928 }
929
930 $result = $tmpobject->fetch($objid);
931
932 if ($result <= 0) {
933 $this->error = $tmpobject->error;
934 $this->errors = $tmpobject->errors;
935 dol_syslog("Failed to fetch object with id ".$objid, LOG_ERR);
936 return -1;
937 }
938
939 $this->linktoref .= ($this->linktoref ? ',' : '').$tmpobject->ref;
940
941 // Set the ->module_source of payment from origin object if relevant
942 if (property_exists($tmpobject, 'module_source')) {
943 if (is_null($originofpayment)) {
944 $originofpayment = (string) $tmpobject->module_source;
945 } elseif ($originofpayment != $tmpobject->module_source) {
946 $originofpayment = 'mix'; // the payment is on several invoices with different origins of module
947 } else {
948 $originofpayment = (string) $tmpobject->module_source;
949 }
950 }
951 // Set the ->pos_source of payment from origin object if relevant
952 if (property_exists($tmpobject, 'pos_source')) {
953 if (is_null($terminalofpayment)) {
954 $terminalofpayment = (string) $tmpobject->pos_source;
955 } elseif ($terminalofpayment != $tmpobject->pos_source) {
956 $terminalofpayment = 'mix'; // the payment is on several invoices with same origin of module but different terminals
957 } else {
958 $terminalofpayment = (string) $tmpobject->pos_source;
959 }
960 }
961 $paymentpart = new stdClass();
962 $paymentpart->amount = $amount;
963
964 // If we want to add thirdparty on each payment level
965 // (seems not necessary as we have one thirdparty per payment on invoice level)
966 if ($addthirdpartyatpaymentlevel) {
967 $result = $tmpobject->fetch_thirdparty();
968 if ($result == 0) {
969 $this->error = 'Failed to fetch thirdparty for object with id '.$tmpobject->id;
970 $this->errors[] = $this->error;
971 dol_syslog("Failed to fetch thirdparty for object with id ".$tmpobject->id, LOG_ERR);
972 return -1;
973 } elseif ($result < 0) {
974 $this->error = $tmpobject->error;
975 $this->errors = $tmpobject->errors;
976 return -1;
977 }
978
979 $paymentpart->thirdparty = new stdClass();
980 foreach ($tmpobject->thirdparty as $key => $value) {
981 if (in_array($key, $arrayoffieldstoexclude)) {
982 continue; // Discard some properties
983 }
984 // List of thirdparty fields qualified
985 if (!in_array($key, array(
986 'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
987 'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
988 ))) {
989 continue; // Discard if not into a dedicated list
990 }
991 if (!is_object($value) && !is_null($value) && $value !== '') {
992 $paymentpart->thirdparty->$key = $value;
993 }
994 }
995 }
996
997 // Init object to avoid warnings
998 if ($this->element == 'payment_donation') {
999 $paymentpart->donation = new stdClass();
1000 } elseif ($this->element == 'payment_various') {
1001 $paymentpart->various = new stdClass();
1002 } else {
1003 $paymentpart->invoice = new stdClass();
1004 }
1005
1006 if ($this->element != 'payment_various') {
1007 foreach ($tmpobject as $key => $value) {
1008 if (in_array($key, $arrayoffieldstoexclude)) {
1009 continue; // Discard some properties
1010 }
1011 // List of fields qualified
1012 if (!in_array($key, array(
1013 'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public',
1014 'pos_source', 'module_source', 'pos_print_counter', 'email_sent_counter'
1015 ))) {
1016 continue; // Discard if not into a dedicated list
1017 }
1018
1019 $valuequalifiedforstorage = false;
1020 if (!is_object($value)) {
1021 if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
1022 $valuequalifiedforstorage = true; // We accept '' value for some fields
1023 $value = (string) $value;
1024 }
1025 if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
1026 $valuequalifiedforstorage = true; // We accept zero value for amounts
1027 }
1028 if (!is_null($value) && (string) $value !== '') {
1029 $valuequalifiedforstorage = true;
1030 }
1031 }
1032
1033 if ($valuequalifiedforstorage) {
1034 if ($this->element == 'payment_donation') {
1035 $paymentpart->donation->$key = $value;
1036 } elseif ($this->element == 'payment_various') {
1037 $paymentpart->various->$key = $value;
1038 } else {
1039 $paymentpart->invoice->$key = $value;
1040 }
1041 }
1042 }
1043
1044 $paymentpartnumber++; // first payment will be 1
1045 $this->object_data->payment_part[$paymentpartnumber] = $paymentpart;
1046 }
1047 }
1048
1049 $this->module_source = (string) $originofpayment;
1050 $this->pos_source = (string) $terminalofpayment;
1051 } elseif (!empty($object->amount)) {
1052 $totalamount = $object->amount;
1053 }
1054
1055 $this->object_data->amount = $totalamount;
1056
1057 if (!empty($object->newref)) {
1058 $this->object_data->ref = $object->newref;
1059 }
1060 } elseif ($this->element == 'payment_salary') {
1061 '@phan-var-force PaymentSalary $object';
1062 $this->object_data->amounts = array($object->amount);
1063
1064 if (!empty($object->newref)) {
1065 $this->object_data->ref = $object->newref;
1066 }
1067 } elseif ($this->element == 'subscription') {
1068 '@phan-var-force Subscription $object';
1069 foreach ($object as $key => $value) {
1070 if (in_array($key, $arrayoffieldstoexclude)) {
1071 continue; // Discard some properties
1072 }
1073 if (!in_array($key, array(
1074 'id', 'datec', 'dateh', 'datef', 'fk_adherent', 'amount', 'import_key', 'statut', 'note'
1075 ))) {
1076 continue; // Discard if not into a dedicated list
1077 }
1078 if (!is_object($value) && !is_null($value) && $value !== '') {
1079 $this->object_data->$key = $value;
1080 }
1081 }
1082
1083 if (!empty($object->newref)) {
1084 $this->object_data->ref = $object->newref;
1085 }
1086 } elseif ($this->element == 'stockmouvement') {
1087 '@phan-var-force StockTransfer $object';
1088 foreach ($object as $key => $value) {
1089 if (in_array($key, $arrayoffieldstoexclude)) {
1090 continue; // Discard some properties
1091 }
1092 if (!is_object($value) && !is_null($value) && $value !== '') {
1093 $this->object_data->$key = $value;
1094 }
1095 }
1096 } else {
1097 if ($object->element == 'cashcontrol') {
1098 $this->module_source = (string) $object->posmodule; // Module
1099 $this->pos_source = (string) $object->posnumber; // Terminal
1100 }
1101
1102 // Generic case
1103 foreach ($object as $key => $value) {
1104 if (in_array($key, $arrayoffieldstoexclude)) {
1105 continue; // Discard some properties
1106 }
1107 if (!is_object($value) && !is_null($value) && $value !== '') {
1108 $this->object_data->$key = $value;
1109 }
1110 }
1111
1112 if ($object->element == 'cashcontrol') {
1113 $period = $object->year_close;
1114 $period .= ($object->month_close ? "-".sprintf("%02d", $object->month_close) : "");
1115 $period .= ($object->day_close ? "-".sprintf("%02d", $object->day_close) : "");
1116
1117 $this->object_data->period = $period;
1118 }
1119
1120 if (!empty($object->newref)) {
1121 $this->object_data->ref = $object->newref;
1122 }
1123 }
1124
1125 // A trick to be sure all the object_data is an associative array
1126 // json_encode and json_decode are not able to manage mixed object (with array/object, only full arrays or full objects)
1127 $this->object_data = json_decode(json_encode($this->object_data, JSON_FORCE_OBJECT), false);
1128
1129 return 1;
1130 }
1131
1138 public function fetch($id)
1139 {
1140 global $langs;
1141
1142 if (empty($id)) {
1143 $this->error = 'BadParameter';
1144 return -1;
1145 }
1146
1147 $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,";
1148 $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,";
1149 $sql .= " b.note";
1150 $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog as b";
1151 if ($id) {
1152 $sql .= " WHERE b.rowid = ".((int) $id);
1153 }
1154
1155 $resql = $this->db->query($sql);
1156 if ($resql) {
1157 $obj = $this->db->fetch_object($resql);
1158 if ($obj) {
1159 $this->id = $obj->rowid;
1160 $this->entity = $obj->entity;
1161
1162 // Must be at top
1163 $tz = 'gmt';
1164 if (empty($obj->object_format) || $obj->object_format == 'V1') {
1165 $tz = 'tzserver';
1166 }
1167
1168 $this->date_creation = $this->db->jdate($obj->date_creation, $tz); // jdate(date_creation)is UTC
1169 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
1170 $this->date_modification = $this->db->jdate($obj->tms, $tz); // jdate(tms) is UTC
1171
1172
1173 $this->action = $obj->action;
1174 $this->module_source = $obj->module_source;
1175 $this->pos_source = $obj->pos_source;
1176
1177 $this->amounts_taxexcl = (is_null($obj->amounts_taxexcl) ? null : (float) $obj->amounts_taxexcl);
1178 $this->amounts = (float) $obj->amounts;
1179
1180 $this->fk_object = $obj->fk_object;
1181 $this->date_object = $this->db->jdate($obj->date_object, $tz); // jdate(date_object) is UTC
1182 //var_dump($obj->date_object, dol_print_date($this->date_object, 'dayhour' , $tz));
1183 //exit;
1184
1185 $this->ref_object = $obj->ref_object;
1186 $this->type_code = $obj->type_code;
1187 $this->linktoref = $obj->linktoref;
1188 $this->linktype = $obj->linktype;
1189
1190 $this->fk_user = $obj->fk_user;
1191 $this->user_fullname = $obj->user_fullname;
1192
1193 $this->object_data = $this->dolDecodeBlockedData($obj->object_data);
1194 $this->object_version = $obj->object_version;
1195 $this->object_format = $obj->object_format;
1196
1197 $this->element = $obj->element;
1198
1199 $this->signature = $obj->signature;
1200 $this->certified = ($obj->certified == 1);
1201
1202 $this->note = $obj->note;
1203 //$this->debuginfo = $obj->debuginfo; // We don't need this, sot we don't load it to save memory.
1204
1205 return 1;
1206 } else {
1207 $langs->load("errors");
1208 $this->error = $langs->trans("ErrorRecordNotFound");
1209 return 0;
1210 }
1211 } else {
1212 $this->error = $this->db->error();
1213 return -1;
1214 }
1215 }
1216
1217
1225 public function dolEncodeBlockedData($data, $mode = 1)
1226 {
1227 $aaa = json_encode($data);
1228
1229 return $aaa;
1230 }
1231
1232
1240 public function dolDecodeBlockedData($data, $mode = 0)
1241 {
1242 $aaa = null;
1243 try {
1244 $aaa = (object) jsonOrUnserialize($data, false);
1245 } catch (Exception $e) {
1246 // print $e->getErrs);
1247 }
1248
1249 return $aaa;
1250 }
1251
1252
1258 public function setCertified()
1259 {
1260 $res = $this->db->query("UPDATE ".MAIN_DB_PREFIX."blockedlog SET certified = 1 WHERE rowid = ".((int) $this->id));
1261 if (!$res) {
1262 return false;
1263 }
1264
1265 return true;
1266 }
1267
1275 public function create($user, $forcesignature = '')
1276 {
1277 global $conf, $langs, $mysoc;
1278
1279 $langs->load('blockedlog');
1280
1281 // Clean data
1282 $this->amounts = (float) $this->amounts;
1283
1284 dol_syslog(get_class($this).'::create action='.$this->action.' fk_user='.$this->fk_user.' user_fullname='.$this->user_fullname, LOG_DEBUG);
1285
1286 // Check parameters/properties
1287 if (!isset($this->amounts)) { // amount can be 0 for some events (like when module is disabled)
1288 $langs->load("errors");
1289 $this->error = $langs->trans("ErrorBlockLogNeedAmountsValue");
1290 dol_syslog($this->error, LOG_WARNING);
1291 return -1;
1292 }
1293
1294 if (empty($this->element)) {
1295 $langs->load("errors");
1296 $this->error = $langs->trans("ErrorBlockLogNeedElement");
1297 dol_syslog($this->error, LOG_WARNING);
1298 return -2;
1299 }
1300
1301 if (empty($this->object_data)) {
1302 $langs->load("errors");
1303 $this->error = $langs->trans("ErrorBlockLogNeedObject");
1304 dol_syslog($this->error, LOG_WARNING);
1305 return -2;
1306 }
1307
1308 if (empty($this->action)) {
1309 $langs->load("errors");
1310 $this->error = $langs->trans("ErrorBadParameterWhenCallingCreateOfBlockedLog");
1311 dol_syslog($this->error, LOG_WARNING);
1312 return -3;
1313 }
1314 if (empty($this->fk_user)) {
1315 $this->user_fullname = '(Anonymous)';
1316 }
1317
1318 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
1319
1320 $this->db->begin();
1321
1322 $this->date_creation = dol_now();
1323
1324 $this->object_version = DOL_VERSION;
1325
1326 // The object_format defines the formatting rules and syntax into
1327 // buildKeyForSignature and buildFirstPartOfKeyForSignature and buildFinalSignatureHash
1328 // This may vary when the Immutable Log module version is modified, but only if algorithm has changed.
1329 $this->object_format = 'V2';
1330
1331 $tz = 'gmt';
1332
1333 $previoushash = '';
1334 $previousid = 0;
1335 $previousdatecreation = 0;
1336
1337 try {
1338 $tmparray = $this->getPreviousHash(1, 0); // This get last record and lock database until insert is done and transaction closed
1339
1340 $previoushash = $tmparray['previoushash'];
1341 $previousid = $tmparray['previousid'];
1342 $previousdatecreation = $tmparray['previousdatecreation'];
1343
1344 // The string of line to sign
1345 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1346
1347 // The new hash, including previous hash
1348 $this->signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1349
1350 // For debug info (we can clean this field later)
1351 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1352 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1353 }
1354 } catch (Exception $e) {
1355 $this->error = $e->getMessage();
1356
1357 dol_syslog($this->error, LOG_ERR);
1358
1359 $this->db->rollback();
1360 return -1;
1361 }
1362
1363 if ($forcesignature) {
1364 $this->signature = $forcesignature;
1365 }
1366 //var_dump($previoushash, $concatenateddata, $this->signature);
1367
1368 $sql = "INSERT INTO ".MAIN_DB_PREFIX."blockedlog (";
1369 $sql .= " date_creation,";
1370 $sql .= " action,";
1371 $sql .= " module_source,";
1372 $sql .= " pos_source,";
1373 $sql .= " amounts_taxexcl,";
1374 $sql .= " amounts,";
1375 $sql .= " signature,";
1376 $sql .= " element,";
1377 $sql .= " fk_object,";
1378 $sql .= " date_object,";
1379 $sql .= " ref_object,";
1380 $sql .= " type_code,";
1381 $sql .= " linktoref,";
1382 $sql .= " linktype,";
1383 $sql .= " object_data,";
1384 $sql .= " object_version,";
1385 $sql .= " object_format,";
1386 $sql .= " certified,";
1387 $sql .= " fk_user,";
1388 $sql .= " user_fullname,";
1389 $sql .= " entity,";
1390 $sql .= " debuginfo"; // Only stored
1391 $sql .= ") VALUES (";
1392 $sql .= "'".$this->db->idate($this->date_creation, $tz)."',";
1393 $sql .= "'".$this->db->escape($this->action)."',";
1394 $sql .= "'".$this->db->escape((string) $this->module_source)."',";
1395 $sql .= "'".$this->db->escape((string) $this->pos_source)."',";
1396 $sql .= (is_null($this->amounts_taxexcl) ? "null" : (float) $this->amounts_taxexcl).",";
1397 $sql .= (float) $this->amounts.",";
1398 $sql .= "'".$this->db->escape($this->signature)."',";
1399 $sql .= "'".$this->db->escape($this->element)."',";
1400 $sql .= (int) $this->fk_object.",";
1401 $sql .= "'".$this->db->idate($this->date_object, $tz)."',";
1402 $sql .= "'".$this->db->escape($this->ref_object)."',";
1403 $sql .= "'".$this->db->escape($this->type_code)."',";
1404 $sql .= ($this->linktoref ? "'".$this->db->escape($this->linktoref)."'" : "null").",";
1405 $sql .= ($this->linktype ? "'".$this->db->escape($this->linktype)."'" : "null").",";
1406 $sql .= "'".$this->db->escape($this->dolEncodeBlockedData($this->object_data))."',";
1407 $sql .= "'".$this->db->escape($this->object_version)."',";
1408 $sql .= "'".$this->db->escape($this->object_format)."',";
1409 $sql .= "0,";
1410 $sql .= ((int) $this->fk_user).",";
1411 $sql .= "'".$this->db->escape($this->user_fullname)."',";
1412 $sql .= ((int) ($this->entity ? $this->entity : $conf->entity)).",";
1413 $sql .= "'".$this->db->escape($this->debuginfo)."'";
1414 $sql .= ")";
1415
1416 /*
1417 $a = serialize($this->object_data); $a2 = unserialize($a); $a4 = print_r($a2, true);
1418 $b = json_encode($this->object_data); $b2 = json_decode($b); $b4 = print_r($b2, true);
1419 var_dump($a4 == print_r($this->object_data, true) ? 'a=a' : 'a not = a');
1420 var_dump($b4 == print_r($this->object_data, true) ? 'b=b' : 'b not = b');
1421 exit;
1422 */
1423
1424 $res = $this->db->query($sql);
1425 if ($res) {
1426 $id = $this->db->last_insert_id(MAIN_DB_PREFIX."blockedlog");
1427
1428 if ($id > 0) {
1429 // The new ID
1430 $this->id = $id;
1431
1432 // Check and store the signature of this new line in the .end flag.
1433 try {
1434 $finalsignature = $this->signature;
1435 $finalnote = '';
1436
1437 $lockfile = $this->getEndOfChainFlagFile();
1438
1439 // Load the .end flag.
1440 // If not found (has been removed), we track the record as error.
1441 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1442 dol_mkdir(dirname($lockfile)); // Create at least directory for the lock file. Nothing if already exists.
1443
1444 if (!file_exists($lockfile)) {
1445 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1446
1447 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1448
1449 // The string of line to sign
1450 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1451
1452 // The new hash, including previous hash
1453 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1454 $finalnote = $this->note;
1455
1456 $line = '';
1457 } elseif (is_writable($lockfile)) {
1458 $line = file_get_contents($lockfile);
1459 } else {
1460 // Go to the catch()
1461 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.');
1462 }
1463 } else {
1464 $sql = "SELECT value from ".MAIN_DB_PREFIX."const";
1465 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1466 $resql = $this->db->query($sql);
1467 if ($resql) {
1468 $obj = $this->db->fetch_object($resql);
1469 if ($obj) {
1470 $line = $obj->value;
1471 } else {
1472 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1473
1474 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1475
1476 // The string of line to sign
1477 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1478
1479 // The new hash, including previous hash
1480 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1481 $finalnote = $this->note;
1482
1483 $line = '';
1484 }
1485 } else {
1486 // Go to the catch()
1487 throw new Exception("Cannot read into the blockedlog .end flag ".basename($lockfile).' to update it. Transaction aborted.');
1488 }
1489 }
1490
1491 // Check the .end flag file.
1492 $headstring = '';
1493 $remoteobfuscationkey = '';
1494 if (preg_match('/^dolcrypt/', $line)) { // Old method (does not happen after migration)
1495 $headstring = dolDecrypt($line);
1496 } elseif (preg_match('/^dolobfuscation/', $line)) {
1497 $remoteobfuscationkey = $this->getObfuscationKey();
1498 if (empty($remoteobfuscationkey)) {
1499 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.");
1500 }
1501 $headstring = dolDecrypt($line, $remoteobfuscationkey);
1502 }
1503
1504 $reg = array();
1505 if (preg_match('/^BLOCKEDLOGHEAD (\d+) ([^\s]+) ([a-zA-Z0-9\-]+)/', $headstring, $reg)) {
1506 // We succeed in decypting the head
1507 $previousidheadflag = $reg[1];
1508 $previousdatecreationheadflag = $reg[2];
1509 $previoushashheadflag = $reg[3];
1510
1511 // Check the signature of the previous line
1512 if ($previousid < $previousidheadflag || $previoushash != $previoushashheadflag) {
1513 // We detect that old record were removed. We force a non valid signature on the new record.
1514 $this->note = 'EndOfChainDeletionDetected ['.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').' - '.dol_print_date($previousdatecreationheadflag, 'dayhourrfc', 'gmt').']';
1515
1516 // The string of line to sign
1517 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1518
1519 // The new hash, including previous hash
1520 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1521 $finalnote = $this->note;
1522 }
1523 } elseif ($headstring != '') {
1524 // Failed to decrypt the head
1525 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.");
1526 }
1527
1528 // If a note has been added to track an anomaly (signature is also different in this case).
1529 if ($finalsignature != $this->signature) {
1530 // For debug info (we can clean this field later)
1531 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1532 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1533 }
1534
1535 // We update the record we have just inserted to record the new "anomaly" we have detected. Anomaly is also incrusted into the signature.
1536 $sql = "UPDATE ".MAIN_DB_PREFIX."blockedlog";
1537 $sql .= " SET signature = '".$this->db->escape($finalsignature)."',";
1538 $sql .= " note = '".$this->db->escape($finalnote)."',";
1539 $sql .= " debuginfo = '".$this->db->escape($this->debuginfo)."'";
1540 $sql .=" WHERE rowid = ".((int) $this->id);
1541 $resql = $this->db->query($sql);
1542 if (!$resql) {
1543 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.");
1544 }
1545 }
1546
1547
1548 // We can now write the new .end file
1549 $stringtowrite = 'BLOCKEDLOGHEAD '.$this->id." ".dol_print_date($this->date_creation, 'dayhourrfc', 'gmt')." ".(string) $finalsignature;
1550
1551 if (isALNERunningVersion(1, ($this->action == 'MODULE_SET' ? 1 : 0)) && $mysoc->country_code == 'FR') {
1552 $remoteobfuscationkey = $this->getObfuscationKey();
1553 if (empty($remoteobfuscationkey)) {
1554 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.");
1555 }
1556 $stringtowriteencoded = dolEncrypt($stringtowrite, $remoteobfuscationkey, '', '', 'dolobfuscationv1-'.$mysoc->idprof1.'-'.$this->id);
1557 } else {
1558 $stringtowriteencoded = dolEncrypt($stringtowrite, '', '', '', 'dolcrypt-'.$mysoc->idprof1.'-'.$this->id);
1559 }
1560
1561
1562 // Update or create the .end file.
1563 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1564 $lockhandle = fopen($lockfile, 'w+');
1565 if ($lockhandle) {
1566 if (fwrite($lockhandle, $stringtowriteencoded."\n") === false) {
1567 throw new Exception("Cannot write to the blockedlog .end file ".$lockfile);
1568 }
1569
1570 fclose($lockhandle); // Remove the lock
1571 dolChmod($lockfile);
1572 } else {
1573 throw new Exception("Cannot open for writing the blockedlog .end file ".$lockfile);
1574 }
1575 } else {
1576 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1577 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1578 $resql = $this->db->query($sql);
1579
1580 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1581 $sql .= " VALUES('".$this->db->escape(basename($lockfile))."', '".$this->db->escape($stringtowriteencoded)."', 'chaine', 0, 'Blockedlog end of chain flag', ".((int) $conf->entity).")";
1582 $resql = $this->db->query($sql);
1583 if (!$resql) {
1584 throw new Exception("Cannot update the blockedlog .end flag ".basename($lockfile));
1585 }
1586 }
1587 } catch (Exception $e) {
1588 $this->error = $e->getMessage();
1589
1590 dol_syslog($this->error, LOG_ERR);
1591
1592 $this->db->rollback();
1593 return -1;
1594 }
1595
1596 $this->db->commit();
1597
1598 // Call remote API service to record the last counter
1599 /*
1600 $error = 0;
1601
1602 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1603 try {
1604 $resultcall = callApiToPushCounter((int) $this->id, $this->signature, $this->date_creation, 0, (int) $previousid, $previoushash, $previousdatecreation);
1605 } catch (Exception $e) {
1606 $error++;
1607 $this->error = $e->getMessage();
1608 }
1609
1610 if (!$error) {
1611 return $this->id;
1612 } else {
1613 return -3;
1614 }
1615 */
1616 return $this->id;
1617 } else {
1618 $this->db->rollback();
1619 return -2;
1620 }
1621 } else {
1622 $this->error = $this->db->error();
1623 $this->db->rollback();
1624 return -1;
1625 }
1626
1627 // The commit or rollback will release the lock so app can insert other record now
1628 }
1629
1635 public function getEndOfChainFlagFile()
1636 {
1637 global $conf;
1638
1639 // Note: We must not use $conf->blockedlog->dir_output because we need this
1640 // function to work even when module not yet enabled.
1641 return DOL_DATA_ROOT.'/blockedlog/blockedlog-'.((int) $conf->entity).'.end';
1642 }
1643
1651 public function checkSignature($previoushash = '', $returnarray = 0)
1652 {
1653 if (empty($previoushash)) {
1654 $tmparray = $this->getPreviousHash(0, $this->id);
1655 $previoushash = $tmparray['previoushash'];
1656 }
1657
1658 $concatenateddata = '';
1659 $signature = '';
1660
1661 // Recalculate the signature
1662 try {
1663 // Build the string for the signature
1664 $concatenateddata = $this->buildKeyForSignature();
1665
1666 $signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata);
1667
1668 //var_dump($previoushash, $concatenateddata, $this->object_format, $signature);
1669 } catch (Exception $e) {
1670 $res = ($signature === $this->signature);
1671 $this->error = $e->getMessage();
1672
1673 dol_syslog($this->error, LOG_ERR);
1674
1675 if ($returnarray) {
1676 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'error' => $this->error);
1677 } else {
1678 return false;
1679 }
1680 }
1681
1682 $res = ($signature === $this->signature);
1683
1684 if (!$res) {
1685 $this->error = 'Signature KO';
1686 }
1687
1688 if ($returnarray) {
1689 if ($returnarray == 1) {
1690 unset($concatenateddata);
1691 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash);
1692 } else { // Consume much memory ($concatenateddata is a large var)
1693 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'keyforsignature' => $concatenateddata);
1694 }
1695 } else {
1696 unset($concatenateddata);
1697 return $res;
1698 }
1699 }
1700
1709 private function buildFirstPartOfKeyForSignature($format = '')
1710 {
1711 if (empty($format)) {
1712 $format = $this->object_format;
1713 }
1714
1715 // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1716 if ($format == '') {
1717 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1718 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1719 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1720 } elseif ($format == 'V2') {
1721 $s = $this->entity;
1722 $s .= '|'.$this->date_creation.'|'.$this->action.'|'.$this->module_source.'|'.$this->pos_source.'|'.$this->amounts_taxexcl;
1723 $s .= '|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1724 if ($this->type_code) {
1725 $s .= '|'.(string) $this->type_code;
1726 }
1727 $s .= '|'.(string) $this->linktoref;
1728 $s .= '|'.(string) $this->linktype;
1729 if ($this->note) {
1730 $s .= '|'.(string) $this->note;
1731 }
1732 return $s;
1733 } else {
1734 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1735 }
1736 }
1737
1744 public function buildKeyForSignature($format = '')
1745 {
1746 //print_r($this->object_data);
1747 if (empty($format)) {
1748 $format = $this->object_format;
1749 }
1750
1751 if ($format == '') {
1752 return $this->buildFirstPartOfKeyForSignature($format).'|'.print_r($this->object_data, true);
1753 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1754 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1755 } elseif ($format == 'V2') {
1756 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1757 } else {
1758 throw new Exception('Error bad value "'.$format.'" for object_format');
1759 }
1760 }
1761
1769 private function buildFinalSignatureHash($clearstring, $format = '')
1770 {
1771 global $mysoc;
1772
1773 if (empty($format)) {
1774 $format = $this->object_format;
1775 }
1776
1777 if ($format == '') {
1778 return dol_hash($clearstring, '5');
1779 } elseif ($format == 'V1') {
1780 return dol_hash($clearstring, '5');
1781 } elseif ($format == 'V2') {
1782 // BLOCKEDLOG_HMAC_KEY is a HMAC key starting with 'BLOCKEDLOGHMAC....'. It is not stored as a clear data but
1783 // is a string dolcrypt:... or dolobfuscationv1... It will be decrypted later.
1784 $hmac_encoded_secret_key = $this->getEncodedHMACSecretKey();
1785
1786 if (empty($hmac_encoded_secret_key)) {
1787 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');
1788 }
1789
1790 // Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
1791 $hmac_secret_key = '';
1792 try {
1793 $hmac_secret_key = $this->getClearHMACSecretKey($hmac_encoded_secret_key); // Note: On network trouble, an Exception is thrown to the caller
1794 } catch (Exception $e) {
1795 $firsterrormessage = $e->getMessage();
1796
1797 // Another chance to get HMAC when saved with old obfuscation method (dolcrypt)
1798 $hmac_encoded_secret_key_alt = $this->getEncodedHMACSecretKey(1, 1);
1799 if (!empty($hmac_encoded_secret_key_alt)) {
1800 try {
1801 $hmac_secret_key_alt = $this->getClearHMACSecretKey($hmac_encoded_secret_key_alt); // Note: On network trouble, an Exception is thrown to the caller
1802 if (preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key_alt)) { // Alternative is ok
1803 $hmac_secret_key = $hmac_secret_key_alt;
1804 }
1805 } catch (Exception $e) {
1806 throw new Exception($firsterrormessage);
1807 }
1808 } else {
1809 throw new Exception($firsterrormessage);
1810 }
1811 }
1812
1813 // Last check on validity of key
1814 if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
1815 //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.');
1816 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).');
1817 }
1818
1819 // Here the $hmac_secret_key is in memory with the correct value.
1820
1821 // On old versions, we must switch the data saving mode to use the new method.
1822 // Live migration of the way the key is stored.
1823 $needremoteobfuscation = (isALNERunningVersion(1) && $mysoc->country_code == 'FR');
1824
1825 if ($needremoteobfuscation && !preg_match('/^dolobfuscationv1/', $hmac_encoded_secret_key)) { // For old versions, we must switch the data saving mode to use the new method.
1826 $obfuscationkey = '';
1827 $errormsg = '';
1828 try {
1829 $obfuscationkey = $this->getObfuscationKey(); // Get obfuscation key providing $mysoc->idprof1 and $registrationnumber. Note: On network trouble, an Exception is thrown to the caller
1830 } catch (Exception $e) {
1831 $errormsg = $e->getMessage();
1832 }
1833 if (!$errormsg && $obfuscationkey) {
1834 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolobfuscationv1-'.$mysoc->idprof1, $obfuscationkey); // gitleaks:allow
1835 }
1836 } elseif (!$needremoteobfuscation && !preg_match('/^dolcrypt/', $hmac_encoded_secret_key)) {
1837 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolcrypt'); // gitleaks:allow
1838 }
1839
1840 // Here HMAC secret key is a long string starting with BLOCKEDLOGHMAC..., we can use it to sign the data.
1841 return hash_hmac('sha256', $clearstring, $hmac_secret_key);
1842 } else {
1843 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1844 }
1845 }
1846
1847
1856 public function saveHMACSecretKey($hmac_secret_key, $obfuscationmode, $obfuscationkey = '')
1857 {
1858 global $conf;
1859
1860 //var_dump($hmac_secret_key, $obfuscationmode, $obfuscationkey);exit;
1861
1862 if (preg_match('/^dolobfuscationv1/', $obfuscationmode) && empty($obfuscationkey)) {
1863 return -1;
1864 }
1865
1866 $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.
1867
1868 $this->db->begin();
1869
1870 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1871 $sql .= " WHERE name = '".$this->db->escape($name)."'";
1872 if ($this->entity >= 0) {
1873 $sql .= " AND entity = ".((int) $this->entity);
1874 }
1875
1876 dol_syslog("saveHMACSecretKey", LOG_DEBUG);
1877
1878 $resql = $this->db->query($sql);
1879
1880 if (preg_match('/^dolobfuscationv1/', $obfuscationmode)) {
1881 $newvalue = dolEncrypt($hmac_secret_key, $obfuscationkey, '', '', $obfuscationmode); // AES-256
1882 } else {
1883 // if ($obfuscationmode == 'dolcrypt')
1884 $newvalue = dolEncrypt($hmac_secret_key); // AES-256
1885 }
1886
1887 // Clear cache
1888 unset($conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)]);
1889
1890 // Save in database
1891 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1892 $sql .= " VALUES (";
1893 $sql .= "'".$this->db->escape($name)."'";
1894 $sql .= ", '".$this->db->escape($newvalue)."'";
1895 $sql .= ", 'chaine', '0', '', ".((int) $this->entity).")";
1896
1897 //print "xx".$db->escape($value);
1898 $resql = $this->db->query($sql);
1899
1900 if ($resql) {
1901 // Now reload it to check it was saved correctly for a paranoiac control.
1902 $saved = $this->getEncodedHMACSecretKey(); // This also reload the cache
1903
1904 if ($saved == $newvalue) {
1905 $this->db->commit();
1906
1907 return 1;
1908 } else {
1909 $this->error = 'Data read of HMAC key is not same than the one we expect to save.';
1910 $this->db->rollback();
1911
1912 return -1;
1913 }
1914 } else {
1915 $this->error = $this->db->lasterror();
1916 $this->db->rollback();
1917
1918 return -1;
1919 }
1920 }
1921
1922
1931 public function getObfuscationKey()
1932 {
1933 global $conf, $mysoc;
1934
1935
1936 // Uncomment the next line to emulate a network error to get the remote obfuscation key
1937 //throw new Exception('Failed to get the remote obfuscation key - error emulated');
1938
1939 // If key found into the user session memory cache, we use it
1940 if (!empty($_SESSION['obfuscationkey_'.((int) $this->entity)])) {
1941 dol_syslog("getObfuscationKey remote obfuscation key found into session cache", LOG_DEBUG);
1942 return (string) $_SESSION['obfuscationkey_'.((int) $this->entity)];
1943 }
1944 // If key found into the page memory cache, we use it
1945 if (!empty($conf->cache['obfuscationkey_'.((int) $this->entity)])) {
1946 dol_syslog("getObfuscationKey remote obfuscation key found into conf->cache", LOG_DEBUG);
1947 return (string) $conf->cache['obfuscationkey_'.((int) $this->entity)];
1948 }
1949
1950 $obfuscationkey = '';
1951
1952 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1953 $registrationnumber = getHashUniqueIdOfRegistration();
1954
1955 // Value is not into cache, we must get it from ping.dolibarr.org
1956 $obfuscationkey = callApiToGetObfuscationKey($mysoc->idprof1, $registrationnumber);
1957 if (empty($obfuscationkey)) {
1958 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);
1959 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.');
1960 }
1961 if (strpos($obfuscationkey, 'ERROR') === 0) {
1962 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);
1963 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);
1964 }
1965
1966 // Now store value in cache
1967 if ($obfuscationkey) {
1968 $_SESSION['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1969 $conf->cache['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1970 }
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 = ".$conf->entity;
2233 } elseif ($element == 'not_certified') {
2234 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2235 WHERE entity = ".$conf->entity." AND certified = 0";
2236 } elseif ($element == 'just_certified') {
2237 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2238 WHERE entity = ".$conf->entity." AND certified = 1";
2239 } else {
2240 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2241 WHERE entity = ".$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 $tmp = natural_search("module_source", implode(',', $search_module_source), 0, 1);
2295 $tmp = str_replace('%backoffice%', '', $tmp);
2296 $sql .= $tmp;
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.
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.