dolibarr/htdocs/blockedlog/class/blockedlog.class.php
2026-06-15 13:51:52 +02:00

2064 lines
76 KiB
PHP

<?php
/* Copyright (C) 2017 ATM Consulting <contact@atm-consulting.fr>
* Copyright (C) 2017-2020 Laurent Destailleur <eldy@destailleur.fr>
* Copyright (C) 2022 charlene benke <charlene@patas-monkey.com>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* See https://medium.com/@lhartikk/a-blockchain-in-200-lines-of-code-963cc1cc0e54
*/
include_once DOL_DOCUMENT_ROOT.'/blockedlog/versionmod.inc.php';
/**
* Class to manage Blocked Log
*/
class BlockedLog
{
/**
* @var DoliDB Database handler
*/
public $db;
/**
* Id of the log
* @var int
*/
public $id;
/**
* Entity
* @var int
*/
public $entity;
/**
* Picto
* @var string
*/
public $picto = 'blockedlog';
/**
* @var string Error message
*/
public $error = '';
/**
* @var string[] Error codes (or messages)
*/
public $errors = array();
/**
* Unique fingerprint of the log
* @var string
*/
public $signature = '';
/**
* @var float|string|null
*/
public $amounts = null;
/**
* @var float|string|null
*/
public $amounts_taxexcl = null;
/**
* trigger action
* @var string
*/
public $action = '';
/**
* @var string Module source
*/
public $module_source = '';
/**
* @var string Terminal nb
*/
public $pos_source = '';
/**
* @var string $linktype. Example 'paymentofinvoice'
*/
public $linktype = '';
/**
* @var string $linktoref
*/
public $linktoref = '';
/**
* Object element
* @var string
*/
public $element = '';
/**
* Object id
* @var int
*/
public $fk_object = 0;
/**
* Log certified by remote authority or not
* @var boolean
*/
public $certified = false;
/**
* Author
* @var int
*/
public $fk_user = 0;
/**
* @var int|string Note we store in database in gmt time, not in server timezone time
*/
public $date_creation;
/**
* @var int|string
*/
public $date_modification;
/**
* @var int
*/
public $date_object = 0;
/**
* @var string
*/
public $ref_object = '';
/**
* @var string
*/
public $type_code = '';
/**
* @var ?stdClass
*/
public $object_data = null;
/**
* @var string Version of application
*/
public $object_version = '';
/**
* @var string Version of format of line ('', 'V1', ...).
*/
public $object_format = '';
/**
* @var string
*/
public $user_fullname = '';
/**
* @var string
*/
public $debuginfo;
/**
* Array of tracked event codes. They are event codes that triggers a record in the unalterable log (and you can filter in list of events).
* @var array<string,string|mixed>
*/
public $trackedevents = array();
/**
* Array of controlled event codes. They are event the execute a control when they occurs. An error return will cancel the action.
* @var array<string,string|mixed>
*/
public $controlledevents = array();
/**
* Array of tracked modules (key => label). List of modules we can see in module_pos.
* @var array<int|string,string>
*/
public $trackedmodules = array();
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct(DoliDB $db)
{
$this->db = $db;
}
/**
* Load list of tracked and controlled events into $this->controlled, $this->trackedevents and $this->trackedmodules
*
* @return int<1,1> Always 1
*/
public function loadTrackedEvents()
{
global $langs;
$this->controlledevents = array();
$this->trackedevents = array();
$this->trackedmodules = array();
$sep = 0;
$this->controlledevents['BILL_MODIFY'] = array('id' => 'BILL_MODIFY', 'label' => 'logBILL_MODIFY');
$this->trackedmodules[0] = 'None';
if (isModEnabled('takepos')) {
$this->trackedmodules['takepos'] = 'TakePOS';
}
// Customer Invoice/Facture / Payment (For most VAT antifraud laws)
if (isModEnabled('invoice')) {
$sep++;
$this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Invoices").' | '.$langs->trans("Payments").'</span>', 'disabled' => 1);
$this->trackedevents['BILL_VALIDATE'] = array('id' => 'BILL_VALIDATE', 'label' => 'logBILL_VALIDATE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_VALIDATE'));
//$this->trackedevents['BILL_UPDATE'] = array('id' => 'BILL_VALIDATE', 'label' => 'logBILL_UPDATE', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_UPDATE'));
$this->trackedevents['BILL_SENTBYMAIL'] = array('id' => 'BILL_SENTBYMAIL', 'label' => 'logBILL_SENTBYMAIL', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('logBILL_SENTBYMAIL'));
$this->trackedevents['DOC_DOWNLOAD'] = array('id' => 'DOC_DOWNLOAD', 'label' => 'BlockedLogBillDownload', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('BlockedLogBillDownload'));
$this->trackedevents['DOC_PREVIEW'] = array('id' => 'DOC_PREVIEW', 'label' => 'BlockedLogBillPreview', 'labelhtml' => img_picto('', 'bill', 'class="pictofixedwidth").').$langs->trans('BlockedLogBillPreview'));
$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'));
$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'));
}
/* Supplier
// Supplier Invoice / Payment
if (isModEnabled("fournisseur")) {
$this->trackedevents['BILL_SUPPLIER_VALIDATE']='BlockedLogSupplierBillValidate';
$this->trackedevents['BILL_SUPPLIER_DELETE']='BlockedLogSupplierBillDelete';
$this->trackedevents['BILL_SUPPLIER_SENTBYMAIL']='BlockedLogSupplierBillSentByEmail'; // Trigger key does not exists, we want just into array to list it as done
$this->trackedevents['SUPPLIER_DOC_DOWNLOAD']='BlockedLogSupplierBillDownload'; // Trigger key does not exists, we want just into array to list it as done
$this->trackedevents['SUPPLIER_DOC_PREVIEW']='BlockedLogSupplierBillPreview'; // Trigger key does not exists, we want just into array to list it as done
$this->trackedevents['PAYMENT_SUPPLIER_CREATE']='BlockedLogSupplierBillPaymentCreate';
$this->trackedevents['PAYMENT_SUPPLIER_DELETE']='BlockedLogsupplierBillPaymentCreate';
}
*/
// Donation
if (isModEnabled('don') && getDolGlobalString('BLOCKEDLOG_ENABLE_DONATION')) { // For countries that need unalterable logs for donations
if (!empty($this->trackedevents)) {
$sep++;
$this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Donations").' | '.$langs->trans("Payments").'</span>', 'disabled' => 1);
}
$this->trackedevents['DON_VALIDATE'] = array('id' => 'DON_VALIDATE', 'label' => 'logDON_VALIDATE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDON_VALIDATE'));
$this->trackedevents['DON_DELETE'] = array('id' => 'DON_DELETE', 'label' => 'logDON_DELETE', 'labelhtml' => img_picto('', 'donation', 'class="pictofixedwidth").').$langs->trans('logDON_DELETE'));
//$this->trackedevents['DON_SENTBYMAIL'] = array('id' => 'BILL_VALIDATE', img_picto('', 'don', 'class="pictofixedwidth").').$langs->trans('labelhtml' => 'logDON_SENTBYMAIL');
$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'));
$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'));
}
/*
// Salary
if (isModEnabled('salary')) {
$this->trackedevents['PAYMENT_SALARY_CREATE'] = 'BlockedLogSalaryPaymentCreate';
$this->trackedevents['PAYMENT_SALARY_MODIFY'] = 'BlockedLogSalaryPaymentCreate';
$this->trackedevents['PAYMENT_SALARY_DELETE'] = 'BlockedLogSalaryPaymentCreate';
}
*/
// Members
if (isModEnabled('member') && getDolGlobalString('BLOCKEDLOG_ENABLE_MEMBER')) { // For countries that need unalterable logs for membership management
if (!empty($this->trackedevents)) {
$sep++;
$this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("MenuMembers").'</span>', 'disabled' => 1);
}
$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'));
$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'));
$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'));
}
// Bank
/*
if (isModEnabled("bank")) {
if (!empty($this->trackedevents)) {
$sep++;
$this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("VariousPayment").'</span>', 'disabled' => 1);
}
$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'));
$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'));
$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'));
}
*/
// Cash register closing
// $conf->global->BANK_ENABLE_POS_CASHCONTROL must be set to 1 by all external POS modules
$moduleposenabled = (isModEnabled('cashdesk') || isModEnabled('takepos') || getDolGlobalString('BANK_ENABLE_POS_CASHCONTROL'));
if ($moduleposenabled) {
if (!empty($this->trackedevents)) {
$sep++;
$this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("CashControl").'</span>', 'disabled' => 1);
}
if (getDolGlobalString('BLOCKEDLOG_ADD_OLD_CASHCONTROL_VALIDATE')) {
$this->trackedevents['CASHCONTROL_VALIDATE'] = array('id' => 'CASHCONTROL_VALIDATE', 'label' => 'logCASHCONTROL_VALIDATE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_VALIDATE'));
}
$this->trackedevents['CASHCONTROL_CLOSE'] = array('id' => 'CASHCONTROL_CLOSE', 'label' => 'logCASHCONTROL_CLOSE', 'labelhtml' => img_picto('', 'pos', 'class="pictofixedwidth").').$langs->trans('logCASHCONTROL_CLOSE'));
}
// Add more action to track from a conf variable. For the case we want to track other actions into the unalterable log.
// For example: STOCK_MOVEMENT, ...
if (getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED')) {
if (!empty($this->trackedevents)) {
$sep++;
$this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----------</span>', 'disabled' => 1);
}
$tmparrayofmoresupportedevents = explode(',', getDolGlobalString('BLOCKEDLOG_ADD_ACTIONS_SUPPORTED'));
foreach ($tmparrayofmoresupportedevents as $val) {
$this->trackedevents[$val] = array('id' => $val, 'label' => 'log'.$val, 'labelhtml' => img_picto('', 'generic', 'class="pictofixedwidth").').$langs->trans('log'.$val));
}
}
if (!empty($this->trackedevents)) {
$sep++;
$this->trackedevents['separator_'.$sep] = array('id' => 'separator_'.$sep, 'label' => '----------', 'labelhtml' => '<span class="opacitymedium">----- '.$langs->trans("Other").'</span>', 'disabled' => 1);
}
$this->trackedevents['BLOCKEDLOG_EXPORT'] = array('id' => 'BLOCKEDLOG_EXPORT', 'label' => 'logBLOCKEDLOG_EXPORT', 'labelhtml' => img_picto('', $this->picto, 'class="pictofixedwidth").').$langs->trans('logBLOCKEDLOG_EXPORT'));
return 1;
}
/**
* Try to retrieve source object (it it still exists).
*
* @return string URL string of source object
*/
public function getObjectLink()
{
global $langs;
if ($this->element === 'facture') {
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
$object = new Facture($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
}
if ($this->element === 'invoice_supplier') {
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
$object = new FactureFournisseur($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'payment') {
require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
$object = new Paiement($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'payment_supplier') {
require_once DOL_DOCUMENT_ROOT.'/fourn/class/paiementfourn.class.php';
$object = new PaiementFourn($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'payment_donation') {
require_once DOL_DOCUMENT_ROOT.'/don/class/paymentdonation.class.php';
$object = new PaymentDonation($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'payment_various') {
require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
$object = new PaymentVarious($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'don' || $this->element === 'donation') {
require_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php';
$object = new Don($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'subscription') {
require_once DOL_DOCUMENT_ROOT.'/adherents/class/subscription.class.php';
$object = new Subscription($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'cashcontrol') {
require_once DOL_DOCUMENT_ROOT.'/compta/cashcontrol/class/cashcontrol.class.php';
$object = new CashControl($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'stockmouvement') {
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
$object = new MouvementStock($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->element === 'project') {
require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
$object = new Project($this->db);
if ($object->fetch($this->fk_object) > 0) {
return $object->getNomUrl(1);
} else {
$this->error = (string) (((int) $this->error) + 1);
}
} elseif ($this->action == 'BLOCKEDLOG_EXPORT') {
return '<i class="opacitymedium">'.$langs->trans("logBLOCKEDLOG_EXPORT").'</i>';
} elseif ($this->action == 'MODULE_SET') {
return '<i class="opacitymedium">'.$langs->trans("BlockedLogEnabled").'</i>';
} elseif ($this->action == 'MODULE_RESET') {
if ($this->signature == '0000000000') {
return '<i class="opacitymedium">'.$langs->trans("BlockedLogDisabled").'</i>';
} else {
return '<i class="opacitymedium">'.$langs->trans("BlockedLogDisabledBis").'</i>';
}
}
return '<i class="opacitymedium">'.$langs->trans('ImpossibleToReloadObject', $this->element, $this->fk_object).'</i>';
}
/**
* Try to retrieve user author
*
* @return string
*/
public function getUser()
{
global $langs, $cachedUser;
if (empty($cachedUser)) {
$cachedUser = array();
}
if (empty($cachedUser[$this->fk_user])) {
$u = new User($this->db);
if ($u->fetch($this->fk_user) > 0) {
$cachedUser[$this->fk_user] = $u;
}
}
if (!empty($cachedUser[$this->fk_user])) {
return $cachedUser[$this->fk_user]->getNomUrl(1);
}
return $langs->trans('ImpossibleToRetrieveUser', $this->fk_user);
}
/**
* Populate properties of an unalterable log entry from object data.
* This populates ->object_data but also other fields like ->action, ->module_source, ->amounts_taxexcl, ->amounts and ->linktoref and ->linktype
* It also populates some debug info like ->element and ->fk_object
*
* @param CommonObject|stdClass $object Object to store
* @param string $action Action code ('BILL_VALIDATE', 'BILL_SENTBYMAIL', ...)
* @param float|int $amounts amounts (incl tax)
* @param ?User $fuser User object (forced)
* @param float|int|null $amounts_taxexcl amounts (excl tax or null if not relevant)
* @return int<-1,-1>|int<1,1> Return >0 if OK, <0 if KO
*/
public function setObjectData(&$object, $action, $amounts, $fuser = null, $amounts_taxexcl = null)
{
global $langs, $user, $mysoc;
if (is_object($fuser)) {
$user = $fuser;
}
// Init object_data for JSON data
$this->object_data = new stdClass();
// Generic fields
// entity
$this->entity = $object->entity ?? getDolEntity();
// action
$this->action = $action;
// amount
$this->amounts_taxexcl = $amounts_taxexcl;
$this->amounts = $amounts;
if ($action === 'MEMBER_SUBSCRIPTION_DELETE' || $action === 'PAYMENT_CUSTOMER_DELETE' || $action === 'PAYMENT_SUPPLIER_DELETE' || $action === 'DONATION_PAYMENT_DELETE') {
$this->amounts_taxexcl = - $this->amounts_taxexcl;
$this->amounts = - $this->amounts;
}
// date
if ($object->element == 'payment' || $object->element == 'payment_supplier') {
'@phan-var-force Paiement|PaiementFourn $object';
$this->date_object = empty($object->datepaye) ? $object->date : $object->datepaye;
} elseif ($object->element == 'payment_salary') {
'@phan-var-force PaymentSalary $object';
$this->date_object = $object->datev;
} elseif ($object->element == 'payment_donation' || $object->element == 'payment_various') {
'@phan-var-force PaymentDonation $object';
$this->date_object = empty($object->datepaid) ? $object->datep : $object->datepaid;
} elseif ($object->element == 'subscription') {
'@phan-var-force Subscription $object';
$this->date_object = $object->dateh;
} elseif ($object->element == 'cashcontrol') {
/** var CashControl $object */
'@phan-var-force CashControl $object';
$this->date_object = $object->date_creation;
$this->module_source = $object->posmodule;
$this->pos_source = $object->posnumber;
} elseif (property_exists($object, 'date')) {
// Generic case
$this->date_object = $object->date; // @phan-suppress-current-line PhanUndeclaredProperty
} elseif (property_exists($object, 'datem')) {
// Generic case (second chance, for example for stock movement)
$this->date_object = $object->datem; // @phan-suppress-current-line PhanUndeclaredProperty
}
// In case of credit note, we add link to source invoice to have more tracking info when doing tracking later
if ($object->element == 'invoice_supplier') {
'@phan-var-force FactureFournisseur $object';
if ($object->type == FactureFournisseur::TYPE_CREDIT_NOTE) {
$invoice = new FactureFournisseur($this->db);
$invoice->fetch($object->fk_facture_source);
if ($invoice->id > 0) {
$this->linktype = 'credit_note_of';
$this->linktoref = $invoice->ref;
}
//$this->module_source = (string) $invoice->module_source;
//$this->pos_source = (string) $invoice->pos_source;
}
}
if ($object->element == 'facture') {
'@phan-var-force Facture $object';
if ($object->type == Facture::TYPE_CREDIT_NOTE) {
$invoice = new Facture($this->db);
$invoice->fetch($object->fk_facture_source);
if ($invoice->id > 0) {
$this->linktype = 'credit_note_of';
$this->linktoref = $invoice->ref;
$this->object_data->link = $this->linktype.' '.$this->linktoref;
}
$this->module_source = (string) $invoice->module_source;
$this->pos_source = (string) $invoice->pos_source;
}
}
// ref object
$this->ref_object = ((!empty($object->newref)) ? $object->newref : $object->ref); // newref is set when validating a draft, ref is set in other cases
// type of object
$this->element = $object->element;
// id of object
$this->fk_object = $object->id;
// Add thirdparty info if not yet done
if (empty($object->thirdparty) && method_exists($object, 'fetch_thirdparty')) {
$object->fetch_thirdparty();
}
// Add fields to exclude (this has become useless because we now use a list fields to keep later).
$arrayoffieldstoexclude = array(
'table_element', 'fields',
'ref_previous', 'ref_next',
'origin', 'origin_id',
'oldcopy', 'picto', 'error', 'errors',
'model_pdf', 'modelpdf', 'last_main_doc', 'civility_id', 'contact', 'contact_id',
'table_element_line', 'ismultientitymanaged', 'isextrafieldmanaged',
'array_languages',
'childtables',
'contact_ids',
'context',
'element',
'labelStatus',
'labelStatusShort',
'linkedObjectsIds',
'linkedObjects',
'fk_delivery_address',
'projet', // There is already ->fk_project
'restrictiononfksoc',
'specimen',
);
// Add more fields to exclude depending on object type
if ($this->element == 'cashcontrol') {
$arrayoffieldstoexclude = array_merge($arrayoffieldstoexclude, array(
'name', 'lastname', 'firstname', 'region', 'region_id', 'region_code', 'state', 'state_id', 'state_code', 'country', 'country_id', 'country_code',
'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
'barcode_type', 'barcode_type_code', 'barcode_type_label', 'barcode_type_coder', 'mode_reglement_id', 'cond_reglement_id', 'mode_reglement', 'cond_reglement', 'shipping_method_id',
'extraparams', 'fk_incoterms', 'fk_user_creat', 'fk_user_valid', 'label_incoterms', 'location_incoterms', 'lines', 'nb', 'tms', 'comments', 'array_options', 'warnings',
'opening', 'status', 'date_valid'
)
);
}
// For customer payment and supplier payment, the thirdparty can be added in payment detail
$addthirdpartyatpaymentlevel = 0;
if (!empty($object->thirdparty) && !in_array($this->element, array('payment', 'payment_supplier'))) {
$addthirdpartyatpaymentlevel = 1;
}
if (!empty($object->thirdparty) && !$addthirdpartyatpaymentlevel) {
$this->object_data->thirdparty = new stdClass();
foreach ($object->thirdparty as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
// List of fields qualified
if (!in_array($key, array(
'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax2_assuj', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
))) {
continue; // Discard if not into this dedicated list
}
$valuequalifiedforstorage = false;
if (!is_object($value)) {
if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
$valuequalifiedforstorage = true; // We accept '' value for some fields
$value = (string) $value;
}
if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
$valuequalifiedforstorage = true; // We accept zero value for amounts
}
if (!is_null($value) && (string) $value !== '') {
$valuequalifiedforstorage = true;
}
}
if ($valuequalifiedforstorage) {
$this->object_data->thirdparty->$key = $value;
}
}
}
// Add my company info (Only for customer invoice and payment)
if (!empty($mysoc) && in_array($object->element, array('facture', 'paiement'))) {
$this->object_data->mycompany = new stdClass();
foreach ($mysoc as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
// List of fields qualified to keep
if (!in_array($key, array(
'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
'tva_assuj', 'tva_intra', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
))) {
continue; // Discard if not into this dedicated list
}
$valuequalifiedforstorage = false;
if (!is_object($value)) {
if (empty($value) && in_array($key, array('country_code', 'idprof1', 'idprof2', 'tva_intra'))) {
$valuequalifiedforstorage = true; // We accept '' value for some fields
$value = (string) $value;
}
if (!is_null($value) && empty($value) && in_array($key, array('tva_assuj', 'localtax1_assuj', 'localtax2_assuj'))) {
$valuequalifiedforstorage = true; // We accept zero value for amounts
}
if (!is_null($value) && (string) $value !== '') {
$valuequalifiedforstorage = true;
}
}
if ($valuequalifiedforstorage) {
$this->object_data->mycompany->$key = $value;
}
}
}
// Add user info
if (!empty($user)) {
$this->fk_user = $user->id;
$this->user_fullname = $user->getFullName($langs);
}
// Field specific to object
if ($this->element == 'facture') {
'@phan-var-force Facture $object';
$this->module_source = (string) $object->module_source;
$this->pos_source = (string) $object->pos_source;
foreach ($object as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
// List of fields qualified
if (!in_array($key, array(
'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'datev', 'type',
//'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx', There is no rate at full doc level
'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2',
'revenuestamp', 'datepointoftax', 'note_public',
'lines',
'module_source', 'pos_source', 'pos_print_counter', 'email_sent_counter'
))) {
continue; // Discarded if not into the dedicated list
}
if ($key == 'lines') {
$lineid = 0;
foreach ($value as $tmpline) { // $tmpline is object FactureLine
$lineid++;
foreach ($tmpline as $keyline => $valueline) {
if (!in_array($keyline, array(
'ref', 'product_type', 'product_label',
'qty', 'subprice',
'vat_src_code', 'tva_tx', 'localtax1_tx', 'localtax2_tx',
'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
'multicurrency_code', 'multicurrency_total_ht', 'multicurrency_total_tva', 'multicurrency_total_ttc',
'info_bits', 'special_code', 'remise_percent'
))) {
continue; // Discard if not into a dedicated list
}
if (empty($this->object_data->invoiceline[$lineid]) || !is_object($this->object_data->invoiceline[$lineid])) { // To avoid warning
$this->object_data->invoiceline[$lineid] = new stdClass();
}
$valuequalifiedforstorage = false;
if (!is_object($valueline)) {
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'))) {
$valuequalifiedforstorage = true; // We accept zero value for amounts
}
if (!is_null($valueline) && (string) $valueline !== '') {
$valuequalifiedforstorage = true;
}
}
if ($keyline == 'product_label' && empty($valueline)) {
$valueline = dol_trunc(dolGetFirstLineOfText($tmpline->desc)); // Fallback on description if label is empty
$valuequalifiedforstorage = true;
}
if ($valuequalifiedforstorage) {
$this->object_data->invoiceline[$lineid]->$keyline = $valueline;
}
}
}
} else {
$valuequalifiedforstorage = false;
if (!is_object($value)) {
if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
$valuequalifiedforstorage = true; // We accept '' value for some fields
$value = (string) $value;
}
if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
$valuequalifiedforstorage = true; // We accept zero value for amounts
}
if (!is_null($value) && (string) $value !== '') {
$valuequalifiedforstorage = true;
}
}
if ($valuequalifiedforstorage) {
$this->object_data->$key = $value;
}
}
}
if (!empty($object->newref)) {
$this->object_data->ref = $object->newref;
}
// Add data for action emails
if ($action == 'BILL_SENTBYMAIL') {
$this->object_data->action_email_sent = array(
"email_from" => $object->context['email_from'],
"email_to" => $object->context['email_to'],
"email_msgid" => $object->context['email_msgid']
);
}
} elseif ($this->element == 'invoice_supplier') {
'@phan-var-force FactureFournisseur $object';
foreach ($object as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
// List of fields qualified
if (!in_array($key, array(
'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public'
))) {
continue; // Discard if not into a dedicated list
}
$valuequalifiedforstorage = false;
if (!is_object($value)) {
if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
$valuequalifiedforstorage = true; // We accept '' value for some fields
$value = (string) $value;
}
if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
$valuequalifiedforstorage = true; // We accept zero value for amounts
}
if (!is_null($value) && (string) $value !== '') {
$valuequalifiedforstorage = true;
}
}
if ($valuequalifiedforstorage) {
$this->object_data->$key = $value;
}
}
if (!empty($object->newref)) {
$this->object_data->ref = $object->newref;
}
} elseif ($this->element == 'payment' || $this->element == 'payment_supplier' || $this->element == 'payment_donation' || $this->element == 'payment_various') {
'@phan-var-force Paiement|PaiementFourn|PaymentDonation|PaymentVarious $object';
$datepayment = $object->datepaye ? $object->datepaye : ($object->datepaid ? $object->datepaid : $object->datep);
$paymenttypeid = $object->paiementid ? $object->paiementid : ($object->paymenttype ? $object->paymenttype : $object->type_payment);
$this->object_data->ref = $object->ref;
$this->object_data->date = $datepayment;
$this->object_data->type_code = dol_getIdFromCode($this->db, $paymenttypeid, 'c_paiement', 'id', 'code');
if (!empty($object->num_payment)) {
$this->object_data->payment_num = $object->num_payment;
}
if (!empty($object->note_private)) {
$this->object_data->note_private = $object->note_private;
}
//$this->object_data->fk_account = $object->fk_account;
//var_dump($this->object_data);exit;
$totalamount = 0;
$this->type_code = $this->object_data->type_code;
$this->linktype = $this->element;
$this->linktoref = '';
// If payment and $object->amounts is empty (for example when we delete), we complete the information
if ($this->element == 'payment' && empty($object->amounts) && $object instanceOf Paiement) {
$amountsarray = $object->getAmountsArray();
$object->amounts = $amountsarray;
// Invert the sign of amount into the array ->amounts if it is a deletion
if ($action == 'PAYMENT_CUSTOMER_DELETE') {
foreach ($object->amounts as $amountkey => $amountval) {
$object->amounts[$amountkey] = - $amountval;
}
}
}
// Loop on each invoice payment amount (the payment_part)
if (is_array($object->amounts) && !empty($object->amounts)) {
// Loop on each invoice the payment is part of to set the linktoref and the module_source and pos_source
$originofpayment = null;
$terminalofpayment = null;
$paymentpartnumber = 0;
foreach ($object->amounts as $objid => $amount) {
if (empty($amount)) {
continue;
}
$totalamount += $amount;
$tmpobject = null;
if ($this->element == 'payment_supplier') {
include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
$tmpobject = new FactureFournisseur($this->db);
} elseif ($this->element == 'payment') {
include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
$tmpobject = new Facture($this->db);
} elseif ($this->element == 'payment_donation') {
include_once DOL_DOCUMENT_ROOT.'/don/class/don.class.php';
$tmpobject = new Don($this->db);
} elseif ($this->element == 'payment_various') {
include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/paymentvarious.class.php';
$tmpobject = new PaymentVarious($this->db);
}
if (!is_object($tmpobject)) {
continue;
}
$result = $tmpobject->fetch($objid);
if ($result <= 0) {
$this->error = $tmpobject->error;
$this->errors = $tmpobject->errors;
dol_syslog("Failed to fetch object with id ".$objid, LOG_ERR);
return -1;
}
$this->linktoref .= ($this->linktoref ? ',' : '').$tmpobject->ref;
// Set the ->module_source of payment from origin object if relevant
if (property_exists($tmpobject, 'module_source')) {
if (is_null($originofpayment)) {
$originofpayment = (string) $tmpobject->module_source;
} elseif ($originofpayment != $tmpobject->module_source) {
$originofpayment = 'mix'; // the payment is on several invoices with different origins of module
} else {
$originofpayment = (string) $tmpobject->module_source;
}
}
// Set the ->pos_source of payment from origin object if relevant
if (property_exists($tmpobject, 'pos_source')) {
if (is_null($terminalofpayment)) {
$terminalofpayment = (string) $tmpobject->pos_source;
} elseif ($terminalofpayment != $tmpobject->pos_source) {
$terminalofpayment = 'mix'; // the payment is on several invoices with same origin of module but different terminals
} else {
$terminalofpayment = (string) $tmpobject->pos_source;
}
}
$paymentpart = new stdClass();
$paymentpart->amount = $amount;
// If we want to add thirdparty on each payment level
// (seems not necessary as we have one thirdparty per payment on invoice level)
if ($addthirdpartyatpaymentlevel) {
$result = $tmpobject->fetch_thirdparty();
if ($result == 0) {
$this->error = 'Failed to fetch thirdparty for object with id '.$tmpobject->id;
$this->errors[] = $this->error;
dol_syslog("Failed to fetch thirdparty for object with id ".$tmpobject->id, LOG_ERR);
return -1;
} elseif ($result < 0) {
$this->error = $tmpobject->error;
$this->errors = $tmpobject->errors;
return -1;
}
$paymentpart->thirdparty = new stdClass();
foreach ($tmpobject->thirdparty as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
// List of thirdparty fields qualified
if (!in_array($key, array(
'name', 'name_alias', 'ref_ext', 'address', 'zip', 'town', 'state_code', 'country_code', 'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6', 'phone', 'fax', 'email', 'barcode',
'tva_intra', 'tva_assuj', 'localtax1_assuj', 'localtax1_value', 'localtax2_assuj', 'localtax2_value', 'managers', 'capital', 'typent_code', 'forme_juridique_code', 'code_client', 'code_fournisseur'
))) {
continue; // Discard if not into a dedicated list
}
if (!is_object($value) && !is_null($value) && $value !== '') {
$paymentpart->thirdparty->$key = $value;
}
}
}
// Init object to avoid warnings
if ($this->element == 'payment_donation') {
$paymentpart->donation = new stdClass();
} elseif ($this->element == 'payment_various') {
$paymentpart->various = new stdClass();
} else {
$paymentpart->invoice = new stdClass();
}
if ($this->element != 'payment_various') {
foreach ($tmpobject as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
// List of fields qualified
if (!in_array($key, array(
'ref', 'ref_client', 'ref_supplier', 'date', 'datef', 'type', 'total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'revenuestamp', 'datepointoftax', 'note_public',
'pos_source', 'module_source', 'pos_print_counter', 'email_sent_counter'
))) {
continue; // Discard if not into a dedicated list
}
$valuequalifiedforstorage = false;
if (!is_object($value)) {
if (empty($value) && in_array($key, array('pos_source', 'module_source'))) {
$valuequalifiedforstorage = true; // We accept '' value for some fields
$value = (string) $value;
}
if (!is_null($value) && empty($value) && in_array($key, array('total_ht', 'total_tva', 'total_ttc', 'localtax1', 'localtax2', 'pos_print_counter', 'email_sent_counter'))) {
$valuequalifiedforstorage = true; // We accept zero value for amounts
}
if (!is_null($value) && (string) $value !== '') {
$valuequalifiedforstorage = true;
}
}
if ($valuequalifiedforstorage) {
if ($this->element == 'payment_donation') {
$paymentpart->donation->$key = $value;
} elseif ($this->element == 'payment_various') {
$paymentpart->various->$key = $value;
} else {
$paymentpart->invoice->$key = $value;
}
}
}
$paymentpartnumber++; // first payment will be 1
$this->object_data->payment_part[$paymentpartnumber] = $paymentpart;
}
}
$this->module_source = (string) $originofpayment;
$this->pos_source = (string) $terminalofpayment;
} elseif (!empty($object->amount)) {
$totalamount = $object->amount;
}
$this->object_data->amount = $totalamount;
if (!empty($object->newref)) {
$this->object_data->ref = $object->newref;
}
} elseif ($this->element == 'payment_salary') {
'@phan-var-force PaymentSalary $object';
$this->object_data->amounts = array($object->amount);
if (!empty($object->newref)) {
$this->object_data->ref = $object->newref;
}
} elseif ($this->element == 'subscription') {
'@phan-var-force Subscription $object';
foreach ($object as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
if (!in_array($key, array(
'id', 'datec', 'dateh', 'datef', 'fk_adherent', 'amount', 'import_key', 'statut', 'note'
))) {
continue; // Discard if not into a dedicated list
}
if (!is_object($value) && !is_null($value) && $value !== '') {
$this->object_data->$key = $value;
}
}
if (!empty($object->newref)) {
$this->object_data->ref = $object->newref;
}
} elseif ($this->element == 'stockmouvement') {
'@phan-var-force StockTransfer $object';
foreach ($object as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
if (!is_object($value) && !is_null($value) && $value !== '') {
$this->object_data->$key = $value;
}
}
} else {
if ($object->element == 'cashcontrol') {
$this->module_source = (string) $object->posmodule; // Module
$this->pos_source = (string) $object->posnumber; // Terminal
}
// Generic case
foreach ($object as $key => $value) {
if (in_array($key, $arrayoffieldstoexclude)) {
continue; // Discard some properties
}
if (!is_object($value) && !is_null($value) && $value !== '') {
$this->object_data->$key = $value;
}
}
if ($object->element == 'cashcontrol') {
$period = $object->year_close;
$period .= ($object->month_close ? "-".sprintf("%02d", $object->month_close) : "");
$period .= ($object->day_close ? "-".sprintf("%02d", $object->day_close) : "");
$this->object_data->period = $period;
}
if (!empty($object->newref)) {
$this->object_data->ref = $object->newref;
}
}
// A trick to be sure all the object_data is an associative array
// json_encode and json_decode are not able to manage mixed object (with array/object, only full arrays or full objects)
$this->object_data = json_decode(json_encode($this->object_data, JSON_FORCE_OBJECT), false);
return 1;
}
/**
* Get object from database
*
* @param int $id Id of object to load
* @return int<-1,1> >0 if OK, <0 if KO, 0 if not found
*/
public function fetch($id)
{
global $langs;
if (empty($id)) {
$this->error = 'BadParameter';
return -1;
}
$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,";
$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";
$sql .= " FROM ".MAIN_DB_PREFIX."blockedlog as b";
if ($id) {
$sql .= " WHERE b.rowid = ".((int) $id);
}
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
if ($obj) {
$this->id = $obj->rowid;
$this->entity = $obj->entity;
// Must be at top
$tz = 'gmt';
if (empty($obj->object_format) || $obj->object_format == 'V1') {
$tz = 'tzserver';
}
$this->date_creation = $this->db->jdate($obj->date_creation, $tz); // jdate(date_creation)is UTC
// @phan-suppress-next-line PhanPluginSuspiciousParamOrder
$this->date_modification = $this->db->jdate($obj->tms, $tz); // jdate(tms) is UTC
$this->action = $obj->action;
$this->module_source = $obj->module_source;
$this->pos_source = $obj->pos_source;
$this->amounts_taxexcl = (is_null($obj->amounts_taxexcl) ? null : (float) $obj->amounts_taxexcl);
$this->amounts = (float) $obj->amounts;
$this->fk_object = $obj->fk_object;
$this->date_object = $this->db->jdate($obj->date_object, $tz); // jdate(date_object) is UTC
//var_dump($obj->date_object, dol_print_date($this->date_object, 'dayhour' , $tz));
//exit;
$this->ref_object = $obj->ref_object;
$this->type_code = $obj->type_code;
$this->linktoref = $obj->linktoref;
$this->linktype = $obj->linktype;
$this->fk_user = $obj->fk_user;
$this->user_fullname = $obj->user_fullname;
$this->object_data = $this->dolDecodeBlockedData($obj->object_data);
$this->object_version = $obj->object_version;
$this->object_format = $obj->object_format;
$this->element = $obj->element;
$this->signature = $obj->signature;
$this->certified = ($obj->certified == 1);
return 1;
} else {
$langs->load("errors");
$this->error = $langs->trans("ErrorRecordNotFound");
return 0;
}
} else {
$this->error = $this->db->error();
return -1;
}
}
/**
* Encode data
*
* @param ?stdClass $data Data to serialize
* @param int<0,1> $mode 0=serialize, 1=json_encode
* @return string Value serialized, an object (stdClass).
*/
public function dolEncodeBlockedData($data, $mode = 1)
{
$aaa = json_encode($data);
return $aaa;
}
/**
* Decode data
*
* @param string $data Data to unserialize
* @param int $mode 0=unserialize, 1=json_decode
* @return Object Value unserialized, an object (stdClass)
*/
public function dolDecodeBlockedData($data, $mode = 0)
{
$aaa = null;
try {
$aaa = (object) jsonOrUnserialize($data, false);
} catch (Exception $e) {
// print $e->getErrs);
}
return $aaa;
}
/**
* Set block certified by an external authority
*
* @return boolean
*/
public function setCertified()
{
$res = $this->db->query("UPDATE ".MAIN_DB_PREFIX."blockedlog SET certified = 1 WHERE rowid = ".((int) $this->id));
if (!$res) {
return false;
}
return true;
}
/**
* Create blocked log in database.
*
* @param User $user Object user that create
* @param string $forcesignature Force signature (for example '0000000000' when we disabled the module, to force a non valid record, for test purpose for example)
* @return int<-3,-1>|int<1,1> Return integer <0 if KO, >0 if OK
*/
public function create($user, $forcesignature = '')
{
global $conf, $langs;
$langs->load('blockedlog');
// Clean data
$this->amounts = (float) $this->amounts;
dol_syslog(get_class($this).'::create action='.$this->action.' fk_user='.$this->fk_user.' user_fullname='.$this->user_fullname, LOG_DEBUG);
// Check parameters/properties
if (!isset($this->amounts)) { // amount can be 0 for some events (like when module is disabled)
$langs->load("errors");
$this->error = $langs->trans("ErrorBlockLogNeedAmountsValue");
dol_syslog($this->error, LOG_WARNING);
return -1;
}
if (empty($this->element)) {
$langs->load("errors");
$this->error = $langs->trans("ErrorBlockLogNeedElement");
dol_syslog($this->error, LOG_WARNING);
return -2;
}
if (empty($this->object_data)) {
$langs->load("errors");
$this->error = $langs->trans("ErrorBlockLogNeedObject");
dol_syslog($this->error, LOG_WARNING);
return -2;
}
if (empty($this->action)) {
$langs->load("errors");
$this->error = $langs->trans("ErrorBadParameterWhenCallingCreateOfBlockedLog");
dol_syslog($this->error, LOG_WARNING);
return -3;
}
if (empty($this->fk_user)) {
$this->user_fullname = '(Anonymous)';
}
include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
$this->db->begin();
$this->date_creation = dol_now();
$this->object_version = DOL_VERSION;
// The object_format define the formatting rules into buildKeyForSignature and buildFirstPartOfKeyForSignature and buildFinalSignatureHash
$this->object_format = 'V2'; // TODO Switch to V2 for every version
if (defined('CERTIF_LNE') && in_array((int) constant('CERTIF_LNE'), array(1, 2))) {
$this->object_format = 'V2';
}
$tz = 'gmt';
// if (empty($this->object_format) || $this->object_format == 'V1') {
// $tz = 'tzserver';
// }
$previoushash = '';
$previousid = 0;
$previousdatecreation = 0;
try {
$tmparray = $this->getPreviousHash(1, 0); // This get last record and lock database until insert is done and transaction closed
$previoushash = $tmparray['previoushash'];
$previousid = $tmparray['previousid'];
$previousdatecreation = $tmparray['previousdatecreation'];
$concatenateddata = $this->buildKeyForSignature(); // All the information for the hash (meta data + data saved)
// The new hash
$this->signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata); // Build the hmac signature
// For debug info (we can clean this field later)
if (getDolGlobalString('BLOCKEDLOG_ADD_DEBUG_INFO')) {
$this->debuginfo = 'previoushash='.$previoushash.' concatenateddatafirstpart='.$this->buildFirstPartOfKeyForSignature().' => signature='.$this->signature; // Not used
}
} catch (Exception $e) {
$this->error = $e->getMessage();
dol_syslog($this->error, LOG_ERR);
$this->db->rollback();
return -1;
}
if ($forcesignature) {
$this->signature = $forcesignature;
}
//var_dump($previoushash, $concatenateddata, $this->signature);
$sql = "INSERT INTO ".MAIN_DB_PREFIX."blockedlog (";
$sql .= " date_creation,";
$sql .= " action,";
$sql .= " module_source,";
$sql .= " pos_source,";
$sql .= " amounts_taxexcl,";
$sql .= " amounts,";
$sql .= " signature,";
$sql .= " element,";
$sql .= " fk_object,";
$sql .= " date_object,";
$sql .= " ref_object,";
$sql .= " type_code,";
$sql .= " linktoref,";
$sql .= " linktype,";
$sql .= " object_data,";
$sql .= " object_version,";
$sql .= " object_format,";
$sql .= " certified,";
$sql .= " fk_user,";
$sql .= " user_fullname,";
$sql .= " entity,";
$sql .= " debuginfo"; // Only stored
$sql .= ") VALUES (";
$sql .= "'".$this->db->idate($this->date_creation, $tz)."',";
$sql .= "'".$this->db->escape($this->action)."',";
$sql .= "'".$this->db->escape((string) $this->module_source)."',";
$sql .= "'".$this->db->escape((string) $this->pos_source)."',";
$sql .= (is_null($this->amounts_taxexcl) ? "null" : (float) $this->amounts_taxexcl).",";
$sql .= (float) $this->amounts.",";
$sql .= "'".$this->db->escape($this->signature)."',";
$sql .= "'".$this->db->escape($this->element)."',";
$sql .= (int) $this->fk_object.",";
$sql .= "'".$this->db->idate($this->date_object, $tz)."',";
$sql .= "'".$this->db->escape($this->ref_object)."',";
$sql .= "'".$this->db->escape($this->type_code)."',";
$sql .= ($this->linktoref ? "'".$this->db->escape($this->linktoref)."'" : "null").",";
$sql .= ($this->linktype ? "'".$this->db->escape($this->linktype)."'" : "null").",";
$sql .= "'".$this->db->escape($this->dolEncodeBlockedData($this->object_data))."',";
$sql .= "'".$this->db->escape($this->object_version)."',";
$sql .= "'".$this->db->escape($this->object_format)."',";
$sql .= "0,";
$sql .= ((int) $this->fk_user).",";
$sql .= "'".$this->db->escape($this->user_fullname)."',";
$sql .= ((int) ($this->entity ? $this->entity : $conf->entity)).",";
$sql .= "'".$this->db->escape($this->debuginfo)."'";
$sql .= ")";
/*
$a = serialize($this->object_data); $a2 = unserialize($a); $a4 = print_r($a2, true);
$b = json_encode($this->object_data); $b2 = json_decode($b); $b4 = print_r($b2, true);
var_dump($a4 == print_r($this->object_data, true) ? 'a=a' : 'a not = a');
var_dump($b4 == print_r($this->object_data, true) ? 'b=b' : 'b not = b');
exit;
*/
$res = $this->db->query($sql);
if ($res) {
$id = $this->db->last_insert_id(MAIN_DB_PREFIX."blockedlog");
if ($id > 0) {
// The new ID
$this->id = $id;
$this->db->commit();
// Call remote API service to record the last counter
/*
$error = 0;
include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
try {
$resultcall = callApiToPushCounter((int) $this->id, $this->signature, $this->date_creation, 0, (int) $previousid, $previoushash, $previousdatecreation);
} catch (Exception $e) {
$error++;
$this->error = $e->getMessage();
}
if (!$error) {
return $this->id;
} else {
return -3;
}
*/
return $this->id;
} else {
$this->db->rollback();
return -2;
}
} else {
$this->error = $this->db->error();
$this->db->rollback();
return -1;
}
// The commit or rollback will release the lock so app can insert other record now
}
/**
* Check if calculated signature still correct compared to the value in the chain
*
* @param string $previoushash If previous signature hash is known, we can provide it to avoid to make a search of it in database.
* @param int<0,2> $returnarray 1=Return array of details, 2=Return array of details including keyforsignature, 0=Return a boolean
* @return boolean|array{checkresult:bool,calculatedsignature:string,previoushash:string,keyforsignature?:string} Array or true if OK, false if KO
*/
public function checkSignature($previoushash = '', $returnarray = 0)
{
if (empty($previoushash)) {
$tmparray = $this->getPreviousHash(0, $this->id);
$previoushash = $tmparray['previoushash'];
}
$concatenateddata = '';
$signature = '';
// Recalculate the signature
try {
// Build the string for the signature
$concatenateddata = $this->buildKeyForSignature();
$signature = $this->buildFinalSignatureHash($previoushash.$concatenateddata);
//var_dump($previoushash, $concatenateddata, $this->object_format, $signature);
} catch (Exception $e) {
$res = ($signature === $this->signature);
$this->error = $e->getMessage();
dol_syslog($this->error, LOG_ERR);
if ($returnarray) {
return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'error' => $this->error);
} else {
return false;
}
}
$res = ($signature === $this->signature);
if (!$res) {
$this->error = 'Signature KO';
}
if ($returnarray) {
if ($returnarray == 1) {
unset($concatenateddata);
return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash);
} else { // Consume much memory ($concatenateddata is a large var)
return array('checkresult' => $res, 'calculatedsignature' => $signature, 'previoushash' => $previoushash, 'keyforsignature' => $concatenateddata);
}
} else {
unset($concatenateddata);
return $res;
}
}
/**
* Return first part of string for signature (clear data)
* Note: rowid of line not included as it is not a business data and this allow to make backup of a year
* and restore it into another database with different ids without comprimising checksums
*
* @param string $format Force format to use
* @return string First part of key for signature
*/
private function buildFirstPartOfKeyForSignature($format = '')
{
if (empty($format)) {
$format = $this->object_format;
}
// Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
if ($format == '') {
return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
} elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
return $this->date_creation.'|'.$this->action.'|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
} elseif ($format == 'V2') {
$s = $this->entity;
$s .= '|'.$this->date_creation.'|'.$this->action.'|'.$this->module_source.'|'.$this->pos_source.'|'.$this->amounts_taxexcl;
$s .= '|'.$this->amounts.'|'.$this->ref_object.'|'.$this->date_object.'|'.$this->user_fullname;
if ($this->type_code) {
$s .= '|'.(string) $this->type_code;
}
$s .= '|'.(string) $this->linktoref;
$s .= '|'.(string) $this->linktype;
return $s;
} else {
throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
}
}
/**
* Return the string for signature (clear data).
*
* @param string $format Force format to use
* @return string Key for signature
*/
public function buildKeyForSignature($format = '')
{
//print_r($this->object_data);
if (empty($format)) {
$format = $this->object_format;
}
if ($format == '') {
return $this->buildFirstPartOfKeyForSignature($format).'|'.print_r($this->object_data, true);
} elseif ($format == 'V1') { // Note: $this->amounts can be '0', '1.1', '1.123'; // All 0 at end should have been removed already
return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
} elseif ($format == 'V2') {
return $this->buildFirstPartOfKeyForSignature($format).'|'.json_encode($this->object_data, JSON_FORCE_OBJECT);
} else {
throw new Exception('Error bad value "'.$format.'" for object_format');
}
}
/**
* Return a hash that is the signature of a line (hash_hmac en SHA256 des données + clé secrète)
*
* @param string $clearstring Data to sign
* @param string $format Force format to use
* @return string Signature string
*/
private function buildFinalSignatureHash($clearstring, $format = '')
{
global $conf;
if (empty($format)) {
$format = $this->object_format;
}
if ($format == '') {
return dol_hash($clearstring, '5');
} elseif ($format == 'V1') {
return dol_hash($clearstring, '5');
} elseif ($format == 'V2') {
// BLOCKEDLOG_HMAC_KEY is a HMAC key starting with 'BLOCKEDLOGHMAC....', but it is not stored as a clear data. It will be decrypted later.
$hmac_encoded_secret_key = '';
// Get value of the $hmac_encoded_secret_key from the database
if (empty($conf->cache['hmac_encoded_secret_key'])) {
$sql = "SELECT value FROM ".MAIN_DB_PREFIX."const WHERE name = 'BLOCKEDLOG_HMAC_KEY'";
$sql .= " AND entity IN (0, ".$this->entity.")";
$sql .= " ORDER BY entity DESC LIMIT 1";
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
if ($obj) {
$hmac_encoded_secret_key = $obj->value;
}
}
$conf->cache['hmac_encoded_secret_key'] = $hmac_encoded_secret_key; // Save value in memory page cache (if we recall the same function in same page transaction).
} else {
$hmac_encoded_secret_key = $conf->cache['hmac_encoded_secret_key'];
}
if (empty($hmac_encoded_secret_key)) {
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');
}
// Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
$hmac_secret_key = '';
// Get the obfuscation key from ping.dolibarr.org (used just after to decode HMAC secret key)
$errormsg = '';
try {
$obfuscationkey = $this->getObfuscationKey(); // Note: On network trouble, an Exception is thrown to the caller
} catch (Exception $e) {
$errormsg = $e->getMessage();
}
if (!$errormsg && $obfuscationkey) {
// Decode the encrypted parameter using the obfuscation key to get the HMAC key in memory.
$hmac_secret_key = $this->dolDecodeHMACKey($hmac_encoded_secret_key, $obfuscationkey);
}
if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
// 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 previous versions).
// We test this and we force a migrate data if this is the case.
// Example with the old demo sample database:
// dolcrypt:AES-256-CTR:46cb611f00c4cff8:XVfEh15vX/JOYmpiw2QPNamcTQwdbBZJTcXBh9rMpzYJOpVPZubIWcgA8wHMXA== and instance_unique_id=11f3c81e86fc9e3b3fd11d81c9a31bd0
/*if (!empty($conf->file->dolcrypt_key)) {
// If dolcrypt_key is defined, we used it in priority. Note: this param has never been set for the moment.
$oldobfuscationkey = $conf->file->dolcrypt_key;
} else {*/
// We fall back on the instance_unique_id (coming from $dolibarr_main_instance_unique_id, for backward compatibility).
$oldobfuscationkey = !empty($conf->file->instance_unique_id) ? $conf->file->instance_unique_id : "";
/* } */
$hmac_secret_key = $this->dolDecodeHMACKey($hmac_encoded_secret_key, $oldobfuscationkey); // Decode the encrypted parameter using the obfuscation key from ping.dolibarr.org to decode HMAC key
if (!preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
//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.');
throw new Exception('Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_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 : ''));
}
}
if (!preg_match('/^dolobfuscationv1:/', $hmac_encoded_secret_key) && preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key)) {
// On old versions, the old method is ok, we must update the data saving mode to use the new method.
// TODO insert/update into database the new value of the HMAC key obfuscated with $obfuscationkey.
//print 'rrrrrrrrr';
}
// Here HMAC secret key is a long string starting with BLOCKEDLOGHMAC....
return hash_hmac('sha256', $clearstring, $hmac_secret_key);
} else {
throw new Exception('Error bad value "'.$this->object_format.'" for object_format');
}
}
/**
* Return the obfuscation key from ping.dolibarr.org (used later to decode HMAC secret key)
*
* @return string Obfuscation key or '' if error.
*/
private function getObfuscationKey()
{
global $conf, $mysoc;
// If key found into the user session memory cache, we use it
if (!empty($_SESSION['hmac_secret_key'])) {
return (string) $_SESSION['hmac_secret_key'];
}
// If key found into the page memory cache, we use it
if (!empty($conf->cache['hmac_secret_key'])) {
return (string) $conf->cache['hmac_secret_key'];
}
include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
$registrationnumber = getHashUniqueIdOfRegistration();
// Value is not into cache, we must get it from ping.dolibarr.org
$obfuscationkey = callApiToGetObfuscationKey($mysoc->idprof1, $registrationnumber);
if (empty($obfuscationkey) || strpos($obfuscationkey, 'ERROR') === 0) {
throw new Exception('Error: Failed to get the obfuscation key from ping.dolibarr.org. May be the SIREN is not valid or the server is down. Re-try later.');
}
// Now store value in cache
$_SESSION['hmac_secret_key'] = $obfuscationkey;
$conf->cache['hmac_secret_key'] = $obfuscationkey;
return $obfuscationkey;
}
/**
* Return the unobfuscated string of a HMAC obfuscated string using the key.
* This function is called by getObfuscationKey() that use a memory cache.
*
* @param string $hmac_encoded_secret_key HMAC encrypted key in database. Example: 'dolcrypt:AES-256-CBC:12345678901234567890123456789012:12345678901234567890123456789012'
* @param string $obfuscationkey Obfuscation key
* @return string Decoded string
*/
private function dolDecodeHMACKey($hmac_encoded_secret_key, $obfuscationkey = '')
{
$reg = array();
if (preg_match('/^(dolobfuscationv1|dolcrypt):([^:]+):(.+)$/', $hmac_encoded_secret_key, $reg)) { // deprecated code
$ciphering = $reg[2];
if (function_exists('openssl_decrypt')) {
if (empty($obfuscationkey)) {
dol_syslog("Error dolDecodeHMACKey decrypt key is empty", LOG_WARNING);
return '';
}
$tmpexplode = explode(':', $reg[3]);
if (!empty($tmpexplode[1])) {
$newchain = openssl_decrypt($tmpexplode[1], $ciphering, $obfuscationkey, 0, $tmpexplode[0]);
} else {
$newchain = openssl_decrypt((string) $tmpexplode[0], $ciphering, $obfuscationkey, 0, '');
}
// Test validity of decryption
if (!ascii_check($newchain)) {
dol_syslog("Error dolDecodeHMACKey failed: The string decoded with obfuscation key return a non valid value (not ascii)", LOG_ERR);
return '';
}
} else {
dol_syslog("Error dolDecodeHMACKey openssl_decrypt is not available", LOG_ERR);
return '';
}
return $newchain;
} else {
return '';
}
}
/**
* Get previous signature/hash in chain. If there is no previous line, return the init hash.
*
* @param int<0,1> $withlock 1=With a lock (Used in the ->create() transaction)
* @param int $beforeid ID of a record
* @return array<string, int|string> Hash of previous record (if beforeid is defined) or hash of last record (if beforeid is 0)
*/
public function getPreviousHash($withlock = 0, $beforeid = 0)
{
global $conf;
$previousid = 0;
$previoussignature = '';
$previousdatecreation = 0;
// Fast search of previous record by searching with beforeid - 1. This is very fast and will work 99% of time.
if ($beforeid) {
$sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
$sql .= " WHERE entity = ".((int) $conf->entity);
$sql .= " AND rowid = ".((int) $beforeid - 1);
$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
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
if ($obj) {
$previousid = $obj->rowid;
$previoussignature = $obj->signature;
$tz = 'gmt';
if (empty($obj->object_format) || $obj->object_format == 'V1') {
$tz = 'tzserver';
}
$previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
}
} else {
dol_print_error($this->db);
exit;
}
}
if (empty($previoussignature)) {
// Note: a select max rowid and then a select to get signature seems not faster due to filter on entity
$sql = "SELECT rowid, signature, date_creation, object_format FROM ".MAIN_DB_PREFIX."blockedlog";
if ($beforeid) {
$sql .= $this->db->hintindex('entity_rowid', 1);
}
$sql .= " WHERE entity = ".((int) $conf->entity);
if ($beforeid) {
$sql .= " AND rowid < ".(int) $beforeid;
}
$sql .= " ORDER BY rowid DESC LIMIT 1";
$sql .= ($withlock ? " FOR UPDATE " : "");
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
if ($obj) {
$previousid = $obj->rowid;
$previoussignature = $obj->signature;
$tz = 'gmt';
if (empty($obj->object_format) || $obj->object_format == 'V1') {
$tz = 'tzserver';
}
$previousdatecreation = $this->db->jdate($obj->date_creation, $tz);
}
} else {
dol_print_error($this->db); // can happen after a deadlock when too many requests do create into blocked log happen at the same time.
http_response_code(503);
exit;
}
}
if (empty($previoussignature)) {
// First signature line (line 0)
$previousid = 0;
$previoussignature = $this->getOrInitFirstSignature();
}
return array('previousid' => $previousid, 'previoushash' => $previoussignature, 'previousdatecreation' => $previousdatecreation);
}
/**
* Return array of unalterable log objects (filtered with criteria)
*
* @param string $element Element to search
* @param string|int $fk_object Id of object to search. Can be a UFS search criteria.
* @param int<0,max> $limit Max number of element, 0 for all
* @param string $sortfield Sort field
* @param string $sortorder Sort order
* @param int $search_fk_user Id of user(s)
* @param int $search_start Start time limit
* @param int $search_end End time limit
* @param string $search_ref Search ref
* @param string $search_amount Search amount
* @param string|string[] $search_code Search code
* @param string $search_signature Search signature
* @param string $search_module_source Search on module source
* @param string $search_pos_source Search on terminal
* @param string $search_type_code Search on type code
* @return BlockedLog[]|int<-2,-1> Array of object log or <0 if error
*/
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 = '')
{
global $conf;
//global $cachedlogs;
/* $cachedlogs allow fastest search */
//if (empty($cachedlogs)) $cachedlogs = array();
if ($element == 'all') {
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
WHERE entity = ".$conf->entity;
} elseif ($element == 'not_certified') {
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
WHERE entity = ".$conf->entity." AND certified = 0";
} elseif ($element == 'just_certified') {
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
WHERE entity = ".$conf->entity." AND certified = 1";
} else {
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."blockedlog
WHERE entity = ".$conf->entity." AND element = '".$this->db->escape($element)."'";
}
if ($fk_object) {
$sql .= natural_search("rowid", (string) $fk_object, 1);
}
if ($search_fk_user > 0) {
$sql .= natural_search("fk_user", (string) $search_fk_user, 2);
}
if ($search_start > 0) {
$sql .= " AND date_creation >= '".$this->db->idate($search_start, 'gmt')."'";
}
if ($search_end > 0) {
$sql .= " AND date_creation <= '".$this->db->idate($search_end, 'gmt')."'";
}
if ($search_type_code) {
$sql .= natural_search("type_code", (string) $search_type_code);
}
if ($search_ref != '') {
$sql .= " AND (".natural_search("ref_object", $search_ref, 0, 1);
$sql .= " OR ".natural_search("linktoref", $search_ref, 0, 1).")";
}
if ($search_amount != '') {
$sql .= natural_search("amounts", $search_amount, 1);
}
if ($search_signature != '') {
$sql .= natural_search("signature", $search_signature, 0);
}
if (is_array($search_code)) {
if (!empty($search_code)) {
$sql .= natural_search("action", implode(',', $search_code), 3);
}
} else {
if ($search_code != '' && $search_code != '-1') {
$sql .= natural_search("action", $search_code, 3);
}
}
if (is_array($search_module_source)) {
if (!empty($search_module_source)) {
$sql .= " AND (";
if (in_array('0', $search_module_source)) {
$sql .= "module_source = ''";
unset($search_module_source[0]);
if (!empty($search_module_source)) {
$sql .= " OR ";
}
}
if (!empty($search_module_source)) {
$sql .= natural_search("module_source", implode(',', $search_module_source), 3, 1);
}
$sql .= " OR module_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different origins (pos and not pos)
$sql .= ")";
}
} else {
if ($search_module_source != '' && $search_module_source != '-1') {
$sql .= natural_search("module_source", $search_module_source, 3);
}
}
if ($search_pos_source != '') {
$sql .= " AND (";
$sql .= natural_search("pos_source", $search_pos_source, 0, 1);
$sql .= " OR pos_source = 'mix'"; // When a payment was recorded and payment was on an invoice with different terminal (pos and not pos)
$sql .= ")";
}
$sql .= $this->db->order($sortfield, $sortorder);
$sql .= $this->db->plimit($limit + 1); // We want more, because we will stop into loop later with error if we reach max
$res = $this->db->query($sql);
if ($res) {
$results = array();
$i = 0;
while ($obj = $this->db->fetch_object($res)) {
$i++;
if ($i > $limit) {
// Too many record, we will consume too much memory
return -2;
}
//if (!isset($cachedlogs[$obj->rowid]))
//{
$b = new BlockedLog($this->db);
$result = $b->fetch($obj->rowid);
//$b->loadTrackedEvents();
//$cachedlogs[$obj->rowid] = $b;
//}
//$results[] = $cachedlogs[$obj->rowid];
if ($result < 0) {
$this->error = $b->error;
$this->errors = $b->errors;
return -1;
}
$results[] = $b;
}
return $results;
}
return -1;
}
/**
* Return the signature (hash) of the "genesis-block" (Block 0).
*
* @return string Signature of genesis-block for current conf->entity
*/
public function getOrInitFirstSignature()
{
global $db, $conf;
if (!getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT')) { // creation of a unique fingerprint
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
$fingerprint = bin2hex(random_bytes(32)); // 64 char hex
dolibarr_set_const($db, 'BLOCKEDLOG_ENTITY_FINGERPRINT', $fingerprint, 'chaine', 0, 'Initial signature fingerprint', $conf->entity);
$conf->global->BLOCKEDLOG_ENTITY_FINGERPRINT = $fingerprint;
}
return getDolGlobalString('BLOCKEDLOG_ENTITY_FINGERPRINT');
}
/**
* Check if module was already used or not for at least one recording.
*
* @param int<0,1> $ignoresystem Ignore system events for the test
* @return bool
*/
public function alreadyUsed($ignoresystem = 0)
{
include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
return isBlockedLogUsed($ignoresystem);
}
/**
* Check if module can be enabled.
*
* @return string '' if ok, error message if not possible
*/
public function canBeEnabled()
{
include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
include_once DOL_DOCUMENT_ROOT.'/core/lib/securitycore.lib.php';
$isqualified = isALNEQualifiedVersion(0, 1);
if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && !isHTTPS()) {
return 'Error: The HTTPS must be enabled to allow the use of this module in France.';
}
return '';
}
/**
* Check if module can be disabled.
*
* @return int<0,1> 0=Can't be disabled, 1=Can be disabled
*/
public function canBeDisabled()
{
global $mysoc;
include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
$isqualified = isALNEQualifiedVersion();
$canbedisabled = 1;
// For france, we can never disable the module (except in debug mode)
if ($isqualified && ($isqualified != 'CERTIF_LNE_IS_2') && $mysoc->country_code == 'FR') {
$canbedisabled = 0;
}
return $canbedisabled;
}
/**
* Return current number of records.
*
* @return int Number of recor for all instances
*/
public function countRecord()
{
$nb = 0;
$sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."blockedlog";
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
$nb = $obj->nb;
} else {
dol_print_error($this->db);
}
$this->db->free($resql);
return $nb;
}
}