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