Must be able to export lockedlog for a dedicated year.

Fix date are nto humanly readable.
This commit is contained in:
Laurent Destailleur 2018-01-25 12:57:36 +01:00
parent 005bccc63d
commit 8d7f6843a4
6 changed files with 264 additions and 123 deletions

View File

@ -127,7 +127,7 @@ print info_admin(showModulesExludedForExternal($modules)).'<br>'."\n";
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder" width="100%">';
// Affiche lignes des permissions
// Show permissions lines
$sql = "SELECT r.id, r.libelle, r.module, r.perms, r.subperms, r.bydefault";
$sql.= " FROM ".MAIN_DB_PREFIX."rights_def as r";
$sql.= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous"

View File

@ -31,7 +31,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
$langs->loadLangs(array("admin", "other", "blockedlog", "bills"));
if (! $user->admin) accessforbidden();
if ((! $user->admin && ! $user->rights->blockedlog->read) || empty($conf->blockedlog->enabled)) accessforbidden();
$action = GETPOST('action','alpha');
$contextpage= GETPOST('contextpage','aZ')?GETPOST('contextpage','aZ'):'blockedloglist'; // To manage different context of search
@ -103,93 +103,147 @@ if ($action === 'downloadblockchain') {
exit;
}
else if($action === 'downloadcsv') {
else if (GETPOST('downloadcsv','alpha'))
{
$error = 0;
$sql = "SELECT rowid,date_creation,tms,user_fullname,action,amounts,element,fk_object,date_object,ref_object,signature,fk_user,object_data";
$sql.= " FROM ".MAIN_DB_PREFIX."blockedlog";
$sql.= " WHERE entity = ".$conf->entity;
$sql.= " ORDER BY rowid ASC"; // Required so later we can use the parameter $previoushash of checkSignature()
$previoushash='';
$firstid='';
$res = $db->query($sql);
if($res) {
$signature = $block_static->getSignature();
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"archive-log-" .$signature. ".csv\"");
print $langs->transnoentities('Id')
.';'.$langs->transnoentities('Date')
.';'.$langs->transnoentities('User')
.';'.$langs->transnoentities('Action')
.';'.$langs->transnoentities('Element')
.';'.$langs->transnoentities('Amounts')
.';'.$langs->transnoentities('ObjectId')
.';'.$langs->transnoentities('Date')
.';'.$langs->transnoentities('Ref')
.';'.$langs->transnoentities('Fingerprint')
.';'.$langs->transnoentities('Status')
.';'.$langs->transnoentities('FullData')
."\n";
$previoushash = '';
$loweridinerror = 0;
while ($obj = $db->fetch_object($res))
if (! $error)
{
// Get ID of first line
$sql = "SELECT rowid,date_creation,tms,user_fullname,action,amounts,element,fk_object,date_object,ref_object,signature,fk_user,object_data";
$sql.= " FROM ".MAIN_DB_PREFIX."blockedlog";
$sql.= " WHERE entity = ".$conf->entity;
if (GETPOST('yeartoexport','int') > 0)
{
// We set here all data used into signature calculation (see checkSignature method) and more
// IMPORTANT: We must have here, the same rule for transformation of data than into the fetch method (db->jdate for date, ...)
$block_static->id = $obj->rowid;
$block_static->date_creation = $db->jdate($obj->date_creation);
$block_static->date_modification = $db->jdate($obj->tms);
$block_static->action = $obj->action;
$block_static->fk_object = $obj->fk_object;
$block_static->element = $obj->element;
$block_static->amounts = (double) $obj->amounts;
$block_static->ref_object = $obj->ref_object;
$block_static->date_object = $db->jdate($obj->date_object);
$block_static->user_fullname = $obj->user_fullname;
$block_static->fk_user = $obj->fk_user;
$block_static->signature = $obj->signature;
$block_static->object_data = unserialize($obj->object_data);
$dates = dol_get_first_day(GETPOST('yeartoexport','int'), 1);
$datee = dol_get_last_day(GETPOST('yeartoexport','int'), 12);
$sql.= " AND date_creation BETWEEN '".$db->idate($dates)."' AND '".$db->idate($datee)."'";
}
$sql.= " ORDER BY rowid ASC"; // Required so we get the first one
$sql.= $db->plimit(1);
$checksignature = $block_static->checkSignature($previoushash); // If $previoushash is not defined, checkSignature will search it
$res = $db->query($sql);
if($res)
{
// Make the first fetch to get first line
$obj = $db->fetch_object($res);
if ($checksignature)
{
if ($loweridinerror > 0) $statusofrecord = 'ValidButFoundAPreviousKO';
else $statusofrecord = 'Valid';
}
else
{
$statusofrecord = 'KO';
$loweridinerror = $obj->rowid;
}
$previoushash = $block_static->getPreviousHash(0, $obj->rowid);
$firstid = $obj->rowid;
}
else
{
$error++;
setEventMessage($db->lasterror, 'errors');
}
}
print $obj->rowid
.';'.$obj->date_creation
.';"'.$obj->user_fullname.'"'
.';'.$obj->action
.';'.$obj->element
.';'.$obj->amounts
.';'.$obj->fk_object
.';'.$obj->date_object
.';"'.$obj->ref_object.'"'
.';'.$obj->signature
.';'.$statusofrecord
.';"'.str_replace('"','""',$obj->object_data).'"'
if (! $error)
{
// Now restart request with all data
$sql = "SELECT rowid,date_creation,tms,user_fullname,action,amounts,element,fk_object,date_object,ref_object,signature,fk_user,object_data";
$sql.= " FROM ".MAIN_DB_PREFIX."blockedlog";
$sql.= " WHERE entity = ".$conf->entity;
if (GETPOST('yeartoexport','int') > 0)
{
$dates = dol_get_first_day(GETPOST('yeartoexport','int'), 1);
$datee = dol_get_last_day(GETPOST('yeartoexport','int'), 12);
$sql.= " AND date_creation BETWEEN '".$db->idate($dates)."' AND '".$db->idate($datee)."'";
}
$sql.= " ORDER BY rowid ASC"; // Required so later we can use the parameter $previoushash of checkSignature()
$res = $db->query($sql);
if($res)
{
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"archive-log-" .(GETPOST('yeartoexport','int')>0?GETPOST('yeartoexport','int').'-':'').'-'.$previoushash. ".csv\"");
print $langs->transnoentities('Id')
.';'.$langs->transnoentities('Date')
.';'.$langs->transnoentities('User')
.';'.$langs->transnoentities('Action')
.';'.$langs->transnoentities('Element')
.';'.$langs->transnoentities('Amounts')
.';'.$langs->transnoentities('ObjectId')
.';'.$langs->transnoentities('Date')
.';'.$langs->transnoentities('Ref')
.';'.$langs->transnoentities('Fingerprint')
.';'.$langs->transnoentities('Status')
.';'.$langs->transnoentities('Note')
.';'.$langs->transnoentities('FullData')
."\n";
// Set new previous hash for next fetch
$previoushash = $obj->signature;
}
$loweridinerror = 0;
$i = 0;
exit;
}
else
{
setEventMessage($db->lasterror, 'errors');
while ($obj = $db->fetch_object($res))
{
// We set here all data used into signature calculation (see checkSignature method) and more
// IMPORTANT: We must have here, the same rule for transformation of data than into the fetch method (db->jdate for date, ...)
$block_static->id = $obj->rowid;
$block_static->date_creation = $db->jdate($obj->date_creation);
$block_static->date_modification = $db->jdate($obj->tms);
$block_static->action = $obj->action;
$block_static->fk_object = $obj->fk_object;
$block_static->element = $obj->element;
$block_static->amounts = (double) $obj->amounts;
$block_static->ref_object = $obj->ref_object;
$block_static->date_object = $db->jdate($obj->date_object);
$block_static->user_fullname = $obj->user_fullname;
$block_static->fk_user = $obj->fk_user;
$block_static->signature = $obj->signature;
$block_static->object_data = unserialize($obj->object_data);
$checksignature = $block_static->checkSignature($previoushash); // If $previoushash is not defined, checkSignature will search it
if ($checksignature)
{
$statusofrecord = 'Valid';
if ($loweridinerror > 0) $statusofrecordnote = 'ValidButFoundAPreviousKO';
else $statusofrecordnote = '';
}
else
{
$statusofrecord = 'KO';
$statusofrecordnote = 'LineCorruptedOrNotMatchingPreviousOne';
$loweridinerror = $obj->rowid;
}
if ($i==0)
{
$statusofrecordnote = $langs->trans("PreviousFingerprint").': '.$previoushash.($statusofrecordnote?' - '.$statusofrecordnote:'');
}
print $obj->rowid
.';'.$obj->date_creation
.';"'.$obj->user_fullname.'"'
.';'.$obj->action
.';'.$obj->element
.';'.$obj->amounts
.';'.$obj->fk_object
.';'.$obj->date_object
.';"'.$obj->ref_object.'"'
.';'.$obj->signature
.';'.$statusofrecord
.';'.$statusofrecordnote
.';"'.str_replace('"','""',$obj->object_data).'"'
."\n";
// Set new previous hash for next fetch
$previoushash = $obj->signature;
$i++;
}
exit;
}
else
{
setEventMessage($db->lasterror, 'errors');
}
}
}
@ -262,18 +316,22 @@ if (GETPOST('withtab','alpha')) $param.='&withtab='.urlencode(GETPOST('withtab',
// Add $param from extra fields
//include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
print '<div align="right">';
//print ' <a href="?all=1'.(GETPOST('withtab','alpha')?'&withtab='.GETPOST('withtab','alpha'):'').'">'.$langs->trans('ShowAllFingerPrintsMightBeTooLong').'</a>';
//print ' | <a href="?all=1&search_showonlyerrors=1'.(GETPOST('withtab','alpha')?'&withtab='.GETPOST('withtab','alpha'):'').'">'.$langs->trans('ShowAllFingerPrintsErrorsMightBeTooLong').'</a>';
print ' <a href="?action=downloadcsv'.(GETPOST('withtab','alpha')?'&withtab='.GETPOST('withtab','alpha'):'').'">'.$langs->trans('DownloadLogCSV').'</a>';
print $langs->trans("YearToExport").': ';
print '<input type="text" name="yeartoexport" class="maxwidth100" value="'.GETPOST('yeartoexport','int').'">';
print '<input type="hidden" name="withtab" value="'.GETPOST('withtab','alpha').'">';
print '<input type="submit" name="downloadcsv" class="button" value="'.$langs->trans('DownloadLogCSV').'">';
if (!empty($conf->global->BLOCKEDLOG_USE_REMOTE_AUTHORITY)) print ' | <a href="?action=downloadblockchain'.(GETPOST('withtab','alpha')?'&withtab='.GETPOST('withtab','alpha'):'').'">'.$langs->trans('DownloadBlockChain').'</a>';
print ' </div>';
print '</form>';
print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
print '<div class="div-table-responsive">'; // You can use div-table-responsive-no-min if you dont need reserved height for your table
print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'">';
if ($optioncss != '') print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
@ -330,6 +388,9 @@ $array=array("1"=>$langs->trans("OnlyNonValid"));
print $form->selectarray('search_showonlyerrors', $array, $search_showonlyerrors, 1);
print '</td>';
// Status note
print '<td class="liste_titre"></td>';
// Action column
print '<td class="liste_titre" align="middle">';
$searchpicto=$form->showFilterButtons();
@ -349,6 +410,7 @@ print getTitleFieldOfList($langs->trans('Amount'), 0, $_SERVER["PHP_SELF"],'',''
print getTitleFieldOfList($langs->trans('DataOfArchivedEvent'), 0, $_SERVER["PHP_SELF"],'','',$param,'align="center"',$sortfield,$sortorder,'')."\n";
print getTitleFieldOfList($langs->trans('Fingerprint'), 0, $_SERVER["PHP_SELF"],'','',$param,'',$sortfield,$sortorder,'')."\n";
print getTitleFieldOfList($langs->trans('Status'), 0, $_SERVER["PHP_SELF"],'','',$param,'align="center"',$sortfield,$sortorder,'')."\n";
print getTitleFieldOfList('', 0, $_SERVER["PHP_SELF"],'','',$param,'align="center"',$sortfield,$sortorder,'')."\n";
print getTitleFieldOfList('<span id="blockchainstatus"></span>', 0, $_SERVER["PHP_SELF"],'','',$param,'align="center"',$sortfield,$sortorder,'')."\n";
print '</tr>';
@ -386,7 +448,8 @@ if (is_array($blocks))
{
$object_link = $block->getObjectLink();
if (empty($search_showonlyerrors) || ! $checkresult[$block->id] || ($loweridinerror && $block->id >= $loweridinerror))
//if (empty($search_showonlyerrors) || ! $checkresult[$block->id] || ($loweridinerror && $block->id >= $loweridinerror))
if (empty($search_showonlyerrors) || ! $checkresult[$block->id])
{
print '<tr class="oddeven">';
// ID
@ -411,10 +474,11 @@ if (is_array($blocks))
print $form->textwithpicto(dol_trunc($block->signature, '12'), $block->signature);
print '</td>';
// Status
print '<td class="center">';
if (! $checkresult[$block->id] || ($loweridinerror && $block->id >= $loweridinerror)) // If error
{
if ($checkresult[$block->id]) print img_picto($langs->trans('OkCheckFingerprintValidityButChainIsKo'), 'statut1');
if ($checkresult[$block->id]) print img_picto($langs->trans('OkCheckFingerprintValidityButChainIsKo'), 'statut4');
else print img_picto($langs->trans('KoCheckFingerprintValidity'), 'statut8');
}
else
@ -422,10 +486,19 @@ if (is_array($blocks))
print img_picto($langs->trans('OkCheckFingerprintValidity'), 'statut4');
}
print '</td>';
// Status note
print '<td class="center">';
if (! $checkresult[$block->id] || ($loweridinerror && $block->id >= $loweridinerror)) // If error
{
if ($checkresult[$block->id]) print $form->textwithpicto('', $langs->trans('OkCheckFingerprintValidityButChainIsKo'));
}
if(!empty($conf->global->BLOCKEDLOG_USE_REMOTE_AUTHORITY) && !empty($conf->global->BLOCKEDLOG_AUTHORITY_URL)) {
print ' '.($block->certified ? img_picto($langs->trans('AddedByAuthority'), 'info') : img_picto($langs->trans('NotAddedByAuthorityYet'), 'info_black') );
}
print '</td>';
print '</td>';
print '<td></td>';
@ -437,12 +510,11 @@ if (is_array($blocks))
print '</table>';
print '</form>';
print '</div>';
print '</form>';
// Javascript to manage the showinfo popup
print '<script type="text/javascript">
jQuery(document).ready(function () {
@ -454,36 +526,19 @@ jQuery(document).ready(function () {
$("a[rel=show-info]").click(function() {
console.log("We click on tooltip");
jQuery("#dialogforpopup").html(\'<div id="pop-info"><table width="100%" height="80%" class="border"><thead><th width="50%">'.$langs->trans('Field').'</th><th>'.$langs->trans('Value').'</th></thead><tbody></tbody></table></div>\');
console.log("We click on tooltip, we open popup and get content using an ajax call");
var fk_block = $(this).attr("data-blockid");
$.ajax({
url:"../ajax/block-info.php?id="+fk_block
,dataType:"json"
,dataType:"html"
}).done(function(data) {
drawData(data,"");
jQuery("#dialogforpopup").html(data);
});
jQuery("#dialogforpopup").dialog("open");
});
function drawData(data, prefix) {
for(x in data) {
value = data[x];
if (typeof value != "object") {
$("#pop-info table tbody").append("<tr><td>"+prefix+x+"</td><td class=\"wordwrap\">"+value+"</td></tr>");
}
if ((typeof value === "object") && (value !== null)) {
drawData(value, prefix+x+" &gt;&gt; ");
}
}
}
})
</script>'."\n";

View File

@ -36,12 +36,77 @@ if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1');
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php';
$id = GETPOST('id');
$id = GETPOST('id','int');
$block = new BlockedLog($db);
if ($block->fetch($id)>0) {
echo json_encode($block->object_data);
if ((! $user->admin && ! $user->rights->blockedlog->read) || empty($conf->blockedlog->enabled)) accessforbidden();
/*
* View
*/
print '<div id="pop-info"><table width="100%" height="80%" class="border"><thead><th width="50%" class="left">'.$langs->trans('Field').'</th><th class="left">'.$langs->trans('Value').'</th></thead>';
print '<tbody>';
if ($block->fetch($id) > 0)
{
$objtoshow = $block->object_data;
print formatObject($objtoshow, '');
}
else {
echo json_encode(false);
print 'Error, failed to get unalterable log with id '.$id;
}
print '</tbody>';
print '</table></div>';
$db->close();
/**
* formatObject
*
* @param Object $objtoshow Object to show
* @param string $prefix Prefix of key
* @return string String formatted
*/
function formatObject($objtoshow, $prefix)
{
$s = '';
$newobjtoshow = $objtoshow;
if (is_object($newobjtoshow) || is_array($newobjtoshow))
{
//var_dump($newobjtoshow);
foreach($newobjtoshow as $key => $val)
{
if (! is_object($val) && ! is_array($val))
{
$s.='<tr><td>'.($prefix?$prefix.' > ':'').$key.'</td>';
$s.='<td>';
if (in_array($key, array('date','datef')))
{
$s.=dol_print_date($val, 'dayhour');
}
else
{
$s.=$val;
}
$s.='</td></tr>';
}
elseif (is_array($val))
{
$s.=formatObject($val, ($prefix?$prefix.' > ':'').$key);
}
elseif (is_object($val))
{
$s.=formatObject($val, ($prefix?$prefix.' > ':'').$key);
}
}
}
return $s;
}

View File

@ -363,9 +363,27 @@ class BlockedLog
{
if (in_array($key, array('fields'))) continue; // Discard some properties
if (! in_array($key, array(
'ref','facnumber','ref_client','ref_supplier','datef','type','total_ht','total_tva','total_ttc','localtax1','localtax2','revenuestamp','datepointoftax','note_public'
'ref','facnumber','ref_client','ref_supplier','date','datef','type','total_ht','total_tva','total_ttc','localtax1','localtax2','revenuestamp','datepointoftax','note_public','lines'
))) continue; // Discard if not into a dedicated list
if (!is_object($value)) $this->object_data->{$key} = $value;
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','multicurrency_code','multicurrency_total_ht','multicurrency_total_tva','multicurrency_total_ttc','qty','product_type','vat_src_code','tva_tx','info_bits','localtax1_tx','localtax2_tx','total_ht','total_tva','total_ttc','total_localtax1','total_localtax2'
))) continue; // Discard if not into a dedicated list
if (! is_object($this->object_data->invoiceline[$lineid])) $this->object_data->invoiceline[$lineid] = new stdClass();
$this->object_data->invoiceline[$lineid]->{$keyline} = $valueline;
}
}
}
else if (!is_object($value)) $this->object_data->{$key} = $value;
}
if (! empty($object->newref)) $this->object_data->ref = $object->newref;
@ -376,7 +394,7 @@ class BlockedLog
{
if (in_array($key, array('fields'))) continue; // Discard some properties
if (! in_array($key, array(
'ref','facnumber','ref_client','ref_supplier','datef','type','total_ht','total_tva','total_ttc','localtax1','localtax2','revenuestamp','datepointoftax','note_public'
'ref','facnumber','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
if (!is_object($value)) $this->object_data->{$key} = $value;
}
@ -467,7 +485,7 @@ class BlockedLog
{
if (in_array($key, array('fields'))) continue; // Discard some properties
if (! in_array($key, array(
'ref','facnumber','ref_client','ref_supplier','datef','type','total_ht','total_tva','total_ttc','localtax1','localtax2','revenuestamp','datepointoftax','note_public'
'ref','facnumber','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
if (!is_object($value))
{

View File

@ -38,7 +38,8 @@ if (! function_exists('json_encode'))
}
/**
* Implement json_encode for PHP that does not support it
* Implement json_encode for PHP that does not support it.
* Use json_encode and json_decode in your code !
*
* @param mixed $elements PHP Object to json encode
* @return string Json encoded string
@ -221,6 +222,7 @@ if (! function_exists('json_decode'))
/**
* Implement json_decode for PHP that does not support it
* Use json_encode and json_decode in your code !
*
* @param string $json Json encoded to PHP Object or Array
* @param bool $assoc False return an object, true return an array. Try to always use it with true !

View File

@ -2,9 +2,9 @@ BlockedLog=Unalterable Logs
Field=Field
BlockedLogDesc=This module tracks some events into an unalterable log (that you can't modify once recorded) into a block chain, in real time. This module provides compatibility with requirements of laws of some countries (like France with the law Fincance 2016 - Norme NF535).
Fingerprints=Archived events and fingerprints
FingerprintsDesc=This is the tool to browse the archived unalterable logs. Note that, by definition, there is no feature to purge this log and every change tried to be done directly into this log (by a hacker for example) will be reported with a non valid fingerprint. If you really need to purge this table because you used your application for a demo/test purpose and want to clean your data to start your production, you can ask your reseller or integrator to reset your database (all your data will be removed).
FingerprintsDesc=This is the tool to browse or extract the unalterable logs. Unalterable logs are generated and archived locally into a dedicated table, in real time when you record a business event. You can use this tool to export this archive and save it into an external support (some countries, like France, ask you do it every year). Note that, there is no feature to purge this log and every change tried to be done directly into this log (by a hacker for example) will be reported with a non valid fingerprint. If you really need to purge this table because you used your application for a demo/test purpose and want to clean your data to start your production, you can ask your reseller or integrator to reset your database (all your data will be removed).
CompanyInitialKey=Company initial key (hash of genesis block)
BrowseBlockedLog=Browse unalterable logs
BrowseBlockedLog=Unalterable logs
ShowAllFingerPrintsMightBeTooLong=Show all archived logs (might be long)
ShowAllFingerPrintsErrorsMightBeTooLong=Show all non valid archive logs (might be long)
DownloadBlockChain=Download fingerprints
@ -46,4 +46,5 @@ BlockedLogAreRequiredByYourCountryLegislation=Unalterable Logs module may be req
BlockedLogActivatedBecauseRequiredByYourCountryLegislation=Unalterable Logs module was activated because of the legislation of your country. Disabling this module may render any future transactions invalid with respect to the law and the use of legal software as they can not be validated by a tax audit.
BlockedLogDisableNotAllowedForCountry=List of countries where usage of this module is mandatory (just to prevent to disable the module by error, if your country is in this list, disable of module is not possible without editing this list first)
OnlyNonValid=Non valid
TooManyRecordToScanRestrictFilters=Too many record to scan/analyze. Please restrict list with more restrictive filters.
TooManyRecordToScanRestrictFilters=Too many record to scan/analyze. Please restrict list with more restrictive filters.
YearToExport=Year to export