dolibarr 25.0.0-alpha
blockedlog_archives.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2017 ATM Consulting <contact@atm-consulting.fr>
3 * Copyright (C) 2017-2018 Laurent Destailleur <eldy@destailleur.fr>
4 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
5 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
28// Load Dolibarr environment
29require '../../main.inc.php';
40require_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
41require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php';
42require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
43require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
44require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
45require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
46require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
47
48// Load translation files required by the page
49$langs->loadLangs(array('admin', 'banks', 'bills', 'blockedlog', 'cashdesk', 'compta', 'other'));
50
51// Get Parameters
52$action = GETPOST('action', 'aZ09');
53$confirm = GETPOST('confirm', 'aZ09'); // Used by the actions_linkedfiles.inc.php
54$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : getDolDefaultContextPage(__FILE__); // To manage different context of search
55$backtopage = GETPOST('backtopage', 'alpha'); // Go back to a dedicated page
56$optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
57
58$withtab = GETPOSTISSET('withtab') ? GETPOSTINT('withtab') : 1;
59
60$search_showonlyerrors = GETPOSTINT('search_showonlyerrors');
61if ($search_showonlyerrors < 0) {
62 $search_showonlyerrors = 0;
63}
64
65$search_startyear = GETPOSTINT('search_startyear');
66$search_startmonth = GETPOSTINT('search_startmonth');
67$search_startday = GETPOSTINT('search_startday');
68$search_endyear = GETPOSTINT('search_endyear');
69$search_endmonth = GETPOSTINT('search_endmonth');
70$search_endday = GETPOSTINT('search_endday');
71$search_id = GETPOST('search_id', 'alpha');
72$search_fk_user = GETPOST('search_fk_user', 'intcomma');
73$search_start = -1;
74if (GETPOST('search_startyear') != '') {
75 $search_start = dol_mktime(0, 0, 0, $search_startmonth, $search_startday, $search_startyear);
76}
77$search_end = -1;
78if (GETPOST('search_endyear') != '') {
79 $search_end = dol_mktime(23, 59, 59, $search_endmonth, $search_endday, $search_endyear);
80}
81$search_code = GETPOST('search_code', 'array:alpha');
82$search_ref = GETPOST('search_ref', 'alpha');
83$search_amount = GETPOST('search_amount', 'alpha');
84$search_signature = GETPOST('search_signature', 'alpha');
85
86if (($search_start == -1 || empty($search_start)) && !GETPOSTISSET('search_startmonth') && !GETPOSTISSET('begin')) {
87 $search_start = dol_time_plus_duree(dol_now(), -1, 'w');
88 $tmparray = dol_getdate($search_start);
89 $search_startday = $tmparray['mday'];
90 $search_startmonth = $tmparray['mon'];
91 $search_startyear = $tmparray['year'];
92}
93
94// Load variable for pagination
95$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit;
96$sortfield = GETPOST('sortfield', 'aZ09comma');
97$sortorder = GETPOST('sortorder', 'aZ09comma');
98$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page");
99if (empty($page) || $page == -1) {
100 $page = 0;
101} // If $page is not defined, or '' or -1
102$offset = $limit * $page;
103$pageprev = $page - 1;
104$pagenext = $page + 1;
105
106if (empty($sortfield)) {
107 $sortfield = 'rowid';
108}
109if (empty($sortorder)) {
110 $sortorder = 'DESC';
111}
112
113$block_static = new BlockedLog($db);
114$block_static->loadTrackedEvents();
115
116// Access Control
117if (((!$user->admin && !$user->hasRight('blockedlog', 'read')) || !isModEnabled('blockedlog')) && !userIsTaxAuditor()) {
119}
120
121// We force also permission to write because it does not exists and we need it to upload a file
122$user->rights->blockedlog->create = 1;
123
124$result = restrictedArea($user, 'blockedlog', 0, '');
125
126// Execution Time
127$max_execution_time_for_importexport = getDolGlobalInt('EXPORT_MAX_EXECUTION_TIME', 300); // 5mn if not defined
128$max_time = @ini_get("max_execution_time");
129if ($max_time && $max_time < $max_execution_time_for_importexport) {
130 dol_syslog("max_execution_time=".$max_time." is lower than max_execution_time_for_importexport=".$max_execution_time_for_importexport.". We try to increase it dynamically.");
131 @ini_set("max_execution_time", $max_execution_time_for_importexport); // This work only if safe mode is off. also web servers has timeout of 300
132}
133
134$MAXLINES = getDolGlobalInt('BLOCKEDLOG_MAX_LINES', 10000);
135$MAXFORSHOWNLINKS = getDolGlobalInt('BLOCKEDLOG_MAX_FOR_SHOWN_LINKS', 100);
136
137$permission = $user->hasRight('blockedlog', 'read');
138$permissiontoadd = $user->hasRight('blockedlog', 'read'); // Permission is to upload new files to scan them
139$permtoedit = $permissiontoadd;
140
141$upload_dir = getMultidirOutput($block_static, 'blockedlog').'/archives';
142
143dol_mkdir($upload_dir);
144
145$fh = null;
146
147
148/*
149 * Actions
150 */
151
152// Purge search criteria
153if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers
154 $search_id = '';
155 $search_fk_user = '';
156 $search_start = -1;
157 $search_end = -1;
158 $search_code = array();
159 $search_ref = '';
160 $search_amount = '';
161 $search_signature = '';
162 $search_showonlyerrors = 0;
163 $search_startyear = '';
164 $search_startmonth = '';
165 $search_startday = '';
166 $search_endyear = '';
167 $search_endmonth = '';
168 $search_endday = '';
169 $toselect = array();
170 $search_array_options = array();
171}
172
173include DOL_DOCUMENT_ROOT.'/core/actions_linkedfiles.inc.php';
174
175if ($action == 'export' && $user->hasRight('blockedlog', 'read')) { // read is read/export for blockedlog
176 $error = 0;
177
178 $previoushash = '';
179 $firstid = '';
180 $periodnotcomplete = 0;
181
182 if (! (GETPOSTINT('yeartoexport') > 0)) {
183 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Year")), null, "errors");
184 $action = '';
185 $error++;
186 }
187 /*
188 if (empty($hmacexportkey)) {
189 setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Password")), null, "errors");
190 $error++;
191 }
192 */
193
194 if (!$error) {
195 // Refuse and cancel any trigger event if we are running a certified version without forcing https.
196 // This is a security requirement for certification. We do this check before any other to avoid any risk of logging an event that should be blocked because of non respect of certification rules.
197 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
198 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
199
200 $isqualified = isALNERunningVersion(1);
201 if ($isqualified && (defined('CERTIF_LNE') && (int) constant('CERTIF_LNE') == 1) && !isHTTPS() && !in_array($action, array('DOC_PREVIEW', 'DOC_DOWNLOAD'))) {
202 $errmsg = 'Error: You are using Dolibarr with the module to be compliant with the French Law Finance certification. In this version, the HTTPS is mandatory to be allowed to record any event (Your hosting does not match the install requirements).';
203 dol_syslog($errmsg, LOG_ERR);
204
205 setEventMessages($errmsg, null, 'errors');
206 $error++;
207 }
208 }
209
210 $dates = dol_get_first_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 1);
211 $datee = dol_get_last_day(GETPOSTINT('yeartoexport'), GETPOSTINT('monthtoexport') > 0 ? GETPOSTINT('monthtoexport') : 12);
212
213 if ($datee >= dol_now()) {
214 $periodnotcomplete = 1;
215 }
216
217 $suffixperiod = ($periodnotcomplete ? 'INCOMPLETE' : 'DONOTMODIFY');
218
219 if (!$error) {
220 // Get the ID of the first line qualified
221 $sql = "SELECT rowid";
222 $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog";
223 $sql .= " WHERE entity = ".((int) $conf->entity);
224 // For unalterable log, we are using the date of creation of the log. Note that a bookkeeper may decide to dispatch an invoice
225 // on different periods for example to manage depreciation.
226 $sql .= " AND date_creation BETWEEN '".$db->idate($dates, 'gmt')."' AND '".$db->idate($datee, 'gmt')."'";
227 $sql .= " ORDER BY date_creation ASC, rowid ASC"; // Required so we get the first one
228 $sql .= $db->plimit(1);
229
230 $res = $db->query($sql);
231 if ($res) {
232 // Make the first fetch to get first line and then get the previous hash.
233 $obj = $db->fetch_object($res);
234 if ($obj) {
235 $firstid = $obj->rowid;
236 $tmparray = $block_static->getPreviousHash(0, $firstid);
237 $previoushash = $tmparray['previoushash'];
238 } else { // If not data found for filter, we do not need previoushash neither firstid
239 $firstid = '';
240 $previoushash = 'nodata';
241 }
242 } else {
243 $error++;
244 setEventMessages($db->lasterror, null, 'errors');
245 }
246 }
247
248 // Define file name
249 $registrationnumber = getHashUniqueIdOfRegistration();
250 $secretkey = $registrationnumber;
251
252 $yearmonthtoexport = GETPOSTINT('yeartoexport').'-'.(GETPOSTINT('monthtoexport') > 0 ? sprintf("%02d", GETPOSTINT('monthtoexport')) : '');
253 $yearmonthdateofexport = dol_print_date(dol_now(), 'dayhourrfc', 'gmt');
254 $yearmonthdateofexportstandard = dol_print_date(dol_now(), 'dayhourlog', 'gmt');
255
256 $nameofdownoadedfile = "unalterable-log-archive-".$dolibarr_main_db_name."-".str_replace('-', '', $yearmonthtoexport).'-'.$yearmonthdateofexportstandard.'UTC-'.$suffixperiod.'.csv';
257
258 //$tmpfile = $conf->admin->dir_temp.'/unalterable-log-archive-tmp-'.$user->id.'.csv';
259 $tmpfileshort = 'blockedlog/archives/'.$nameofdownoadedfile;
260 $tmpfile = getMultidirOutput($block_static, 'blockedlog').'/archives/'.$nameofdownoadedfile;
261
262 $formatexport = 'VE2';
263
264 if (!$error) {
265 $fh = fopen($tmpfile, 'w');
266 if (empty($fh)) {
267 $error++;
268 setEventMessages('Failed to open file '.$tmpfileshort.' for writing.', null, 'errors');
269 }
270 }
271
272 if (!$error && $fh) {
273 $refinvoicefound = array();
274 $totalhtamount = array();
275 $totalvatamount = array();
276 $totalamount = array();
277
278 // Check we can validate the status of each line by getting the HMAC key in memory
279 $remoteobfuscationkey = '';
280 if (isALNERunningVersion(1) && $mysoc->country_code == 'FR') {
281 try {
282 $remoteobfuscationkey = $block_static->getObfuscationKey();
283 // Note: To emulate a pb in getting the obfuscation key, there is some code to uncomment into the method
284 } catch (Exception $e) {
285 $error++;
286
287 setEventMessages($e->getMessage(), null, 'errors');
288 setEventMessages('<a class="" href="'.$_SERVER["PHP_SELF"].'?clearcache=1">'.$langs->trans("Retry").'</a>', null, 'errors');
289 }
290 }
291
292 // Now restart request with all data, so without the limit(1) in sql request
293 $sql = "SELECT rowid, entity, date_creation, tms, user_fullname, action, module_source, pos_source, amounts_taxexcl, amounts, element, fk_object, date_object, ref_object,";
294 $sql .= " linktoref, linktype, signature, fk_user, object_data, object_version, object_format, debuginfo, note";
295 $sql .= " FROM ".MAIN_DB_PREFIX."blockedlog";
296 $sql .= " WHERE entity = ".((int) $conf->entity);
297 // For unalterable log, we are using the date of creation of the log. Note that a bookkeeper may decide to dispatch an invoice
298 // or payment on different periods for example to manage depreciation, but what we want here is not accountancy but payment data.
299 $sql .= " AND date_creation BETWEEN '".$db->idate($dates, 'gmt')."' AND '".$db->idate($datee, 'gmt')."'";
300 $sql .= " ORDER BY date_creation ASC, rowid ASC"; // Required so later we can use the parameter $previoushash of checkSignature()
301
302 $resql = $db->query($sql);
303 if ($resql) {
304 // Print line with title
305 fwrite($fh, "BEGIN - regnumber=".dol_trunc($registrationnumber, 10)." - date=".$yearmonthdateofexport." - period=".$yearmonthtoexport.($periodnotcomplete ? '-'.$suffixperiod : '')." - entity=".((int) $conf->entity)." - formatexport=".$formatexport." - user=".$user->getFullName($langs)
306 .';'.$langs->transnoentities('Id')
307 .';'.$langs->transnoentities('DateCreation')
308 .';'.$langs->transnoentities('Action')
309 .';'.$langs->transnoentities('Origin')
310 .';'.$langs->transnoentities('Terminal')
311 .';'.$langs->transnoentities('AmountHT')
312 .';'.$langs->transnoentities('AmountTTC')
313 .';'.$langs->transnoentities('Ref')
314 .';'.$langs->transnoentities('Date')
315 .';'.$langs->transnoentities('User')
316 .';'.$langs->transnoentities('LinkTo')
317 .';'.$langs->transnoentities('LinkType')
318 .';'.$langs->transnoentities('FullData')
319 .';'.$langs->transnoentities('Version') // Version Dolibarr, example 22.0.0
320 .';'.$langs->transnoentities('VersionSignature') // Rule used for fingerprint calculation
321 .';'.$langs->transnoentities('FingerprintDatabase') // Signature
322 .';'.$langs->transnoentities('Status')
323 //.';'.$langs->transnoentities('FingerprintExport')
324 );
325
326 $loweridinerror = 0;
327 $lastrowid = 0;
328 $i = 0;
329
330 while ($obj = $db->fetch_object($resql)) {
331 // We set here all data used into signature calculation (see checkSignature method) and more
332
333 // IMPORTANT: We must have here, the same rule for transformation of data than into
334 // the blockedlog->fetch() method (db->jdate for date, ...)
335
336 $block_static->id = $obj->rowid;
337 $block_static->entity = $obj->entity;
338
339 if ($i == 0) {
340 $tmparray = $block_static->getPreviousHash(0, $block_static->id);
341 $previoushash = $tmparray['previoushash'];
342
343 fwrite($fh, ";NOTE - previoushash=".$previoushash."\n");
344 }
345
346 $tz = 'gmt';
347 if (empty($obj->object_format) || $obj->object_format == 'V1') {
348 $tz = 'tzserver';
349 }
350
351 $block_static->date_creation = $db->jdate($obj->date_creation, $tz); // jdate(date_creation) is UTC
352 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
353 $block_static->date_modification = $db->jdate($obj->tms, $tz); // jdate(tms) is UTC
354
355 $block_static->action = $obj->action;
356 $block_static->module_source = $obj->module_source;
357 $block_static->pos_source = $obj->pos_source;
358
359 $block_static->amounts_taxexcl = is_null($obj->amounts_taxexcl) ? null : (float) $obj->amounts_taxexcl; // Database store value with 8 digits, we cut ending 0 them with (flow)
360 $block_static->amounts = (float) $obj->amounts; // Database store value with 8 digits, we cut ending 0 them with (flow)
361
362 $block_static->fk_object = $obj->fk_object; // Not in signature
363 $block_static->date_object = $db->jdate($obj->date_object, $tz); // jdate(date_object) is UTC
364 $block_static->ref_object = $obj->ref_object;
365
366 $block_static->linktoref = $obj->linktoref;
367 $block_static->linktype = $obj->linktype;
368
369 $block_static->fk_user = $obj->fk_user; // Not in signature
370 $block_static->user_fullname = $obj->user_fullname;
371
372 $block_static->object_data = $block_static->dolDecodeBlockedData($obj->object_data);
373
374 $block_static->note = $obj->note;
375
376 // Old hash + Previous fields concatenated = signature
377 $block_static->signature = $obj->signature;
378
379 $block_static->element = $obj->element; // Not in signature
380
381 $block_static->object_version = $obj->object_version; // Not in signature
382 $block_static->object_format = $obj->object_format; // Not in signature
383
384 $block_static->certified = ($obj->certified == 1); // Not in signature
385
386 //$block_static->debuginfo = $obj->debuginfo;
387
388 //var_dump($block->id.' '.$block->signature, $block->object_data);
389
390 $checksignature = $block_static->checkSignature($previoushash); // If $previoushash is not defined, checkSignature will search it from $block_static->id
391
392 // To see detail to get signature
393 /*
394 if ($block_static->id == 411) {
395 $concatenateddata = $block_static->buildKeyForSignature($block_static->object_format);
396
397 $tmparray = $block_static->checkSignature($previoushash, 2);
398 var_dump($previoushash, $concatenateddata, $tmparray);
399 exit;
400 }
401 */
402
403 if ($checksignature) {
404 if (!empty($block_static->note)) { // signature ok but end of chain deletion detected
405 $statusofrecord = 'KO';
406 $statusofrecordnote = $block_static->note;
407 } else {
408 $statusofrecord = $langs->transnoentitiesnoconv('StatusValid');
409 }
410 if ($loweridinerror > 0) {
411 $statusofrecordnote = 'ValidButFoundAPreviousKO';
412 } else {
413 $statusofrecordnote = '';
414 }
415 } else {
416 $statusofrecord = 'KO';
417 $statusofrecordnote = 'LineCorruptedOrNotMatchingPreviousOne';
418 $loweridinerror = $obj->rowid;
419 }
420
421 if ($i == 0) {
422 $statusofrecordnote = $langs->trans("PreviousFingerprint").': '.$previoushash.($statusofrecordnote ? ' - '.$statusofrecordnote : '');
423 }
424
425 //$concatenateddata = $block_static->buildKeyForSignature();
426
427 $lastrowid = $block_static->id;
428
429 // Define $totalhtamount, $totalvatamount, $totalamount for $block->action event / $block->module_source
430 $total_ht = $total_vat = $total_ttc = 0;
431 sumAmountsForUnalterableEvent($block_static, $refinvoicefound, $totalhtamount, $totalvatamount, $totalamount, $total_ht, $total_vat, $total_ttc);
432
433 fwrite($fh, ";"
434 .csvClean($block_static->id).';'
435 .csvClean(dol_print_date($block_static->date_creation, 'standard', 'gmt')).';'
436 .csvClean($block_static->action).';'
437 .csvClean($block_static->module_source).';'
438 .csvClean($block_static->pos_source).';'
439 .csvClean($block_static->amounts_taxexcl).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1
440 .csvClean($block_static->amounts).';' // Can be 1.20000000 with 8 digits. TODO Clean to have 8 digits in V1
441 .csvClean($block_static->ref_object).';'
442 .csvClean(dol_print_date($block_static->date_object, 'standard', 'gmt')).';'
443 .csvClean($block_static->user_fullname).';'
444 .csvClean($block_static->linktoref).';'
445 .csvClean($block_static->linktype).';'
446 .csvClean($obj->object_data).';' // We must use the string (so $obj->object_data) and not the array decoded with dolDecodeBlockedData
447 .csvClean($block_static->object_version).';'
448 .csvClean($block_static->object_format).';'
449 .csvClean($block_static->signature).';'
450 .csvClean($statusofrecord).';'
451 .csvClean($block_static->note).';'
452 ."\n");
453
454 // Set new previous hash for next fetch
455 $previoushash = $obj->signature;
456
457 $i++;
458 } // end of loop on each record found
459
460 if ($i == 0) {
461 fwrite($fh, ";\n");
462 }
463
464 // Get the last record of the chain (may be used later).
465 $lastrecord = $block_static->getLastRecord();
466
467 // Check if $lastrowid is the last rowid of table blockedlog
468 $isLastRecord = false;
469 if ($lastrecord['id'] == $lastrowid) {
470 $isLastRecord = true;
471 }
472
473 if (!$periodnotcomplete) { // If period is complete
474 if (!$isLastRecord) { // There is another record in database that follow the last one in period. So we use it to see if no deletion were done at end of period.
475 // We check that next record after the last one is ok.
476 $block_static_after = new BlockedLog($db);
477 $nextrecord = $block_static_after->getNextRecord($lastrowid);
478 $nextrecordid = $nextrecord['id'];
479
480 $block_static_after->fetch($nextrecordid);
481
482 $checksignature = $block_static_after->checkSignature();
483
484 if (!$checksignature) { // If the record just after is not valid, it means we removed one or more records inside the chain (at end of period)
485 fwrite($fh, 'ERROR '.$langs->trans("ErrorEndOfChainRecordWasRemoved", str_replace(array('T', 'Z'), ' ', dol_print_date($block_static->date_creation, 'dayhourrfc', 'gmt')), str_replace(array('T', 'Z'), ' ', dol_print_date($block_static_after->date_creation, 'dayhourrfc', 'gmt')))."\n");
486 }
487 } else { // If last output record is the last one in chain, we must use the end of chain protection file to check that no deletion were done in database before export.
488 $lockfile = $block_static->getEndOfChainFlagFile();
489 $lockline = '';
490
491 // Note: We can find a similar code into the blockedlog_list.php page to make the report on screen.
492 if (defined('BLOCKEDLOG_END_FLAG_IN_A_FILE')) {
493 if (!file_exists($lockfile)) {
494 //$error++;
495
496 if ($mysoc->country_code == 'FR') {
497 fwrite($fh, 'ERROR '.$langs->trans("ErrorEndOfChainFlagWasRemoved")."\n");
498 } else {
499 fwrite($fh, 'WARNING '.$langs->trans("WarningNoProtectionOnEndOfChain")."\n");
500 }
501 } else {
502 $lockline = trim(file_get_contents($lockfile));
503 }
504 } else {
505 $sql = "SELECT value from ".MAIN_DB_PREFIX."const";
506 $sql .= " WHERE name = '".$db->escape(basename($lockfile))."' AND entity = ".((int) $conf->entity);
507 $resql = $db->query($sql);
508 if ($resql) {
509 $obj = $db->fetch_object($resql);
510 if ($obj) {
511 $lockline = $obj->value;
512 } else {
513 //$error++;
514
515 if ($mysoc->country_code == 'FR') {
516 fwrite($fh, 'ERROR '.$langs->trans("ErrorEndOfChainFlagWasRemoved")."\n");
517 } else {
518 fwrite($fh, 'WARNING '.$langs->trans("WarningNoProtectionOnEndOfChain")."\n");
519 }
520 }
521 }
522 }
523
524 if (! $error) {
525 $headstring = '';
526 if (preg_match('/^dolcrypt/', $lockline)) {
527 $headstring = dolDecrypt($lockline, '', 'BLOCKEDLOGHEAD');
528 } elseif (preg_match('/^dolobfuscation/', $lockline)) {
529 try {
530 $remoteobfuscationkey = $block_static->getObfuscationKey();
531 if (empty($remoteobfuscationkey)) {
532 throw new Exception('Remote obfuscation key is empty');
533 }
534 } catch (Exception $e) {
535 $error++;
536
537 $url_for_ping = getDolGlobalString('MAIN_URL_FOR_PING', "https://ping.dolibarr.org/");
538 setEventMessages($langs->trans("FailedToGetRemoteObfuscationKeyReTryLater", $url_for_ping), null, 'errors');
539 setEventMessages($langs->trans("CantValidateEndOfChain"), null, 'errors');
540 }
541 $headstring = dolDecrypt($lockline, $remoteobfuscationkey, 'BLOCKEDLOGHEAD');
542 }
543
544 $reg = array();
545 if (preg_match('/^BLOCKEDLOGHEAD (\d+) ([^\s]+) ([a-zA-Z0-9\-]+)/', (string) $headstring, $reg)) { // Failed to decypt the head
546 // Compare with last line
547 $lastrecordid = $lastrecord['id'];
548 $lastrecorddate = $lastrecord['date'];
549 $lastrecordsignature = $lastrecord['signature'];
550
551 if ($reg[1] > $lastrecordid || $reg[3] != $lastrecordsignature) {
552 //$error++;
553
554 // Check that last line is the one declared into the head flag. If not, it means some record were deleted at end of chain.
555 fwrite($fh, $langs->trans("ErrorEndOfChainRecordWasRemoved", str_replace(array('T', 'Z'), ' ', dol_print_date($lastrecorddate, 'dayhourrfc', 'gmt')), str_replace(array('T', 'Z'), ' ', $reg[2]))."\n");
556 }
557 } else {
558 //$error++;
559
560 fwrite($fh, $langs->trans("FailedToDecodeTheHeadFlagEndOfChainIsNotReliable")."\n");
561 }
562 }
563 }
564 }
565 } else {
566 $error++;
567 setEventMessages($db->lasterror, null, 'errors');
568 }
569
570
571 // Define which source we want to show
572 $showtotalfor = array('' => 1);
573 $sql = "SELECT DISTINCT module_source FROM ".MAIN_DB_PREFIX."blockedlog WHERE entity = ".((int) $conf->entity);
574 $resql = $db->query($sql);
575 if ($resql) {
576 while ($obj = $db->fetch_object($resql)) {
577 $showtotalfor[$obj->module_source] = 1;
578 }
579 }
580 if (isModEnabled('takepos')) {
581 $showtotalfor['takepos'] = 1;
582 }
583
584 ksort($totalamount);
585 krsort($showtotalfor);
586
587 // Now calculate cumulative total of all invoices validated
588 foreach ($totalamount as $key => $totalamountofcodepersource) {
589 foreach ($totalamountofcodepersource as $source => $tmpval) {
590 $totalhtamountalllines = array('BILL_VALIDATE' => array(), 'PAYMENT_CUSTOMER' => array());
591 $totalvatamountalllines = array('BILL_VALIDATE' => array(), 'PAYMENT_CUSTOMER' => array());
592 $totalamountalllines = array('BILL_VALIDATE' => array(), 'PAYMENT_CUSTOMER' => array());
593 }
594 }
595 //var_dump($totalamount, $totalhtamount, $totalvatamount); exit;
596
597 $countsource = 0;
598 foreach ($showtotalfor as $source => $tmpval) {
599 $countsource++;
600
601 // Line of title for total for period for $source
602 if ($countsource == 1) {
603 fwrite($fh, "\n");
604 }
605 fwrite($fh, '----- ');
606 fwrite($fh, $langs->transnoentitiesnoconv("TotalForThePeriod"));
607 fwrite($fh, ' - '.($source ? $langs->transnoentitiesnoconv("PointOfSale").' '.ucfirst($source) : $langs->transnoentitiesnoconv("BackOffice")));
608 fwrite($fh, ' ('.$yearmonthtoexport.($periodnotcomplete ? '-'.$suffixperiod : '').')');
609 fwrite($fh, ' -----');
610 fwrite($fh, "\n");
611
612 foreach ($totalamount as $actioncode => $totalamountofcodepersource) {
613 $amountstoshow = '';
614 $s = $actioncode;
615 if ($actioncode == 'BILL_VALIDATE') {
616 $s = 'BILLED = '.$langs->transnoentitiesnoconv("Turnover");
617 $amountstoshow = (float) $totalhtamount['BILL_VALIDATE'][$source].' '.$langs->transnoentitiesnoconv("HT").' - '.(float) $totalvatamount['BILL_VALIDATE'][$source].' '.$langs->transnoentitiesnoconv("VAT").' - '.(float) $totalamount['BILL_VALIDATE'][$source].' '.$langs->transnoentitiesnoconv("TTC");
618 } elseif ($actioncode == 'PAYMENT_CUSTOMER') {
619 $s = 'PAID = '.$langs->transnoentitiesnoconv("TurnoverCollected");
620 $amountstoshow = (float) $totalamount['PAYMENT_CUSTOMER'][$source];
621 }
622
623 fwrite($fh, 'SUMMARY PERIOD '.$s.' = '.$amountstoshow."\n");
624 }
625 }
626
627
628 // Get lifetime amount of all invoices validated and payments created/deleted.
629 // We do not use $totalamountalllines because it is only for the period, but we want lifetime amount since the first record to now.
630
631 $totalamountlifetime = array('BILL_VALIDATE' => array(), 'PAYMENT_CUSTOMER_CREATE' => array(), 'PAYMENT_CUSTOMER_DELETE' => array());
632 $totalhtamountlifetime = array('BILL_VALIDATE' => array(), 'PAYMENT_CUSTOMER_CREATE' => array(), 'PAYMENT_CUSTOMER_DELETE' => array());
633
634 $foundoldformat = 0;
635 $firstrecorddate = 0;
636 global $foundoldformat, $firstrecorddate;
637 include DOL_DOCUMENT_ROOT.'/blockedlog/admin/lifetimeamount.inc.php';
638 '@phan-var-force array<string,array<string,float>> $totalamountlifetime';
639 '@phan-var-force array<string,array<string,float>> $totalhtamountlifetime';
640
641 $countsource = 0;
642 foreach ($showtotalfor as $source => $tmpval) {
643 $countsource++;
644
645 // Line of title for lifetime total for $source
646 if ($countsource == 1) {
647 fwrite($fh, "\n");
648 }
649 fwrite($fh, '----- ');
650 fwrite($fh, $langs->transnoentitiesnoconv("TotalForLifetime"));
651 fwrite($fh, ' - '.($source ? $langs->transnoentitiesnoconv("PointOfSale").' '.ucfirst($source) : $langs->transnoentitiesnoconv("BackOffice")));
652 fwrite($fh, ' (>='.dol_print_date($firstrecorddate, 'standard').')');
653 fwrite($fh, "\n");
654
655 foreach ($totalamount as $actioncode => $totalamountofcodepersource) {
656 $amountstoshow = '';
657 $s = '';
658 if ($actioncode == 'BILL_VALIDATE') {
659 $s = 'BILLED = '.$langs->transnoentitiesnoconv("Turnover");
660 $amountstoshow = $totalhtamountlifetime['BILL_VALIDATE'][$source].' '.$langs->transnoentitiesnoconv("HT")." - ".($foundoldformat ? '' : ((float) $totalamountlifetime['BILL_VALIDATE'][$source] - (float) $totalhtamountlifetime['BILL_VALIDATE'][$source]).' '.$langs->transnoentitiesnoconv("VAT")).' - '.$totalamountlifetime['BILL_VALIDATE'][$source].' '.$langs->transnoentitiesnoconv("TTC");
661 } elseif ($actioncode == 'PAYMENT_CUSTOMER') {
662 $s = 'PAID = '.$langs->transnoentitiesnoconv("TurnoverCollected");
663 $amountstoshow = ((float) $totalamountlifetime['PAYMENT_CUSTOMER_CREATE'][$source] + (float) $totalamountlifetime['PAYMENT_CUSTOMER_DELETE'][$source]);
664 }
665
666 // Add a final line with perpetual total for invoice validations
667 fwrite($fh, 'SUMMARY LIFETIME '.$s.' = '.$amountstoshow."\n");
668 }
669 }
670
671 fwrite($fh, "\n");
672
673 // End of file, we will calculate global signature on it now, before adding last line.
674 fclose($fh);
675
676 // Calculate the signature of the file (the last line has a return line)
677 $algo = 'sha256';
678 $sha256 = hash_file($algo, $tmpfile); // For integrity only
679 $hmacsha256 = hash_hmac_file($algo, $tmpfile, $secretkey); // For integrity + authenticity
680
681 // Now add a signature to check integrity at end of file
682 file_put_contents($tmpfile, 'END - sha256='.$sha256.' - hmac_sha256='.$hmacsha256, FILE_APPEND);
683 dolChmod($tmpfile);
684
685
686 if (!$error) {
687 if ($periodnotcomplete) {
688 setEventMessages($langs->trans("ErrorPeriodMustBePastToAllowExport"), null, "warnings");
689 } else {
690 // We record the export as a new line into the unalterable logs
691 require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php';
692 $b = new BlockedLog($db);
693
694 $object = new stdClass();
695 $object->id = 0;
696 $object->element = 'module';
697 $object->ref = 'systemevent';
698 $object->entity = $conf->entity;
699 $object->date = dol_now();
700 $object->fullname = $user->getFullName($langs);
701
702 $object->label = 'Export unalterable logs';
703
704 $object->period = 'year='.GETPOSTINT('yeartoexport').(GETPOSTINT('monthtoexport') ? ' month='.GETPOSTINT('monthtoexport') : '');
705
706 // There is no trigger for export of archive files, so we force the action code here
707
708 $action = 'BLOCKEDLOG_EXPORT';
709
710 $result = $b->setObjectData($object, $action, 0, $user, 0);
711
712 if ($result < 0) {
713 setEventMessages('Failed to insert the export into the unalterable log. Export canceled: '.$b->error, null, 'errors');
714 dol_delete_file($tmpfile);
715 $error++;
716 }
717
718 $res = $b->create($user);
719
720 if ($res < 0) {
721 setEventMessages('Failed to insert the export into the unalterable log. Export canceled: '.$b->error, null, 'errors');
722 dol_delete_file($tmpfile);
723 $error++;
724 }
725 }
726 } else {
727 dol_delete_file($tmpfile);
728 }
729 }
730
731 if (!$error && $fh) {
732 setEventMessages($langs->trans("FileGenerated"), null);
733 }
734
735 $action = '';
736}
737
738
739/*
740 * View
741 */
742
743$form = new Form($db);
744$formother = new FormOther($db);
745
746if ($withtab && !userIsTaxAuditor()) {
747 $title = $langs->trans("ModuleSetup").' '.$langs->trans('BlockedLog');
748} else {
749 $title = $langs->trans("BrowseBlockedLog");
750}
751$help_url = "EN:Module_Unalterable_Archives_-_Logs|FR:Module_Archives_-_Logs_Inaltérable";
752
753llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'bodyforlist mod-blockedlog page-admin_blockedlog_list');
754
755$blocks = $block_static->getLog('all', (int) $search_id, $MAXLINES, $sortfield, $sortorder, (int) $search_fk_user, $search_start, $search_end, $search_ref, $search_amount, $search_code, $search_signature);
756if (!is_array($blocks)) {
757 if ($blocks == -2) {
758 setEventMessages($langs->trans("TooManyRecordToScanRestrictFilters", $MAXLINES), null, 'errors');
759 } else {
760 dol_print_error($block_static->db, $block_static->error, $block_static->errors);
761 exit;
762 }
763}
764
765$linkback = '';
766if ($withtab && !userIsTaxAuditor()) {
767 $linkback = '<a href="'.dolBuildUrl($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php', ['restore_lastsearch_values' => 1]).'">'.img_picto($langs->trans("BackToModuleList"), 'back', 'class="pictofixedwidth"').'<span class="hideonsmartphone">'.$langs->trans("BackToModuleList").'</span></a>';
768}
769
770$morehtmlcenter = '';
771$texttop = '';
772
773$registrationnumber = getHashUniqueIdOfRegistration();
774if (!userIsTaxAuditor()) {
775 $texttop = '<small class="opacitymedium">'.$langs->trans("RegistrationNumber").':</small> <small>'.dol_trunc($registrationnumber, 10).'</small>';
777 $texttop = '';
778 }
779}
780
781print load_fiche_titre($title.'<br>'.$texttop, $linkback, 'blockedlog', 0, '', '', $morehtmlcenter);
782
783$head = blockedlogadmin_prepare_head($withtab);
784
785print dol_get_fiche_head($head, 'archives', '', -1);
786
787//print $texttop;
788//print '<br><br>';
789
790print '<div class="opacitymedium hideonsmartphone justify">';
791if (!userIsTaxAuditor()) {
792 print $langs->trans("ArchivesDesc")."<br>";
793} else {
794 print $langs->trans("ArchivesAuditorDesc")."<br>";
795}
796print "</div>\n";
797
798
799if ($action == 'check' || $action == 'checkconfirmed') {
800 print '<br>';
801
802 print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
803 print '<input type="hidden" name="urlfile" value="'.GETPOST('urlfile').'">';
804 print '<input type="hidden" name="action" value="checkconfirmed">';
805 print '<input type="hidden" name="token" value="'.newToken().'">';
806
807 print '<div class="neutral">';
808
809 print '<b>'.$langs->trans("File").'</b> : '.GETPOST('urlfile').'<br>';
810
811 $fullpath = $upload_dir.'/'.GETPOST('urlfile');
812
813 $handle = fopen($fullpath, "r");
814 $line = fgets($handle);
815 fclose($handle);
816
817 $reg = array();
818 $period = '';
819 $regnumber = '';
820 $formatexport = '';
821 $fileentity = 1;
822 if (preg_match('/\speriod=([^\s]+)/', $line, $reg)) {
823 $period = $reg[1]; // Get period on first line
824 }
825 if (preg_match('/\sregnumber=([^\s]+)/', $line, $reg)) {
826 $regnumber = str_replace(array('.', '…'), '', $reg[1]); // Get period on first line
827 }
828 if (preg_match('/\sformatexport=([^\s]+)/', $line, $reg)) {
829 $formatexport = $reg[1]; // Get export format (VE1, VE2...)
830 }
831 if (preg_match('/\sentity=([^\s]+)/', $line, $reg)) {
832 $fileentity = $reg[1]; // Get entity of file (usually 1)
833 }
834 print '<b>'.$langs->trans("Period").'</b> : '.$period.'<br>';
835
836 print '<br>';
837
838
839 $registrationnumber = getHashUniqueIdOfRegistration();
840 $secretkey = $registrationnumber;
841 $inputregistrationnumber = '';
842
843 // Prepare to create a temporary file
844 $fullpathtmp = $upload_dir.'/temp/'.GETPOST('urlfile').'.tmp';
845
846 dol_mkdir($upload_dir.'/temp');
847 $result = dol_copy($fullpath, $fullpathtmp);
848
849 // Remove the last line of text file
850 removeLastLine($fullpathtmp);
851
852 print $langs->trans("FileHasBeenEncodedWithASecretKeyStartingWith").' : '.$regnumber.'...<br>';
853 if (preg_match('/^'.$regnumber.'/', $secretkey) && !userIsTaxAuditor()) {
854 print 'As this matches the 10 first characters of the full registration number of this instance, we will use the full registration number to control the archive file...';
855 } else {
856 $secretkey = ''; // The local registration number does not match the one of the archive file, so we won't use the local number to check authenticity.
857
858 if (GETPOST('inputregistrationnumber')) {
859 $inputregistrationnumber = GETPOST('inputregistrationnumber');
860 print $langs->trans("WeWillUseThisValueAsNumber");
861 print '<input type="text" name="inputregistrationnumber" class="width300" placeholder="'.$langs->trans("FullRegistrationNumber").'" value="'.$inputregistrationnumber.'" spellcheck="false">';
862
863 $secretkey = $inputregistrationnumber; // We will use the entered value to check authenticity
864 } else {
865 if (!userIsTaxAuditor() || !isModEnabled('captureserver')) { // @phan-suppress-current-line UnknownModuleName
866 print 'This archive file was not generated by this instance.';
867 print 'The control of authenticity is possible only if you know the full registration number. ';
868 $inputregistrationnumber = '';
869
870 print $langs->trans("PleaseEnterFullRegistrationNumber").' ';
871 } else {
872 // Here module "captureserver" is on. We can search the full registration number and prefill value
873 $sql = "SELECT registerid from ".MAIN_DB_PREFIX."captureserver_captureserver";
874 $sql .= " WHERE registerid LIKE '".$db->escape($regnumber)."%'";
875 $sql .= " AND type = 'dolibarrregistration'";
876 $sql .= " LIMIT 1";
877
878 $resql = $db->query($sql);
879 if ($resql) {
880 $obj = $db->fetch_object($resql);
881 if ($obj) {
882 $inputregistrationnumber = $obj->registerid;
883 }
884 }
885
886 if ($inputregistrationnumber) {
887 print $langs->trans("WeFoundThisFullRegistrationNumberForThisKey").' ';
888 } else {
889 print $langs->trans("WeDidNotFindThisFullRegistrationNumberForThisKey").'.<br>';
890 print $langs->trans("PleaseEnterFullRegistrationNumber").' ';
891 }
892 }
893 print '<input type="text" name="inputregistrationnumber" class="width300" placeholder="'.$langs->trans("FullRegistrationNumber").'" value="'.$inputregistrationnumber.'" spellcheck="false">';
894 }
895 }
896 print '<br><br>';
897
898 print '<center>';
899 print '<input type="submit" class="button small" name="submit" value="'.$langs->trans("ControlFile").'">';
900 print '</center>';
901
902 print '</div>';
903
904 print '</form>';
905
906
907 if ($action == 'checkconfirmed') {
908 $totalhtamountforaction = $totalvatamountforaction = $totalamountforaction = array(
909 'BILL_VALIDATE' => 0,
910 'PAYMENT_CUSTOMER' => 0 // PAYMENT_CUSTOMER_CREATE + PAYMENT_CUSTOMER_DELETE
911 );
912 $reg = array();
913 $refinvoicefound = array();
914 $recalculatedhashsign = '';
915 $recalculatedhashauth = '';
916 $hashsign = '';
917 $hashauth = '';
918 $algosign = '';
919 $algoauth = '';
920 $previoushash = '';
921 $nbLinesModifiedInExportButKo = 0;
922 $nbLinesModifiedBeforeExport = 0;
923
924 $amounthtlifetime = array();
925 $amountvatlifetime = array();
926 $amountttclifetime = array();
927 $amounthtlifetime['BILL_VALIDATE'] = $amounthtlifetime['PAYMENT_CUSTOMER'] = null;
928 $amountvatlifetime['BILL_VALIDATE'] = $amountvatlifetime['PAYMENT_CUSTOMER'] = null;
929 $amountttclifetime['BILL_VALIDATE'] = $amountttclifetime['PAYMENT_CUSTOMER'] = null;
930
931 $footer = '';
932 $errorlines = '';
933
934 $handle = fopen($fullpath, "r");
935 if ($handle) {
936 $numline = 0;
937
938 $block_static = new BlockedLog($db);
939
940 while ($line = fgetcsv($handle, 100000, ';', '"', '')) {
941 $numline++;
942 $lineanalyzed = 0;
943
944 $linetech = $lineactioncode = '';
945 $lineamountht = $lineamountttc = 0;
946 $lineref = '';
947 $statusline = '';
948
949 if ($numline < 2) {
950 // First line, we continue
951 continue;
952 }
953
954 $block_static->id = 0; // reset tmp record
955
956 // Test if line is an empty line
957 if (trim((string) $line[0]) == '' && trim((string) $line[1]) == '') {
958 $lineanalyzed = 2;
959 }
960
961 // Test if line is an comment line
962 if (preg_match('/^-----/', (string) $line[0])) {
963 $lineanalyzed = 2;
964 }
965
966 // Test if line is an error alert line
967 if (preg_match('/^ERROR/', (string) $line[0])) {
968 $lineanalyzed = 3;
969 }
970
971 if (empty($lineanalyzed) && $formatexport == 'VE1' && !empty($line[1])) { // Format Blockedlog V1-
972 $lineanalyzed = 1;
973 $linetech = $line[0];
974
975 $block_static->id = (int) $line[1];
976 $block_static->entity = (int) $fileentity;
977 $block_static->date_creation = (string) $line[2];
978 $block_static->action = $lineactioncode = (string) $line[3];
979 $block_static->module_source = (string) $line[4];
980 $block_static->pos_source = '';
981 $block_static->amounts_taxexcl = $lineamountht = ($line[5] === '' ? null : (float) $line[5]);
982 $block_static->amounts = $lineamountttc = (float) $line[6];
983 $block_static->ref_object = $lineref = (string) $line[7];
984 $block_static->date_object = (int) $line[8];
985 $block_static->user_fullname = (string) $line[9];
986 $block_static->linktoref = (string) $line[10];
987 $block_static->linktype = (string) $line[11];
988 $block_static->object_data = json_decode((string) $line[12]);
989 $block_static->object_version = (string) $line[13];
990 $block_static->object_format = (string) $line[14];
991 $block_static->signature = (string) $line[15];
992
993 // Status from file: 'Valid' or 'KO'
994 $statusline = (string) $line[16];
995 }
996
997 if (empty($lineanalyzed) && $formatexport == 'VE2' && !empty($line[1])) { // Format Blockedlog V2+
998 $lineanalyzed = 1;
999 $linetech = $line[0];
1000
1001 $block_static->id = (int) $line[1];
1002 $block_static->entity = (int) $fileentity;
1003 $block_static->date_creation = (string) $line[2];
1004 $block_static->action = $lineactioncode = (string) $line[3];
1005 $block_static->module_source = (string) $line[4];
1006 $block_static->pos_source = (string) $line[5];
1007 $block_static->amounts_taxexcl = $lineamountht = ($line[6] === '' ? null : (float) $line[6]);
1008 $block_static->amounts = $lineamountttc = (float) $line[7];
1009 $block_static->ref_object = $lineref = (string) $line[8];
1010 $block_static->date_object = (int) $line[9];
1011 $block_static->user_fullname = (string) $line[10];
1012 $block_static->linktoref = (string) $line[11];
1013 $block_static->linktype = (string) $line[12];
1014 $block_static->object_data = json_decode((string) $line[13]);
1015 $block_static->object_version = (string) $line[14];
1016 $block_static->object_format = (string) $line[15];
1017 $block_static->signature = (string) $line[16];
1018
1019 // Status from file: 'Valid' or 'KO'
1020 $statusline = (string) $line[17];
1021 }
1022
1023 if ($block_static->id > 0) {
1024 // Status revalidated from calculation using the HMAC secret key (possible only when we are on the same instance than
1025 // the one hosting the initial database of the archive)
1026 $tmp = $block_static->checkSignature($previoushash, 2);
1027
1028 // To see the detail of line to to test signature calculation
1029 /*
1030 if ($block_static->id == 411) {
1031 $concatenateddata = $block_static->buildKeyForSignature($block_static->object_format);
1032 if (empty($previoushash)) {
1033 $tmparray = $block_static->getPreviousHash(0, $block_static->id);
1034 $previoushash = $tmparray['previoushash'];
1035 }
1036 var_dump($block_static->id, $previoushash, $concatenateddata, $tmp);
1037 exit;
1038 }*/
1039
1040 $signature = $tmp['calculatedsignature'];
1041 $previoushash = $block_static->signature;
1042
1043 //print 'Line '.$numline.': Recalculate from file: '.$signature.', in file '.$block_static->signature."<br>\n";
1044 if ($statusline == 'Valid') {
1045 // The signature calculated must match the recorded signature
1046 if ($signature != $block_static->signature) {
1047 $nbLinesModifiedInExportButKo++;
1048 //print 'Error: Line '.$numline.' reports that signature is ok but it seems to not match the one recalculated.';
1049 }
1050 }
1051 if ($statusline == 'KO') {
1052 $nbLinesModifiedBeforeExport++;
1053 //print 'The line '.$numline.' was modified into the Unalterable Log before being exported.';
1054 }
1055
1056 // Note: this field is not more used, it has been replacedwith a global signature on all the file
1057 /*
1058 try {
1059 $concatenateddata = $block_static->buildKeyForSignature();
1060 $signatureexport = dol_hash($previoushashexport.$concatenateddata, 'sha256');
1061 $previoushashexport = $signatureexport;
1062 } catch(Exception $e) {
1063 setEventMessages('Bad format for line '.$numline.'. '.$e->getMessage(), null, 'errors');
1064 break;
1065 }
1066 */
1067 }
1068
1069 if ($lineanalyzed == 1 && ($lineactioncode == 'BILL_VALIDATE' || $lineactioncode == 'PAYMENT_CUSTOMER_CREATE' || $lineactioncode == 'PAYMENT_CUSTOMER_DELETE')) {
1070 // For action = BILL_VALIDATE, we keep only first invoice found, but this should not happen because edition of invoice is never possible on
1071 // certified version (locked) and very difficult on other version.
1072 if ($lineactioncode != 'BILL_VALIDATE' || empty($refinvoicefound[$lineref])) {
1073 if ($lineactioncode == 'PAYMENT_CUSTOMER_CREATE' || $lineactioncode == 'PAYMENT_CUSTOMER_DELETE') {
1074 $lineactioncode = 'PAYMENT_CUSTOMER';
1075 }
1076
1077 $totalhtamountforaction[$lineactioncode] += $lineamountht;
1078 $totalvatamountforaction[$lineactioncode] += ($lineamountttc - $lineamountht);
1079 $totalamountforaction[$lineactioncode] += $lineamountttc;
1080 }
1081 if ($lineactioncode == 'BILL_VALIDATE') {
1082 $refinvoicefound[$lineref] = 1;
1083 }
1084 }
1085
1086 // Test if line is a summary line
1087 if (preg_match('/^SUMMARY /', (string) $line[0])) {
1088 // We are on a line for summary information
1089 $lineanalyzed = 2;
1090 }
1091
1092 if ($lineanalyzed == 2) {
1093 // We are in the footer section with comments
1094 $footer .= (string) str_replace(array(';', '*'), '', implode(' ', $line))."\n";
1095 }
1096
1097 if ($lineanalyzed == 3) {
1098 // We are in the footer section with comments
1099 $errorlines .= (string) $line[0]."\n";
1100 }
1101
1102 if (preg_match('/END - ([a-z0-9_]+)=([a-z0-9]+) - ([a-z0-9_]+)=([a-z0-9]+)$/', (string) $line[0], $reg)) {
1103 $lineanalyzed = 1;
1104 $algosign=$reg[1];
1105 $hashsign=$reg[2];
1106 $algoauth=$reg[3];
1107 $hashauth=$reg[4];
1108
1109 if ($algosign == 'sha256') {
1110 $algo = 'sha256';
1111 $recalculatedhashsign = hash_file($algo, $fullpathtmp);
1112 }
1113 if ($algoauth == 'hmac_sha256') {
1114 $algo = 'sha256';
1115 $recalculatedhashauth = hash_hmac_file($algo, $fullpathtmp, $secretkey);
1116 }
1117 }
1118
1119 if (!$lineanalyzed) {
1120 print 'Line '.$numline.' has format '.$formatexport.' that is not supported';
1121 }
1122 }
1123 fclose($handle);
1124 } else {
1125 setEventMessages('Failed to open file '.GETPOST('urlfile'), null, 'errors');
1126 }
1127
1128 print '<br><br>';
1129
1130 if ($recalculatedhashsign && hash_equals($recalculatedhashsign, $hashsign)) {
1131 print img_picto('', 'tick', 'class="valignmiddle pictofixedwidth"');
1132 print '<b>'.$langs->trans("FileIntegrity").'</b> ';
1133 print ' '.$form->textwithpicto('', $langs->trans("FileContentMatchSignature").'<br><br>'.$algosign.' = '.$recalculatedhashsign);
1134 } else {
1135 print img_picto('', 'cross', 'class="error valignmiddle pictofixedwidth"');
1136 print '<b>'.$langs->trans("FileIntegrity").'</b> ';
1137 print ' '.$form->textwithpicto('', $langs->trans("FileHasBeenCorrupted").'<br><br>Recalculated '.$recalculatedhashsign.' != Found in file '.$hashsign);
1138 }
1139 print '<br><br>';
1140
1141 if ($secretkey) {
1142 if ($recalculatedhashauth && hash_equals($recalculatedhashauth, $hashauth)) {
1143 print img_picto('', 'tick', 'class="valignmiddle pictofixedwidth"');
1144 print '<b>'.$langs->trans("FileAuthenticity").'</b> ';
1145 if (preg_match('/^'.$regnumber.'/', $secretkey) && !userIsTaxAuditor()) {
1146 print ' - <span class="opacitymedium">'.$langs->trans("FileWasGeneratedByThisInstance").'</span>';
1147 } else {
1148 print ' - <span class="opacitymedium">'.$langs->trans("FileWasGeneratedByTheInstanceWithRegistrationId").'</span>';
1149 }
1150 print ' '.$form->textwithpicto('', $langs->trans("FileContentMatchSignature").'<br><br>'.$algoauth.' = '.$recalculatedhashauth);
1151 } elseif ($recalculatedhashsign && hash_equals($recalculatedhashsign, $hashsign)) {
1152 print img_picto('', 'cross', 'class="error valignmiddle pictofixedwidth"');
1153 print '<b>'.$langs->trans("FileAuthenticity").'</b> ';
1154 print ' '.$form->textwithpicto('', $langs->trans("FileNotFromInstance").'<br><br>Recalculated '.$recalculatedhashauth.' != Found in file '.$hashauth);
1155 } else {
1156 print img_picto('', 'cross', 'class="error valignmiddle pictofixedwidth"');
1157 print '<b>'.$langs->trans("FileAuthenticity").'</b> ';
1158 print ' '.$form->textwithpicto('', $langs->trans("FileHasBeenCorruptedOrNotFromInstance").'<br><br>Recalculated '.$recalculatedhashauth.' != Found in file '.$hashauth);
1159 }
1160 } else {
1161 print img_picto('', 'cross', 'class="valignmiddle pictofixedwidth"');
1162 print '<b>'.$langs->trans("FileAuthenticity").'</b> ';
1163 print ' '.$form->textwithpicto('', $langs->trans("AuthenticityCantBeVerifiedIfFullRegistrationNumberNotProvided"));
1164 }
1165 print '<br><br>';
1166
1167 if ($nbLinesModifiedBeforeExport) {
1168 print img_picto('', 'warning', 'class="error valignmiddle pictofixedwidth"');
1169 print '<b>'.$langs->trans("nbLinesModifiedBeforeExport").'</b>';
1170 print '<br><br>';
1171 }
1172
1173 // Now print the error line section
1174 if ($errorlines) {
1175 print img_picto('', 'warning', 'class="error valignmiddle center pictofixedwidth"').'<b>'.$errorlines.'</b><br>';
1176
1177 print '<br>';
1178 }
1179
1180 // Now print the foot section
1181 print img_picto('', 'tick', 'class="valignmiddle center pictofixedwidth"');
1182 print '<b>'.$langs->trans("Summary").'</b>';
1183 print '<textarea class="centpercent" rows="14" spellcheck="false">';
1184 print dol_htmlcleanlastbr($footer);
1185 print '</textarea>';
1186
1187 print '<br>';
1188
1189 $text = $langs->trans("IfIntegrityAuthenticityIsOkYouCanGetdetailByOpeningTheFile");
1190 $text .= '<br>';
1191 $text .= $langs->trans("IfIntegrityAuthenticityIsOkYouCanGetdetailByOpeningTheFile2");
1192 //$langs->transnoentitiesnoconv("logBILL_VALIDATE"), $langs->transnoentitiesnoconv("logPAYMENT_CUSTOMER_CREATE"), $langs->transnoentitiesnoconv("logDOC_DOWNLOAD"), $langs->transnoentitiesnoconv("logCASHCONTROL_CLOSE")).'...');
1193 print info_admin($text);
1194 }
1195
1196
1197 print '<br><br>';
1198 print '<center><a href="'.$_SERVER["PHP_SELF"].'">'.$langs->trans("BackToList").'</a></center>';
1199}
1200
1201if ($action != 'check' && $action != 'checkconfirmed') {
1202 $htmltext = '';
1203
1204 if (!userIsTaxAuditor()) {
1205 $nbrecorddone = $block_static->countRecord();
1206 $mindisksize = 50; // Gb
1207 $maxtranspermonth = 10000;
1208 $nbrecordallowed = $mindisksize * 1024 * 1024 / 40 - $nbrecorddone;
1209 $nbmonthallowed = $nbrecordallowed / $maxtranspermonth;
1210
1211 $htmltext = '';
1212 $htmltext .= $langs->trans("UnalterableLogTool2", $langs->transnoentitiesnoconv("Archives"))."<br>";
1213 $htmltext .= '<span class="small">'.$langs->trans("UnalterableLogTool2MaxUsage", $nbrecorddone, $mindisksize, $nbrecordallowed)."</span><br>";
1214
1215 if ($mysoc->country_code == 'FR') {
1216 $htmltext .= '<br><span class="small">'.$langs->trans("UnalterableLogTool1FR", $langs->transnoentitiesnoconv("Archives")).'</span><br>';
1217 } else {
1218 $htmltext .= '<span class="small">'.$langs->trans("UnalterableLogTool2b", $langs->transnoentitiesnoconv("Archives"))."</span><br>";
1219 }
1220 //$htmltext .= $langs->trans("UnalterableLogTool1");
1221 //$htmltext .= $langs->trans("UnalterableLogTool3")."<br>";
1222
1223 print info_admin($htmltext, 0, 0, 'warning');
1224
1225 print '<br>';
1226 } else {
1227 print '<br>';
1228 }
1229
1230 $param = '';
1231 if ($contextpage != getDolDefaultContextPage(__FILE__)) {
1232 $param .= '&contextpage='.urlencode($contextpage);
1233 }
1234 if ($limit > 0 && $limit != $conf->liste_limit) {
1235 $param .= '&limit='.((int) $limit);
1236 }
1237 if ($search_id != '') {
1238 $param .= '&search_id='.urlencode($search_id);
1239 }
1240 if ($search_fk_user > 0) {
1241 $param .= '&search_fk_user='.urlencode($search_fk_user);
1242 }
1243 if ($search_startyear > 0) {
1244 $param .= '&search_startyear='.((int) $search_startyear);
1245 }
1246 if ($search_startmonth > 0) {
1247 $param .= '&search_startmonth='.((int) $search_startmonth);
1248 }
1249 if ($search_startday > 0) {
1250 $param .= '&search_startday='.((int) $search_startday);
1251 }
1252 if ($search_endyear > 0) {
1253 $param .= '&search_endyear='.((int) $search_endyear);
1254 }
1255 if ($search_endmonth > 0) {
1256 $param .= '&search_endmonth='.((int) $search_endmonth);
1257 }
1258 if ($search_endday > 0) {
1259 $param .= '&search_endday='.((int) $search_endday);
1260 }
1261 if ($search_amount) {
1262 $param .= '&search_amount='.urlencode($search_amount);
1263 }
1264 if ($search_signature) {
1265 $param .= '&search_signature='.urlencode($search_signature);
1266 }
1267 if ($search_showonlyerrors > 0) {
1268 $param .= '&search_showonlyerrors='.((int) $search_showonlyerrors);
1269 }
1270 if ($optioncss != '') {
1271 $param .= '&optioncss='.urlencode($optioncss);
1272 }
1273 if (GETPOST('withtab', 'alpha')) {
1274 $param .= '&withtab='.urlencode(GETPOST('withtab', 'alpha'));
1275 }
1276
1277 // Add $param from extra fields
1278 //include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
1279
1280 if ($action == 'deletefile') {
1281 $langs->load("companies"); // Need for string DeleteFile+ConfirmDeleteFiles
1282 print $form->formconfirm(
1283 $_SERVER["PHP_SELF"].'?urlfile='.urlencode(GETPOST("urlfile")).'&linkid='.GETPOSTINT('linkid').(empty($param) ? '' : $param),
1284 $langs->trans('DeleteFile'),
1285 $langs->trans('ConfirmDeleteFile'),
1286 'confirm_deletefile',
1287 '',
1288 '',
1289 1
1290 );
1291 }
1292
1293
1294 if (!userIsTaxAuditor()) {
1295 print '<form method="POST" id="exportArchives" action="'.$_SERVER["PHP_SELF"].'?output=file">';
1296 print '<input type="hidden" name="token" value="'.newToken().'">';
1297 print '<input type="hidden" name="action" value="export">';
1298
1299 print '<div class="right">';
1300
1301 print '<span class="hideonsmartphone">'.$langs->trans("RestrictYearToExport").': </span>';
1302 // Month
1303 print $formother->select_month((string) GETPOSTINT('monthtoexport'), 'monthtoexport', $langs->trans("Month"), 0, 'minwidth50 maxwidth75imp valignmiddle', true);
1304 print '<input type="text" name="yeartoexport" class="valignmiddle maxwidth75imp" value="'.GETPOST('yeartoexport').'" placeholder="'.$langs->trans("Year").'">';
1305
1306 print ' ';
1307
1308 // Disabled, we will use the getHashUniqueIdOfRegistration() as secret HMAC
1309 //print '<input type="text" name="hmacexportkey" class="valignmiddle minwidth150imp maxwidth300imp" required value="'.GETPOST('hmacexportkey').'" placeholder="'.$langs->trans("Password").'">';
1310
1311 print ' ';
1312
1313 print '<input type="hidden" name="withtab" value="'.GETPOST('withtab', 'alpha').'">';
1314 print '<input type="submit" name="downloadcsv" class="button" value="'.$langs->trans('DownloadLogCSV').'">';
1315 /*if (getDolGlobalString('BLOCKEDLOG_USE_REMOTE_AUTHORITY')) {
1316 print ' | <a href="?action=downloadblockchain&token='.newToken().''.(GETPOST('withtab', 'alpha') ? '&withtab='.GETPOST('withtab', 'alpha') : '').'">'.$langs->trans('DownloadBlockChain').'</a>';
1317 }*/
1318 print ' </div><br>';
1319
1320 print '</form>';
1321 }
1322
1323 /*
1324 print '<form method="POST" id="searchFormList" action="'.dolBuildUrl($_SERVER["PHP_SELF"]).'">';
1325
1326 if ($optioncss != '') {
1327 print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
1328 }
1329 print '<input type="hidden" name="token" value="'.newToken().'">';
1330 print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
1331 print '<input type="hidden" name="action" value="list">';
1332 print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
1333 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
1334 print '<input type="hidden" name="page" value="'.$page.'">';
1335 print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
1336 print '<input type="hidden" name="withtab" value="'.GETPOST('withtab', 'alpha').'">';
1337
1338 print '<div class="div-table-responsive">'; // You can use div-table-responsive-no-min if you don't need reserved height for your table
1339 */
1340
1341 $filearray = dol_dir_list($upload_dir, 'files', 0, '', null, 'name', SORT_ASC, 1);
1342
1343 $modulepart = 'blockedlog';
1344 $relativepathwithnofile = 'archives/';
1345 $disablemove = 1;
1346 /*
1347 $param = '&id='.$object->id.'&entity='.(empty($object->entity) ? getDolEntity() : $object->entity);
1348 include DOL_DOCUMENT_ROOT.'/core/tpl/document_actions_post_headers.tpl.php';
1349 */
1350
1351 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
1352 $formfile = new FormFile($db);
1353
1354 $savingdocmask = '';
1355
1356 $object = $block_static;
1357
1358 // Get the form to add files (upload and links)
1359 $tmparray = $formfile->form_attach_new_file(
1360 $_SERVER["PHP_SELF"],
1361 '',
1362 0,
1363 0,
1364 $permission,
1365 $conf->browser->layout == 'phone' ? 40 : 60,
1366 $object,
1367 '',
1368 1,
1369 $savingdocmask,
1370 1,
1371 'formuserfile',
1372 '',
1373 '',
1374 0,
1375 0,
1376 0,
1377 2
1378 );
1379
1380 $formToUploadAFile = '';
1381
1382 if (is_array($tmparray) && !empty($tmparray)) {
1383 $formToUploadAFile = $tmparray['formToUploadAFile'];
1384 }
1385
1386 // List of document
1387 $formfile->list_of_documents(
1388 $filearray,
1389 null,
1390 $modulepart,
1391 $param,
1392 0,
1393 $relativepathwithnofile, // relative path with no file. For example "0/1"
1394 $permission,
1395 0,
1396 '',
1397 0,
1398 $langs->transnoentitiesnoconv('Archives'),
1399 '',
1400 0,
1401 $permtoedit,
1402 $upload_dir,
1403 $sortfield,
1404 $sortorder,
1405 $disablemove,
1406 0,
1407 -1,
1408 '',
1409 array('afteruploadtitle' => $formToUploadAFile, 'showhideaddbutton' => 1, 'hideshared' => 1, 'buttons' => array(0 => array('picto' => img_picto($langs->trans("ControlFile"), 'question'), 'url' => $_SERVER["PHP_SELF"].'?action=check&token=notrequired'.$param)))
1410 );
1411}
1412
1413
1414print dol_get_fiche_end();
1415
1416print '<br><br>';
1417
1418// End of page
1419llxFooter();
1420$db->close();
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
sumAmountsForUnalterableEvent($block, &$refinvoicefound, &$totalhtamount, &$totalvatamount, &$totalamount, &$total_ht, &$total_vat, &$total_ttc)
sumAmountsForUnalterableEvent
userIsTaxAuditor()
Call remote API service to push the last counter and signature.
blockedlogadmin_prepare_head($withtabsetup)
Define head array for tabs of blockedlog tools setup pages.
isRegistrationDataSavedAndPushed()
Return if the KYC mandatory parameters are set AND pushed/registered centralized server.
getHashUniqueIdOfRegistration($algo='sha256')
Return a hash unique identifier of the registration (used to identify the registration of instance wi...
isALNERunningVersion($blockedlogusagealreadychecked=0, $blockedlogmoduleonalreadychecked=0)
Return if the application is executed with the LNE requirements on.
Class to manage Blocked Log.
Class to offer components to list and upload files.
Class to manage generation of HTML components Only common components must be here.
Class to help generate other html components Only common components are here.
getFullName($langs, $option=0, $nameorder=-1, $maxlen=0)
Return full name (civility+' '+name+' '+lastname)
global $mysoc
dol_get_first_day($year, $month=1, $gm=false)
Return GMT time for first day of a month or year.
Definition date.lib.php:616
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition date.lib.php:126
dol_get_last_day($year, $month=12, $gm=false)
Return GMT time for last day of a month or year.
Definition date.lib.php:635
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_copy($srcfile, $destfile, $newmask='0', $overwriteifexists=1, $testvirus=0, $indexdatabase=0)
Copy a file to another file.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
removeLastLine($fullpath)
Remove the last line of a text file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
csvClean($newvalue, $charset='', $separator='')
Clean a cell to respect rules of CSV file cells.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dolChmod($filepath, $newmask='')
Change mod of a file.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dol_htmlcleanlastbr($stringtodecode)
This function remove all ending and br at end.
getDolDefaultContextPage($s)
Return the default context page string.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
Definition html.lib.php:540
dol_get_fiche_end($notab=0)
Return tab footer of a card.
Definition html.lib.php:738
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='', $cssfordropdown='info_admin')
Show information in HTML for admin users or standard users.
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $nodie=0, $mode='')
Check permissions of a user to show a page and an object.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.
isHTTPS()
Return if we are using a HTTPS connection Check HTTPS (no way to be modified by user but may be empty...