dolibarr 25.0.0-alpha
blockedlog.class.php
1<?php
2/* Copyright (C) 2017 ATM Consulting <contact@atm-consulting.fr>
3 * Copyright (C) 2017-2020 Laurent Destailleur <eldy@destailleur.fr>
4 * Copyright (C) 2022 charlene benke <charlene@patas-monkey.com>
5 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 *
21 * See https://medium.com/@lhartikk/a-blockchain-in-200-lines-of-code-963cc1cc0e54
22 */
23
24include_once DOL_DOCUMENT_ROOT.'/blockedlog/versionmod.inc.php';
25include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
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->date_object)) { // date_object is a critical field, it is included into the line signature
1310 $langs->load("errors");
1311 $this->error = $langs->trans("ErrorBlockLogNeedDateObject");
1312 dol_syslog($this->error, LOG_WARNING);
1313 return -2;
1314 }
1315
1316 if (empty($this->action)) {
1317 $langs->load("errors");
1318 $this->error = $langs->trans("ErrorBadParameterWhenCallingCreateOfBlockedLog");
1319 dol_syslog($this->error, LOG_WARNING);
1320 return -3;
1321 }
1322 if (empty($this->fk_user)) {
1323 $this->user_fullname = '(Anonymous)';
1324 }
1325
1326 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
1327
1328 $this->db->begin();
1329
1330 $this->date_creation = dol_now();
1331
1332 $this->object_version = DOL_VERSION;
1333
1334 // The object_format defines the formatting rules and syntax into
1335 // buildKeyForSignature and buildFirstPartOfKeyForSignature and buildFinalSignatureHash
1336 // This may vary when the Immutable Log module version is modified, but only if algorithm has changed.
1337 $this->object_format = 'V2';
1338
1339 $tz = 'gmt';
1340
1341 $previoushash = '';
1342 $previousid = 0;
1343 $previousdatecreation = 0;
1344
1345 try {
1346 $tmparray = $this->getPreviousHash(1, 0); // This get last record and lock database until insert is done and transaction closed
1347
1348 $previoushash = $tmparray['previoushash'];
1349 $previousid = $tmparray['previousid'];
1350 $previousdatecreation = $tmparray['previousdatecreation'];
1351
1352 // The string of line to sign
1353 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1354
1355 // The new hash, including previous hash
1356 $this->signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1357
1358 // For debug info (we can clean this field later)
1359 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1360 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1361 }
1362 } catch (Exception $e) {
1363 $this->error = $e->getMessage();
1364
1365 dol_syslog($this->error, LOG_ERR);
1366
1367 $this->db->rollback();
1368 return -1;
1369 }
1370
1371 if ($forcesignature) {
1372 $this->signature = $forcesignature;
1373 }
1374 //var_dump($previoushash, $concatenateddata, $this->signature);
1375
1376 $sql = "INSERT INTO ".MAIN_DB_PREFIX."blockedlog (";
1377 $sql .= " date_creation,";
1378 $sql .= " action,";
1379 $sql .= " module_source,";
1380 $sql .= " pos_source,";
1381 $sql .= " amounts_taxexcl,";
1382 $sql .= " amounts,";
1383 $sql .= " signature,";
1384 $sql .= " element,";
1385 $sql .= " fk_object,";
1386 $sql .= " date_object,";
1387 $sql .= " ref_object,";
1388 $sql .= " type_code,";
1389 $sql .= " linktoref,";
1390 $sql .= " linktype,";
1391 $sql .= " object_data,";
1392 $sql .= " object_version,";
1393 $sql .= " object_format,";
1394 $sql .= " certified,";
1395 $sql .= " fk_user,";
1396 $sql .= " user_fullname,";
1397 $sql .= " entity,";
1398 $sql .= " debuginfo"; // Only stored
1399 $sql .= ") VALUES (";
1400 $sql .= "'".$this->db->idate($this->date_creation, $tz)."',";
1401 $sql .= "'".$this->db->escape($this->action)."',";
1402 $sql .= "'".$this->db->escape((string) $this->module_source)."',";
1403 $sql .= "'".$this->db->escape((string) $this->pos_source)."',";
1404 $sql .= (is_null($this->amounts_taxexcl) ? "null" : (float) $this->amounts_taxexcl).",";
1405 $sql .= (float) $this->amounts.",";
1406 $sql .= "'".$this->db->escape($this->signature)."',";
1407 $sql .= "'".$this->db->escape($this->element)."',";
1408 $sql .= (int) $this->fk_object.",";
1409 $sql .= "'".$this->db->idate($this->date_object, $tz)."',";
1410 $sql .= "'".$this->db->escape($this->ref_object)."',";
1411 $sql .= "'".$this->db->escape($this->type_code)."',";
1412 $sql .= ($this->linktoref ? "'".$this->db->escape($this->linktoref)."'" : "null").",";
1413 $sql .= ($this->linktype ? "'".$this->db->escape($this->linktype)."'" : "null").",";
1414 $sql .= "'".$this->db->escape($this->dolEncodeBlockedData($this->object_data))."',";
1415 $sql .= "'".$this->db->escape($this->object_version)."',";
1416 $sql .= "'".$this->db->escape($this->object_format)."',";
1417 $sql .= "0,";
1418 $sql .= ((int) $this->fk_user).",";
1419 $sql .= "'".$this->db->escape($this->user_fullname)."',";
1420 $sql .= ((int) ($this->entity ? ((int) $this->entity) : ((int) $conf->entity))).",";
1421 $sql .= "'".$this->db->escape($this->debuginfo)."'";
1422 $sql .= ")";
1423
1424 /*
1425 $a = serialize($this->object_data); $a2 = unserialize($a); $a4 = print_r($a2, true);
1426 $b = json_encode($this->object_data); $b2 = json_decode($b); $b4 = print_r($b2, true);
1427 var_dump($a4 == print_r($this->object_data, true) ? 'a=a' : 'a not = a');
1428 var_dump($b4 == print_r($this->object_data, true) ? 'b=b' : 'b not = b');
1429 exit;
1430 */
1431
1432 $res = $this->db->query($sql);
1433 if ($res) {
1434 $id = $this->db->last_insert_id(MAIN_DB_PREFIX."blockedlog");
1435
1436 if ($id > 0) {
1437 // The new ID
1438 $this->id = $id;
1439
1440 // Check and store the signature of this new line in the .end flag.
1441 try {
1442 $finalsignature = $this->signature;
1443 $finalnote = '';
1444
1445 $lockfile = $this->getEndOfChainFlagFile();
1446
1447 // Load the .end flag.
1448 // If not found (has been removed), we track the record as error.
1449 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1450 dol_mkdir(dirname($lockfile)); // Create at least directory for the lock file. Nothing if already exists.
1451
1452 if (!file_exists($lockfile)) {
1453 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1454
1455 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1456
1457 // The string of line to sign
1458 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1459
1460 // The new hash, including previous hash
1461 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1462 $finalnote = $this->note;
1463
1464 $line = '';
1465 } elseif (is_writable($lockfile)) {
1466 $line = file_get_contents($lockfile);
1467 } else {
1468 // Go to the catch()
1469 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.');
1470 }
1471 } else {
1472 $sql = "SELECT value from ".MAIN_DB_PREFIX."const";
1473 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1474 $resql = $this->db->query($sql);
1475 if ($resql) {
1476 $obj = $this->db->fetch_object($resql);
1477 if ($obj) {
1478 $line = $obj->value;
1479 } else {
1480 //throw new Exception("The head file ".$lockfile." was not found or is not writable.");
1481
1482 $this->note = 'EndOfChainDeletionDetected [after '.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').']';
1483
1484 // The string of line to sign
1485 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1486
1487 // The new hash, including previous hash
1488 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1489 $finalnote = $this->note;
1490
1491 $line = '';
1492 }
1493 } else {
1494 // Go to the catch()
1495 throw new Exception("Cannot read into the blockedlog .end flag ".basename($lockfile).' to update it. Transaction aborted.');
1496 }
1497 }
1498
1499 // Check the .end flag file.
1500 $headstring = '';
1501 $remoteobfuscationkey = '';
1502 if (preg_match('/^dolcrypt/', $line)) { // Old method (does not happen after migration)
1503 $headstring = dolDecrypt($line);
1504 } elseif (preg_match('/^dolobfuscation/', $line)) {
1505 $remoteobfuscationkey = $this->getObfuscationKey();
1506 if (empty($remoteobfuscationkey)) {
1507 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.");
1508 }
1509 $headstring = dolDecrypt($line, $remoteobfuscationkey);
1510 }
1511
1512 $reg = array();
1513 if (preg_match('/^BLOCKEDLOGHEAD (\d+) ([^\s]+) ([a-zA-Z0-9\-]+)/', $headstring, $reg)) {
1514 // We succeed in decypting the head
1515 $previousidheadflag = $reg[1];
1516 $previousdatecreationheadflag = $reg[2];
1517 $previoushashheadflag = $reg[3];
1518
1519 // Check the signature of the previous line
1520 if ($previousid < $previousidheadflag || $previoushash != $previoushashheadflag) {
1521 // We detect that old record were removed. We force a non valid signature on the new record.
1522 $this->note = 'EndOfChainDeletionDetected ['.dol_print_date($previousdatecreation, 'dayhourrfc', 'gmt').' - '.dol_print_date($previousdatecreationheadflag, 'dayhourrfc', 'gmt').']';
1523
1524 // The string of line to sign
1525 $concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
1526
1527 // The new hash, including previous hash
1528 $finalsignature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
1529 $finalnote = $this->note;
1530 }
1531 } elseif ($headstring != '') {
1532 // Failed to decrypt the head
1533 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.");
1534 }
1535
1536 // If a note has been added to track an anomaly (signature is also different in this case).
1537 if ($finalsignature != $this->signature) {
1538 // For debug info (we can clean this field later)
1539 if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
1540 $this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
1541 }
1542
1543 // We update the record we have just inserted to record the new "anomaly" we have detected. Anomaly is also incrusted into the signature.
1544 $sql = "UPDATE ".MAIN_DB_PREFIX."blockedlog";
1545 $sql .= " SET signature = '".$this->db->escape($finalsignature)."',";
1546 $sql .= " note = '".$this->db->escape($finalnote)."',";
1547 $sql .= " debuginfo = '".$this->db->escape($this->debuginfo)."'";
1548 $sql .= " WHERE rowid = ".((int) $this->id);
1549 $resql = $this->db->query($sql);
1550 if (!$resql) {
1551 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.");
1552 }
1553 }
1554
1555
1556 // We can now write the new .end file
1557 $stringtowrite = 'BLOCKEDLOGHEAD '.$this->id." ".dol_print_date($this->date_creation, 'dayhourrfc', 'gmt')." ".(string) $finalsignature;
1558
1559 if (isALNERunningVersion(1, ($this->action == 'MODULE_SET' ? 1 : 0)) && $mysoc->country_code == 'FR') {
1560 $remoteobfuscationkey = $this->getObfuscationKey();
1561 if (empty($remoteobfuscationkey)) {
1562 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.");
1563 }
1564 $stringtowriteencoded = dolEncrypt($stringtowrite, $remoteobfuscationkey, '', '', 'dolobfuscationv1-'.$mysoc->idprof1.'-'.$this->id);
1565 } else {
1566 $stringtowriteencoded = dolEncrypt($stringtowrite, '', '', '', 'dolcrypt-'.$mysoc->idprof1.'-'.$this->id);
1567 }
1568
1569
1570 // Update or create the .end file.
1571 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
1572 $lockhandle = fopen($lockfile, 'w+');
1573 if ($lockhandle) {
1574 if (fwrite($lockhandle, $stringtowriteencoded."\n") === false) {
1575 throw new Exception("Cannot write to the blockedlog .end file ".$lockfile);
1576 }
1577
1578 fclose($lockhandle); // Remove the lock
1579 dolChmod($lockfile);
1580 } else {
1581 throw new Exception("Cannot open for writing the blockedlog .end file ".$lockfile);
1582 }
1583 } else {
1584 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1585 $sql .= " WHERE name = '".$this->db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
1586 $resql = $this->db->query($sql);
1587
1588 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1589 $sql .= " VALUES('".$this->db->escape(basename($lockfile))."', '".$this->db->escape($stringtowriteencoded)."', 'chaine', 0, 'Blockedlog end of chain flag', ".((int) $conf->entity).")";
1590 $resql = $this->db->query($sql);
1591 if (!$resql) {
1592 throw new Exception("Cannot update the blockedlog .end flag ".basename($lockfile));
1593 }
1594 }
1595 } catch (Exception $e) {
1596 $this->error = $e->getMessage();
1597
1598 dol_syslog($this->error, LOG_ERR);
1599
1600 $this->db->rollback();
1601 return -1;
1602 }
1603
1604 $this->db->commit();
1605
1606 // Call remote API service to record the last counter
1607 /*
1608 $error = 0;
1609
1610 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1611 try {
1612 $resultcall = callApiToPushCounter((int) $this->id, $this->signature, $this->date_creation, 0, (int) $previousid, $previoushash, $previousdatecreation);
1613 } catch (Exception $e) {
1614 $error++;
1615 $this->error = $e->getMessage();
1616 }
1617
1618 if (!$error) {
1619 return $this->id;
1620 } else {
1621 return -3;
1622 }
1623 */
1624 return $this->id;
1625 } else {
1626 $this->db->rollback();
1627 return -2;
1628 }
1629 } else {
1630 $this->error = $this->db->error();
1631 $this->db->rollback();
1632 return -1;
1633 }
1634
1635 // The commit or rollback will release the lock so app can insert other record now
1636 }
1637
1643 public function getEndOfChainFlagFile()
1644 {
1645 global $conf;
1646
1647 // Note: We must not use $conf->blockedlog->dir_output because we need this
1648 // function to work even when module not yet enabled.
1649 return DOL_DATA_ROOT.'/blockedlog/blockedlog-'.((int) $conf->entity).'.end';
1650 }
1651
1659 public function checkSignature($previoushash = '', $returnarray = 0)
1660 {
1661 if (empty($previoushash)) {
1662 $tmparray = $this->getPreviousHash(0, $this->id);
1663 $previoushash = $tmparray['previoushash'];
1664 }
1665
1666 $concatenateddata = '';
1667 $signature = '';
1668
1669 // Recalculate the signature
1670 try {
1671 // Build the string for the signature
1672 $concatenateddata = $this->buildKeyForSignature();
1673
1674 $signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata);
1675
1676 //var_dump($previoushash, $concatenateddata, $this->object_format, $signature);
1677 } catch (Exception $e) {
1678 $res = ($signature === $this->signature);
1679 $this->error = $e->getMessage();
1680
1681 dol_syslog($this->error, LOG_ERR);
1682
1683 if ($returnarray) {
1684 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'error' => $this->error);
1685 } else {
1686 return false;
1687 }
1688 }
1689
1690 $res = ($signature === $this->signature);
1691
1692 if (!$res) {
1693 $this->error = 'Signature KO';
1694 }
1695
1696 if ($returnarray) {
1697 if ($returnarray == 1) {
1698 unset($concatenateddata);
1699 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash);
1700 } else { // Consume much memory ($concatenateddata is a large var)
1701 return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'keyforsignature' => $concatenateddata);
1702 }
1703 } else {
1704 unset($concatenateddata);
1705 return $res;
1706 }
1707 }
1708
1717 private function buildFirstPartOfKeyForSignature($format = '')
1718 {
1719 if (empty($format)) {
1720 $format = $this->object_format;
1721 }
1722
1723 // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1724 if ($format == '') {
1725 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1726 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1727 return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1728 } elseif ($format == 'V2') {
1729 $s = $this->entity;
1730 $s .= '|'.$this->date_creation.'|'.$this->action.'|'.$this->module_source.'|'.$this->pos_source.'|'.$this->amounts_taxexcl;
1731 $s .= '|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
1732 if ($this->type_code) {
1733 $s .= '|'.(string) $this->type_code;
1734 }
1735 $s .= '|'.(string) $this->linktoref;
1736 $s .= '|'.(string) $this->linktype;
1737 if ($this->note) {
1738 $s .= '|'.(string) $this->note;
1739 }
1740 return $s;
1741 } else {
1742 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1743 }
1744 }
1745
1752 public function buildKeyForSignature($format = '')
1753 {
1754 //print_r($this->object_data);
1755 if (empty($format)) {
1756 $format = $this->object_format;
1757 }
1758
1759 if ($format == '') {
1760 return $this->buildFirstPartOfKeyForSignature($format).'|'.print_r($this->object_data, true);
1761 } elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
1762 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1763 } elseif ($format == 'V2') {
1764 return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
1765 } else {
1766 throw new Exception('Error bad value "'.$format.'" for object_format');
1767 }
1768 }
1769
1777 private function buildFinalSignatureHash($clearstring, $format = '')
1778 {
1779 global $mysoc;
1780
1781 if (empty($format)) {
1782 $format = $this->object_format;
1783 }
1784
1785 if ($format == '') {
1786 return dol_hash($clearstring, '5');
1787 } elseif ($format == 'V1') {
1788 return dol_hash($clearstring, '5');
1789 } elseif ($format == 'V2') {
1790 // BLOCKEDLOG_HMAC_KEY is a HMAC key starting with 'BLOCKEDLOGHMAC....'. It is not stored as a clear data but
1791 // is a string dolcrypt:... or dolobfuscationv1... It will be decrypted later.
1792 $hmac_encoded_secret_key = $this->getEncodedHMACSecretKey();
1793
1794 if (empty($hmac_encoded_secret_key)) {
1795 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');
1796 }
1797
1798 // Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
1799 $hmac_secret_key = '';
1800 try {
1801 $hmac_secret_key = $this->getClearHMACSecretKey($hmac_encoded_secret_key); // Note: On network trouble, an Exception is thrown to the caller
1802 } catch (Exception $e) {
1803 $firsterrormessage = $e->getMessage();
1804
1805 // Another chance to get HMAC when saved with old obfuscation method (dolcrypt)
1806 $hmac_encoded_secret_key_alt = $this->getEncodedHMACSecretKey(1, 1);
1807 if (!empty($hmac_encoded_secret_key_alt)) {
1808 try {
1809 $hmac_secret_key_alt = $this->getClearHMACSecretKey($hmac_encoded_secret_key_alt); // Note: On network trouble, an Exception is thrown to the caller
1810 if (preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key_alt)) { // Alternative is ok
1811 $hmac_secret_key = $hmac_secret_key_alt;
1812 }
1813 } catch (Exception $e) {
1814 throw new Exception($firsterrormessage);
1815 }
1816 } else {
1817 throw new Exception($firsterrormessage);
1818 }
1819 }
1820
1821 // Last check on validity of key
1822 if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
1823 //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.');
1824 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).');
1825 }
1826
1827 // Here the $hmac_secret_key is in memory with the correct value.
1828
1829 // On old versions, we must switch the data saving mode to use the new method.
1830 // Live migration of the way the key is stored.
1831 $needremoteobfuscation = (isALNERunningVersion(1) && $mysoc->country_code == 'FR');
1832
1833 if ($needremoteobfuscation && !preg_match('/^dolobfuscationv1/', $hmac_encoded_secret_key)) { // For old versions, we must switch the data saving mode to use the new method.
1834 $obfuscationkey = '';
1835 $errormsg = '';
1836 try {
1837 $obfuscationkey = $this->getObfuscationKey(); // Get obfuscation key providing $mysoc->idprof1 and $registrationnumber. Note: On network trouble, an Exception is thrown to the caller
1838 } catch (Exception $e) {
1839 $errormsg = $e->getMessage();
1840 }
1841 if (!$errormsg && $obfuscationkey) {
1842 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolobfuscationv1-'.$mysoc->idprof1, $obfuscationkey); // gitleaks:allow
1843 }
1844 } elseif (!$needremoteobfuscation && !preg_match('/^dolcrypt/', $hmac_encoded_secret_key)) {
1845 $this->saveHMACSecretKey((string) $hmac_secret_key, 'dolcrypt'); // gitleaks:allow
1846 }
1847
1848 // Here HMAC secret key is a long string starting with BLOCKEDLOGHMAC..., we can use it to sign the data.
1849 return hash_hmac('sha256', $clearstring, $hmac_secret_key);
1850 } else {
1851 throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
1852 }
1853 }
1854
1855
1864 public function saveHMACSecretKey($hmac_secret_key, $obfuscationmode, $obfuscationkey = '')
1865 {
1866 global $conf;
1867
1868 //var_dump($hmac_secret_key, $obfuscationmode, $obfuscationkey);exit;
1869
1870 if (preg_match('/^dolobfuscationv1/', $obfuscationmode) && empty($obfuscationkey)) {
1871 return -1;
1872 }
1873
1874 $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.
1875
1876 $this->db->begin();
1877
1878 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1879 $sql .= " WHERE name = '".$this->db->escape($name)."'";
1880 if ($this->entity >= 0) {
1881 $sql .= " AND entity = ".((int) $this->entity);
1882 }
1883
1884 dol_syslog("saveHMACSecretKey", LOG_DEBUG);
1885
1886 $resql = $this->db->query($sql);
1887
1888 if (preg_match('/^dolobfuscationv1/', $obfuscationmode)) {
1889 $newvalue = dolEncrypt($hmac_secret_key, $obfuscationkey, '', '', $obfuscationmode); // AES-256
1890 } else {
1891 // if ($obfuscationmode == 'dolcrypt')
1892 $newvalue = dolEncrypt($hmac_secret_key); // AES-256
1893 }
1894
1895 // Clear cache
1896 unset($conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)]);
1897
1898 // Save in database
1899 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const(name, value, type, visible, note, entity)";
1900 $sql .= " VALUES (";
1901 $sql .= "'".$this->db->escape($name)."'";
1902 $sql .= ", '".$this->db->escape($newvalue)."'";
1903 $sql .= ", 'chaine', '0', '', ".((int) $this->entity).")";
1904
1905 //print "xx".$db->escape($value);
1906 $resql = $this->db->query($sql);
1907
1908 if ($resql) {
1909 // Now reload it to check it was saved correctly for a paranoiac control.
1910 $saved = $this->getEncodedHMACSecretKey(); // This also reload the cache
1911
1912 if ($saved == $newvalue) {
1913 $this->db->commit();
1914
1915 return 1;
1916 } else {
1917 $this->error = 'Data read of HMAC key is not same than the one we expect to save.';
1918 $this->db->rollback();
1919
1920 return -1;
1921 }
1922 } else {
1923 $this->error = $this->db->lasterror();
1924 $this->db->rollback();
1925
1926 return -1;
1927 }
1928 }
1929
1930
1939 public function getObfuscationKey()
1940 {
1941 global $conf, $mysoc;
1942
1943
1944 // Uncomment the next line to emulate a network error to get the remote obfuscation key
1945 //throw new Exception('Failed to get the remote obfuscation key - error emulated');
1946
1947 // If key found into the user session memory cache, we use it
1948 if (!empty($_SESSION['obfuscationkey_'.((int) $this->entity)])) {
1949 dol_syslog("getObfuscationKey remote obfuscation key found into session cache", LOG_DEBUG);
1950 return (string) $_SESSION['obfuscationkey_'.((int) $this->entity)];
1951 }
1952 // If key found into the page memory cache, we use it
1953 if (!empty($conf->cache['obfuscationkey_'.((int) $this->entity)])) {
1954 dol_syslog("getObfuscationKey remote obfuscation key found into conf->cache", LOG_DEBUG);
1955 return (string) $conf->cache['obfuscationkey_'.((int) $this->entity)];
1956 }
1957
1958 $obfuscationkey = '';
1959
1960 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
1961 $registrationnumber = getHashUniqueIdOfRegistration();
1962
1963 // Value is not into cache, we must get it from ping.dolibarr.org
1964 $obfuscationkey = callApiToGetObfuscationKey((string) $mysoc->idprof1, $registrationnumber);
1965 if (empty($obfuscationkey)) {
1966 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);
1967 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.');
1968 }
1969 if (strpos($obfuscationkey, 'ERROR') === 0) {
1970 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);
1971 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);
1972 }
1973
1974 // Now store value in cache ($obfuscationkey is not empty because of empty/throw above).
1975 $_SESSION['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1976 $conf->cache['obfuscationkey_'.((int) $this->entity)] = $obfuscationkey;
1977
1978 return (string) $obfuscationkey;
1979 }
1980
1989 public function getEncodedHMACSecretKey($nocache = 0, $noentity = 0)
1990 {
1991 global $conf;
1992
1993 $hmac_encoded_secret_key = '';
1994
1995 // Get value of the $hmac_encoded_secret_key from the database
1996 if ($nocache || empty($conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)])) {
1997 $sql = "SELECT value FROM ".MAIN_DB_PREFIX."const WHERE name = 'BLOCKEDLOG_HMAC_KEY'";
1998 if ($noentity) {
1999 $sql .= " AND entity IN (0)"; // To force to get value on old instances that may have been saved with entity = 0
2000 } else {
2001 $sql .= " AND entity IN (0, ".((int) $this->entity).")";
2002 }
2003 $sql .= " ORDER BY entity DESC LIMIT 1";
2004
2005 $resql = $this->db->query($sql);
2006 if ($resql) {
2007 $obj = $this->db->fetch_object($resql);
2008 if ($obj) {
2009 $hmac_encoded_secret_key = $obj->value;
2010
2011 // Save value in memory page cache (if we recall the same function in same page transaction, we will avoid db access).
2012 if (empty($nocache)) {
2013 $conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)] = $hmac_encoded_secret_key;
2014 }
2015 }
2016 } else {
2017 return 'ERROR '.$this->db->lasterror();
2018 }
2019 } else {
2020 $hmac_encoded_secret_key = $conf->cache['hmac_encoded_secret_key_'.((int) $this->entity)];
2021 }
2022
2023 return $hmac_encoded_secret_key;
2024 }
2025
2032 public function getClearHMACSecretKey($hmac_encoded_secret_key)
2033 {
2034 // Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
2035 $hmac_secret_key = '';
2036 $errormsg = '';
2037
2038 // Get the obfuscation key from ping.dolibarr.org (to be used just after to decode HMAC secret key)
2039 if (preg_match('/^dolobfuscation/', $hmac_encoded_secret_key)) {
2040 $obfuscationkey = '';
2041 try {
2042 $obfuscationkey = $this->getObfuscationKey(); // Get obfuscation key providing $mysoc->idprof1 and $registrationnumber. Note: On network trouble, an Exception is thrown to the caller
2043 } catch (Exception $e) {
2044 $errormsg = $e->getMessage();
2045 }
2046 if (!$errormsg && $obfuscationkey) {
2047 // Decode the encrypted parameter using the obfuscation key to get the HMAC key in memory.
2048 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key, $obfuscationkey);
2049 }
2050 }
2051
2052 if (preg_match('/^dolcrypt/', $hmac_encoded_secret_key)) {
2053 // 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).
2054 // We test this. Note: we force a migration of data to use the new storage if this is the case in method buildFinalSignatureHash().
2055 // Example with the old demo sample database:
2056 // dolcrypt:AES-256-CTR:46cb611f00c4cff8:XVfEh15vX/JOYmpiw2QPNamcTQwdbBZJTcXBh9rMpzYJOpVPZubIWcgA8wHMXA==
2057 // instance_unique_id=11f3c81e86fc9e3b3fd11d81c9a31bd0
2058 // HMAC key=BLOCKEDLOGHMACY3Ewx37RXbSd8gL9JV8p7Wqw7qvq2K2A
2059
2060 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key); // Decode the encrypted parameter using the obfuscation key from ping.dolibarr.org to decode HMAC key
2061 }
2062
2063 if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
2064 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 : ''));
2065 }
2066
2067 return $hmac_secret_key;
2068 }
2069
2077 public function getPreviousHash($withlock = 0, $beforeid = 0)
2078 {
2079 global $conf;
2080
2081 $previousid = 0;
2082 $previoussignature = '';
2083 $previousdatecreation = 0;
2084
2085 // Fast search of previous record by searching with beforeid - 1. This is very fast and will work 99% of time.
2086 if ($beforeid) {
2087 $sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
2088 $sql .= " WHERE entity = ".((int) $conf->entity);
2089 $sql .= " AND rowid = ".((int) $beforeid - 1);
2090 $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
2091
2092 $resql = $this->db->query($sql);
2093 if ($resql) {
2094 $obj = $this->db->fetch_object($resql);
2095 if ($obj) {
2096 $previousid = $obj->rowid;
2097 $previoussignature = $obj->signature;
2098 $tz = 'gmt';
2099 if (empty($obj->object_format) || $obj->object_format == 'V1') {
2100 $tz = 'tzserver';
2101 }
2102 $previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
2103 }
2104 } else {
2105 dol_print_error($this->db);
2106 exit;
2107 }
2108 }
2109
2110 if (empty($previoussignature)) {
2111 dol_syslog("getPreviousHash: We did not found previous record with fast mode so we search with a select max", LOG_DEBUG);
2112
2113 // Note: a select max rowid and then a select to get signature seems not faster due to filter on entity
2114 $sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
2115 if ($beforeid) {
2116 $sql .= $this->db->hintindex('entity_rowid', 1);
2117 }
2118 $sql .= " WHERE entity = ".((int) $conf->entity);
2119 if ($beforeid) {
2120 $sql .= " AND rowid < ".(int) $beforeid;
2121 }
2122 $sql .= " ORDER BY rowid DESC LIMIT 1";
2123 $sql .= ($withlock ? " FOR UPDATE " : "");
2124
2125 $resql = $this->db->query($sql);
2126 if ($resql) {
2127 $obj = $this->db->fetch_object($resql);
2128 if ($obj) {
2129 $previousid = $obj->rowid;
2130 $previoussignature = $obj->signature;
2131 $tz = 'gmt';
2132 if (empty($obj->object_format) || $obj->object_format == 'V1') {
2133 $tz = 'tzserver';
2134 }
2135 $previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
2136 }
2137 } else {
2138 dol_print_error($this->db); // can happen after a deadlock when too many requests do create into blocked log happen at the same time.
2139 http_response_code(503);
2140 exit;
2141 }
2142 }
2143
2144 if (empty($previoussignature)) {
2145 // First signature line (line 0)
2146 $previousid = 0;
2147 $previoussignature = $this->getOrInitFirstSignature();
2148 }
2149
2150 return array('previousid' => $previousid, 'previoushash' => $previoussignature, 'previousdatecreation' => $previousdatecreation);
2151 }
2152
2159 public function getNextRecord($rowidafter = 0)
2160 {
2161 global $conf;
2162
2163 $nextrecord = array('id' => 0, 'date' => 0, 'signature' => '');
2164
2165 // Get next record
2166 $sql = "SELECT rowid, date_creation, signature FROM ".MAIN_DB_PREFIX."blockedlog";
2167 $sql .= " WHERE entity = ".((int) $conf->entity);
2168 $sql .= " AND rowid > ".((int) $rowidafter);
2169 $sql .= " ORDER BY rowid ASC LIMIT 1";
2170
2171 $resql = $this->db->query($sql);
2172 $obj = $this->db->fetch_object($resql);
2173 if ($obj) {
2174 $nextrecord['id'] = $obj->rowid;
2175 $nextrecord['date'] = $this->db->jdate($obj->date_creation, 'gmt');
2176 $nextrecord['signature'] = $obj->signature;
2177 }
2178
2179 return $nextrecord;
2180 }
2181
2187 public function getLastRecord()
2188 {
2189 global $conf;
2190
2191 $lastrecord = array('id' => 0, 'date' => 0, 'signature' => '');
2192
2193 // Get last line
2194 $sql = "SELECT rowid, date_creation, signature FROM ".MAIN_DB_PREFIX."blockedlog";
2195 $sql .= " WHERE entity = ".((int) $conf->entity);
2196 $sql .= " ORDER BY rowid DESC LIMIT 1";
2197 $resql = $this->db->query($sql);
2198 $obj = $this->db->fetch_object($resql);
2199 if ($obj) {
2200 $lastrecord['id'] = $obj->rowid;
2201 $lastrecord['date'] = $this->db->jdate($obj->date_creation, 'gmt');
2202 $lastrecord['signature'] = $obj->signature;
2203 }
2204
2205 return $lastrecord;
2206 }
2207
2228 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 = '')
2229 {
2230 global $conf;
2231 //global $cachedlogs;
2232
2233 /* $cachedlogs allow fastest search */
2234 //if (empty($cachedlogs)) $cachedlogs = array();
2235
2236 if ($element == 'all') {
2237 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2238 WHERE entity = ".((int) $conf->entity);
2239 } elseif ($element == 'not_certified') {
2240 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2241 WHERE entity = ".((int) $conf->entity)." AND certified = 0";
2242 } elseif ($element == 'just_certified') {
2243 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2244 WHERE entity = ".((int) $conf->entity)." AND certified = 1";
2245 } else {
2246 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
2247 WHERE entity = ".((int) $conf->entity)." AND element = '".$this->db->escape($element)."'";
2248 }
2249
2250 if ($fk_object) {
2251 $sql .= natural_search("rowid", (string) $fk_object, 1);
2252 }
2253 if ($search_fk_user > 0) {
2254 $sql .= natural_search("fk_user", (string) $search_fk_user, 2);
2255 }
2256 if ($search_start > 0) {
2257 $sql .= " AND date_creation >= '".$this->db->idate($search_start, 'gmt')."'";
2258 }
2259 if ($search_end > 0) {
2260 $sql .= " AND date_creation <= '".$this->db->idate($search_end, 'gmt')."'";
2261 }
2262 if ($search_type_code) {
2263 $sql .= natural_search("type_code", (string) $search_type_code);
2264 }
2265 if ($search_ref != '') {
2266 $sql .= " AND (".natural_search("ref_object", $search_ref, 0, 1);
2267 $sql .= " OR ".natural_search("linktoref", $search_ref, 0, 1).")";
2268 }
2269 if ($search_amount != '') {
2270 $sql .= natural_search("amounts", $search_amount, 1);
2271 }
2272 if ($search_signature != '') {
2273 $sql .= natural_search("signature", $search_signature, 0);
2274 }
2275 if (is_array($search_code)) {
2276 if (!empty($search_code)) {
2277 if (in_array('PAYMENT_CUSTOMER', $search_code)) { // If we ask codes PAYMENT_CUSTOMER, it means both PAYMENT_CUSTOMER_CREATE and PAYMENT_CUSTOMER_DELETE
2278 $search_code[] = 'PAYMENT_CUSTOMER_CREATE';
2279 $search_code[] = 'PAYMENT_CUSTOMER_DELETE';
2280 }
2281
2282 $sql .= natural_search("action", implode(',', $search_code), 3);
2283 }
2284 } else {
2285 if ($search_code != '' && $search_code != '-1') {
2286 $sql .= natural_search("action", $search_code, 3);
2287 }
2288 }
2289 if (is_array($search_module_source)) {
2290 if (!empty($search_module_source)) {
2291 $sql .= " AND (";
2292 if (in_array('0', $search_module_source)) {
2293 $sql .= "module_source = ''";
2294 unset($search_module_source[0]);
2295 if (!empty($search_module_source)) {
2296 $sql .= " OR ";
2297 }
2298 }
2299 if (!empty($search_module_source)) {
2300 $sqlTmp = natural_search("module_source", implode(',', $search_module_source), 0, 1);
2301 $sqlTmp = str_replace('%backoffice%', '', $sqlTmp);
2302 $sql .= $sqlTmp;
2303 }
2304 $sql .= " OR module_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different origins (pos and not pos)
2305 $sql .= ")";
2306 }
2307 } else {
2308 if ($search_module_source != '' && $search_module_source != '-1') {
2309 $sql .= natural_search("module_source", $search_module_source, 3);
2310 }
2311 }
2312 if ($search_pos_source != '') {
2313 $sql .= " AND (";
2314 $sql .= natural_search("pos_source", $search_pos_source, 0, 1);
2315 $sql .= " OR pos_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different terminal (pos and not pos)
2316 $sql .= ")";
2317 }
2318
2319 $sql .= $this->db->order($sortfield, $sortorder);
2320 $sql .= $this->db->plimit($limit + 1); // We want more, because we will stop into loop later with error if we reach max
2321
2322 $res = $this->db->query($sql);
2323 if ($res) {
2324 $results = array();
2325
2326 $i = 0;
2327 while ($obj = $this->db->fetch_object($res)) {
2328 $i++;
2329 if ($i > $limit) {
2330 // Too many record, we will consume too much memory
2331 return -2;
2332 }
2333
2334 //if (!isset($cachedlogs[$obj->rowid]))
2335 //{
2336 $b = new BlockedLog($this->db);
2337 $result = $b->fetch($obj->rowid);
2338 //$b->loadTrackedEvents();
2339 //$cachedlogs[$obj->rowid] = $b;
2340 //}
2341
2342 //$results[] = $cachedlogs[$obj->rowid];
2343 if ($result < 0) {
2344 $this->error = $b->error;
2345 $this->errors = $b->errors;
2346 return -1;
2347 }
2348
2349 $results[] = $b;
2350 }
2351
2352 return $results;
2353 }
2354
2355 return -1;
2356 }
2357
2363 public function getOrInitFirstSignature()
2364 {
2365 global $db, $conf;
2366
2367 if (!getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT')) { // creation of a unique fingerprint
2368 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
2369 require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
2370 require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
2371
2372 $fingerprint = bin2hex(random_bytes(32)); // 64 char hex
2373
2374 dolibarr_set_const($db, 'BLOCKEDLOG_ENTITY_FINGERPRINT', $fingerprint, 'chaine', 0, 'Initial signature fingerprint', $conf->entity);
2375
2376 $conf->global->BLOCKEDLOG_ENTITY_FINGERPRINT = $fingerprint;
2377 }
2378
2379 return getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT');
2380 }
2381
2382
2389 public function alreadyUsed($ignoresystem = 0)
2390 {
2391 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2392 return isBlockedLogUsed($ignoresystem);
2393 }
2394
2395
2401 public function canBeEnabled()
2402 {
2403 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2404 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
2405
2406 $isqualified = isALNEQualifiedVersion(0, 1);
2407
2408 if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && !isHTTPS()) {
2409 return 'Error: The HTTPS must be enabled to allow the use of this module in France.';
2410 }
2411
2412 return '';
2413 }
2414
2415
2421 public function canBeDisabled()
2422 {
2423 global $mysoc;
2424
2425 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
2426
2427 $isqualified = isALNEQualifiedVersion();
2428
2429 $canbedisabled = 1;
2430 // For france, we can never disable the module (except in debug mode)
2431 if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && $mysoc->country_code == 'FR') {
2432 $canbedisabled = 0;
2433 }
2434
2435 return $canbedisabled;
2436 }
2437
2438
2444 public function countRecord()
2445 {
2446 $nb = 0;
2447
2448 $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."blockedlog";
2449 $resql = $this->db->query($sql);
2450 if ($resql) {
2451 $obj = $this->db->fetch_object($resql);
2452 $nb = $obj->nb;
2453 } else {
2454 dol_print_error($this->db);
2455 }
2456 $this->db->free($resql);
2457
2458 return $nb;
2459 }
2460}
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.
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
getDolEntity()
Return the current entity.
jsonOrUnserialize($stringtodecode, $assoc=true)
Decode an encoded string.
dolChmod($filepath, $newmask='')
Change mod of a file.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
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)
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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
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.