diff --git a/htdocs/admin/perms.php b/htdocs/admin/perms.php index eb348869b34..d3880586f41 100644 --- a/htdocs/admin/perms.php +++ b/htdocs/admin/perms.php @@ -127,7 +127,7 @@ print info_admin(showModulesExludedForExternal($modules)).'
'."\n"; print '
'; print ''; -// 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" diff --git a/htdocs/blockedlog/admin/blockedlog_list.php b/htdocs/blockedlog/admin/blockedlog_list.php index 092a4167b17..f85b1c35a4e 100644 --- a/htdocs/blockedlog/admin/blockedlog_list.php +++ b/htdocs/blockedlog/admin/blockedlog_list.php @@ -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 ''; print '
'; -//print ' '.$langs->trans('ShowAllFingerPrintsMightBeTooLong').''; -//print ' | '.$langs->trans('ShowAllFingerPrintsErrorsMightBeTooLong').''; -print ' '.$langs->trans('DownloadLogCSV').''; +print $langs->trans("YearToExport").': '; +print ''; +print ''; +print ''; if (!empty($conf->global->BLOCKEDLOG_USE_REMOTE_AUTHORITY)) print ' | '.$langs->trans('DownloadBlockChain').''; print '
'; +print ''; + +print ''; print '
'; // You can use div-table-responsive-no-min if you dont need reserved height for your table -print ''; if ($optioncss != '') print ''; print ''; print ''; @@ -330,6 +388,9 @@ $array=array("1"=>$langs->trans("OnlyNonValid")); print $form->selectarray('search_showonlyerrors', $array, $search_showonlyerrors, 1); print ''; +// Status note +print '
'; + // Action column print ''; @@ -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 ''; // ID @@ -411,10 +474,11 @@ if (is_array($blocks)) print $form->textwithpicto(dol_trunc($block->signature, '12'), $block->signature); print ''; + // Status print ''; + + // Status note + print ''; + print ''; print ''; @@ -437,12 +510,11 @@ if (is_array($blocks)) print '
'; $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('', 0, $_SERVER["PHP_SELF"],'','',$param,'align="center"',$sortfield,$sortorder,'')."\n"; print '
'; 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 ''; + 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 '
'; -print ''; - print '
'; +print ''; - +// Javascript to manage the showinfo popup print ''."\n"; diff --git a/htdocs/blockedlog/ajax/block-info.php b/htdocs/blockedlog/ajax/block-info.php index 888f9385630..c8a9cbf70be 100644 --- a/htdocs/blockedlog/ajax/block-info.php +++ b/htdocs/blockedlog/ajax/block-info.php @@ -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 '
'; +print ''; + +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 ''; +print '
'.$langs->trans('Field').''.$langs->trans('Value').'
'; + + +$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.=''.($prefix?$prefix.' > ':'').$key.''; + $s.=''; + if (in_array($key, array('date','datef'))) + { + $s.=dol_print_date($val, 'dayhour'); + } + else + { + $s.=$val; + } + $s.=''; + } + elseif (is_array($val)) + { + $s.=formatObject($val, ($prefix?$prefix.' > ':'').$key); + } + elseif (is_object($val)) + { + $s.=formatObject($val, ($prefix?$prefix.' > ':'').$key); + } + } + } + + return $s; } diff --git a/htdocs/blockedlog/class/blockedlog.class.php b/htdocs/blockedlog/class/blockedlog.class.php index 639d9df909d..b4c0c8e6ef8 100644 --- a/htdocs/blockedlog/class/blockedlog.class.php +++ b/htdocs/blockedlog/class/blockedlog.class.php @@ -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)) { diff --git a/htdocs/core/lib/json.lib.php b/htdocs/core/lib/json.lib.php index 4d31308b4b3..61d6a923c1f 100644 --- a/htdocs/core/lib/json.lib.php +++ b/htdocs/core/lib/json.lib.php @@ -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 ! diff --git a/htdocs/langs/en_US/blockedlog.lang b/htdocs/langs/en_US/blockedlog.lang index bb6c9c155ee..b11e9194a8e 100644 --- a/htdocs/langs/en_US/blockedlog.lang +++ b/htdocs/langs/en_US/blockedlog.lang @@ -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. \ No newline at end of file +TooManyRecordToScanRestrictFilters=Too many record to scan/analyze. Please restrict list with more restrictive filters. +YearToExport=Year to export \ No newline at end of file