dolibarr 25.0.0-alpha
modBlockedLog.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2017-2025 Laurent Destailleur <eldy@users.sourcefore.net>
3 * Copyright (C) 2026 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
27include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
28include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
29include_once DOL_DOCUMENT_ROOT.'/blockedlog/versionmod.inc.php';
30
31
36{
42 public function __construct($db)
43 {
44 global $mysoc;
45
46 $this->db = $db;
47 $this->numero = 3200;
48 // Key text used to identify module (for permissions, menus, etc...)
49 $this->rights_class = 'blockedlog';
50
51 // Family can be 'crm','financial','hr','projects','products','ecm','technic','other'
52 // It is used to group modules in module setup page
53 $this->family = "base";
54 // Module position in the family on 2 digits ('01', '10', '20', ...)
55 $this->module_position = '76';
56 // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module)
57 $this->name = preg_replace('/^mod/i', '', get_class($this));
58 $this->description = "Enable a log on some business events into an unalterable log. This module may be mandatory for some countries.";
59
60 // Possible values for version are: 'development', 'experimental', 'dolibarr' or version
61 $this->version = constant('DOLCERT_VERSION');
62 $this->version_if_core = 1;
63 // Key used in llx_const table to save module status enabled/disabled (where MYMODULE is value of property name of module in uppercase)
64 $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
65 // Name of image file used for this module.
66 $this->picto = 'blockedlog';
67
68 // Data directories to create when module is enabled
69 $this->dirs = array();
70
71 // Config pages
72 //-------------
73 $this->config_page_url = array('registration.php?origin=setupmodule&withtab=1@blockedlog');
74
75 // Dependencies
76 //-------------
77 $this->hidden = false; // A condition to disable module
78 $this->depends = array('always' => 'modFacture'); // List of modules id that must be enabled if this module is enabled
79 $this->requiredby = array(); // List of modules id to disable if this one is disabled
80 $this->conflictwith = array(); // List of modules id this module is in conflict with
81 $this->langfiles = array('blockedlog');
82
83 $this->warnings_activation = array();
84 $this->warnings_activation_ext = array();
85 $this->warnings_unactivation = array('FR' => 'BlockedLogAreRequiredByYourCountryLegislation');
86
87 // Currently, activation is not automatic because only companies (in France) making invoices to non business customers must
88 // enable this module.
89 /*if (getDolGlobalString('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY')) {
90 $tmp = explode(',', getDolGlobalString('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY'));
91 $this->automatic_activation = array();
92 foreach($tmp as $countrycodekey)
93 {
94 $this->automatic_activation[$countrycodekey] = 'BlockedLogActivatedBecauseRequiredByYourCountryLegislation';
95 }
96 }*/
97 //var_dump($this->automatic_activation);
98
99 $this->always_enabled = (isModEnabled('blockedlog')
100 && getDolGlobalString('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY')
101 && in_array((empty($mysoc->country_code) ? '' : $mysoc->country_code), explode(',', getDolGlobalString('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY')))
102 && $this->alreadyUsed());
103
104 // Constants
105 //-----------
106 $this->const = array(
107 1 => array('BLOCKEDLOG_DISABLE_NOT_ALLOWED_FOR_COUNTRY', 'chaine', 'FR', 'This is list of country code where the module may be mandatory', 0, 'current', 0)
108 );
109
110 // New pages on tabs
111 // -----------------
112 $this->tabs = array();
113
114 // Boxes
115 //------
116 $this->boxes = array();
117
118 // Permissions
119 // -----------------
120 $this->rights = array(); // Permission array used by this module
121
122 $r = 1;
123 $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
124 $this->rights[$r][1] = 'Read archived events and fingerprints'; // Permission label
125 $this->rights[$r][3] = 0; // Permission by default for new user (0/1)
126 $this->rights[$r][4] = 'read'; // In php code, permission will be checked by test if ($user->rights->mymodule->level1->level2)
127 $this->rights[$r][5] = '';
128
129 // Main menu entries
130 // -----------------
131 $r = 0;
132 $this->menu[$r] = array(
133 'fk_menu' => 'fk_mainmenu=tools', // Use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
134 'mainmenu' => 'tools',
135 'leftmenu' => 'blockedlogbrowser',
136 'type' => 'left', // This is a Left menu entry
137 'titre' => 'BrowseBlockedLog',
138 'prefix' => img_picto('', $this->picto, 'class="paddingright pictofixedwidth"'),
139 'url' => '/blockedlog/admin/blockedlog_list.php?mainmenu=tools&leftmenu=blockedlogbrowser',
140 'langs' => 'blockedlog', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
141 'position' => 200,
142 'enabled' => 'isModEnabled("blockedlog")', // Define condition to show or hide menu entry. Use '$conf->mymodule->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
143 'perms' => '$user->hasRight("blockedlog", "read")', // Use 'perms'=>'$user->hasRight("mymodule","level1","level2")' if you want your menu with a permission rules
144 'target' => '',
145 'user' => 2, // 0=Menu for internal users, 1=external users, 2=both
146 );
147 $r++;
148 }
149
150
156 public function alreadyUsed()
157 {
158 require_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
159
160 return isBlockedLogUsed();
161 }
162
163
172 public function init($options = '')
173 {
174 global $conf, $langs, $mysoc, $user;
175
176 $sql = array();
177
178 // Detect minimal version of PHP
179 if (version_compare(PHP_VERSION, '7.0.0') < 0) {
180 $errmsg = 'Error: You are using a too low version of PHP';
181 dol_syslog($errmsg, LOG_ERR);
182 $this->error = $errmsg;
183 return 0;
184 }
185
186 // Clear cache
187 unset($_SESSION['obfuscationkey_'.((int) $conf->entity)]);
188 unset($conf->cache['obfuscationkey_'.((int) $conf->entity)]);
189
190 require_once DOL_DOCUMENT_ROOT . '/blockedlog/class/blockedlog.class.php';
191 $b = new BlockedLog($this->db);
192
193 // any value of $options except 'acceptredirect' will bypass this redirection
194 if (isALNEQualifiedVersion(1, 1) && $options == 'acceptredirect') { // Redirect done for all french companies, even if not assujeti.
195 // We first switch on registration page
196 header("Location: ".DOL_URL_ROOT.'/blockedlog/admin/registration.php?origin=initmodule&withtab=0');
197 exit;
198 }
199
200 // Check the context of running company is defined
201 if (empty($mysoc->country_code)) {
202 $errmsg = 'Error: The context of the running company is not defined';
203 dol_syslog($errmsg, LOG_ERR);
204 $this->error = $errmsg;
205 return 0;
206 }
207
208 // Check that the HTTPS is forced
209 $s = $b->canBeEnabled();
210 if ($s) { // Activation not allowed
211 $this->error = $s;
212 return 0;
213 }
214
215 // If we are here, it means the registration has been done, we can activate the module (this means creating a HMAC key).
216
217 $this->db->begin();
218
219 $error = 0;
220
221 // Generate and save the HMAC key if it does not exists yet
222 $hmac_encoded_secret_key = $b->getEncodedHMACSecretKey();
223
224 if (empty($hmac_encoded_secret_key)) {
225 // No HMAC key yet, we generate one.
226 $randomsecret = bin2hex(random_bytes(32)); // 64 char hex - 256 bits
227
228 $hmac_secret_key = 'BLOCKEDLOGHMAC'.$randomsecret; // Example: 'BLOCKEDLOGHMACY3Ewx37RXbSd8gL9JV8p7Wqw7qvq2K2A'
229 //$hmac_secret_key = 'BLOCKEDLOGHMACY3Ewx37RXbSd8gL9JV8p7Wqw7qvq2K2A';
230
231 $obfuscationkey = '';
232 if (isALNERunningVersion(1) && $mysoc->country_code == 'FR') {
233 try {
234 $obfuscationkey = $b->getObfuscationKey(); // Get the obfuscation key from memory or remote server. If not found, we retrieve it.
235 //$obfuscationkey = ''; // Uncomment this to test if obfuscation key can't be retrieved.
236 } catch (Exception $e) {
237 $error++;
238 setEventMessages($e->getMessage(), null, 'errors');
239 $obfuscationkey = '';
240 }
241
242 if (empty($obfuscationkey)) {
243 $error++;
244 $url_for_ping = getDolGlobalString('MAIN_URL_FOR_PING', "https://ping.dolibarr.org/");
245 setEventMessages($langs->trans('FailedToGetRemoteObfuscationKeyReTryLater', $url_for_ping), null, 'errors');
246 }
247
248 if (!$error) {
249 // Save HMAC key to obfuscate it with the $obfuscationkey
250 //$result = dolibarr_set_const($this->db, 'BLOCKEDLOG_HMAC_KEY', $hmac_secret_key, 'chaine', 0, 'The secret key for HMAC used for blockedlog record', 0); // Will encrypt the value using dolCrypt and store it.
251 $result = $b->saveHMACSecretKey($hmac_secret_key, 'dolobfuscationv1-'.$mysoc->idprof1, $obfuscationkey); // gitleaks:allow
252 if ($result < 0) {
253 $error++;
254 setEventMessages($b->error, $b->errors, 'errors');
255 }
256 }
257 } else {
258 $result = $b->saveHMACSecretKey($hmac_secret_key, 'dolcrypt'); // gitleaks:allow
259 if ($result < 0) {
260 $error++;
261 setEventMessages($b->error, $b->errors, 'errors');
262 }
263 }
264 } else {
265 // This case should not happen. If using a certified version, the module can't be disabled and reinitialized, so
266 // we should not reach this code. In case it happens in future (or if using F5 just after enabling module), we reach this protection
267 // that check everything is still ok and report a warning if not.
268
269 // Here we have the obfuscated value of BLOCKEDLOG_HMAC_KEY in $hmac_encoded_secret_key. We need to unobfuscate it.
270 $hmac_secret_key = '';
271 try {
272 $hmac_secret_key = $b->getClearHMACSecretKey($hmac_encoded_secret_key); // Note: On network trouble, an Exception is thrown to the caller
273 } catch (Exception $e) {
274 $firsterrormessage = $e->getMessage();
275
276 // Another chance to get HMAC when saved with old obfuscation method (dolcrypt) - Migration will be done at next writing.
277 $hmac_encoded_secret_key_alt = $b->getEncodedHMACSecretKey(1, 1);
278 if (!empty($hmac_encoded_secret_key_alt)) {
279 try {
280 $hmac_secret_key_alt = $b->getClearHMACSecretKey($hmac_encoded_secret_key_alt); // Note: On network trouble, an Exception is thrown to the caller
281
282 if (preg_match('/^BLOCKEDLOGHMAC/', (string) $hmac_secret_key_alt)) {
283 $hmac_secret_key = $hmac_secret_key_alt;
284 $firsterrormessage = '';
285 }
286 } catch (Exception $e) {
287 if (empty($firsterrormessage)) {
288 $firsterrormessage = $e->getMessage();
289 }
290 }
291 } else {
292 if (empty($firsterrormessage)) {
293 $firsterrormessage = $e->getMessage();
294 }
295 }
296
297 if ($firsterrormessage) { // If error
298 $error++;
299 $this->error = 'modBlockLog init Error: '.$firsterrormessage.'. ';
300 }
301 }
302 if (! preg_match('/^BLOCKEDLOGHMAC/', $hmac_secret_key)) {
303 $error++;
304 $this->error .= 'modBlockedLog init Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY '.$hmac_encoded_secret_key.' using the remote obfuscation key. The value was found in llx_const table but decoding with obfuscation key failed. May be the remote server to get the obfuscation key to decode it was offline.';
305 $this->error .= ' If you don\'t use the Unalterable Log module, you can also remove the BLOCKEDLOG_HMAC_KEY entry from llx_const table. If you use the Unalterable Log, this is not possible because this will invalidate all past record.';
306 }
307 /*
308 if (preg_match('/^dolobfuscationv1/', $hmac_encoded_secret_key)) {
309 // New method
310 $obfuscationkey = '';
311 try {
312 dol_syslog("mysoc->profid1 = ".$mysoc->idprof1);
313 $obfuscationkey = $b->getObfuscationKey(); // Get the obfuscation key from memory or remote server. If not found, we retrieve it.
314 //$obfuscationkey = ''; // Uncomment this to test if obfuscation key can't be retrieved.
315 } catch (Exception $e) {
316 $error++;
317 $this->error = $e->getMessage();
318 $obfuscationkey = '';
319 }
320
321 if (empty($obfuscationkey)) {
322 $error++;
323 $url_for_ping = getDolGlobalString('MAIN_URL_FOR_PING', "https://ping.dolibarr.org/");
324 $this->error = $langs->trans('FailedToGetRemoteObfuscationKeyReTryLater', $url_for_ping);
325 }
326
327 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key, $obfuscationkey);
328
329 if (! preg_match('/^BLOCKEDLOGHMAC/', $hmac_secret_key)) {
330 $error++;
331 $this->error = 'modBlockedLog init Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY '.$hmac_encoded_secret_key.' using the remote obfuscation key. The value was found in llx_const table but decoding with '.$obfuscationkey.' failed. May be the remote server to get the obfucation key to decode it was offline.';
332 $this->error .= 'If you don\'t use the Unalterable Log module, you can also remove the BLOCKEDLOG_HMAC_KEY entry from llx_const table. If you use the Unalterable Log, this is not possible because this will invalidate all past record.';
333 }
334 } else {
335 // Old method for backward compatibility
336 // Note: The migration of the way to store the HMAC key from old method to the new one will be done automatically at next recording by buildFinalSignatureHash()
337 $hmac_secret_key = dolDecrypt($hmac_encoded_secret_key);
338
339 if (! preg_match('/^BLOCKEDLOGHMAC/', $hmac_secret_key)) {
340 $error++;
341 $this->error = 'modBlockedLog init Error: Failed to decode the crypted value of the parameter BLOCKEDLOG_HMAC_KEY '.$hmac_encoded_secret_key.' using the $dolibarr_main_crypt_key. The value was found in llx_const table but decoding failed. May be the database data were restored onto another environment and the coding/decoding key $dolibarr_main_dolcrypt_key was not restored with the same value in conf.php file.';
342 $this->error .= 'Restore the value of $dolibarr_main_crypt_key that was used for encryption in database and restart the migration.';
343 $this->error .= 'If you don\'t use the Unalterable Log module, you can also remove the BLOCKEDLOG_HMAC_KEY entry from llx_const table. If you use the Unalterable Log, this is not possible because this will invalidate all past record.';
344 }
345 }
346 */
347 }
348
349 if ($error) {
350 $this->db->rollback();
351 return 0;
352 } else {
353 $this->db->commit();
354 }
355
356
357 // We add an entry to show we enable the module
358
359 $object = new stdClass();
360 $object->id = 0;
361 $object->element = 'module';
362 $object->ref = 'systemevent';
363 $object->entity = $conf->entity;
364 $object->date = dol_now();
365 $object->label = 'Module enabled';
366
367 // Add first entry in unalterable Log to track that module was activated
368 $action = 'MODULE_SET';
369 $result = $b->setObjectData($object, $action, 0, $user, 0);
370
371 if ($result < 0) {
372 $this->error = $b->error;
373 $this->errors = $b->errors;
374 return 0;
375 }
376
377 $this->db->begin();
378
379 $res = $b->create($user);
380 if ($res <= 0) {
381 $this->db->rollback();
382
383 $this->error = $b->error;
384 $this->errors = $b->errors;
385 return $res;
386 }
387
388 $resinit = $this->_init($sql, $options);
389 if ($resinit <= 0) {
390 $this->db->rollback();
391
392 return $resinit;
393 }
394
395 $this->db->commit();
396
397 return 1;
398 }
399
408 public function remove($options = '')
409 {
410 global $conf, $user;
411
412 $sql = array();
413
414 // If already used, we add an entry to show we enable module
415 require_once DOL_DOCUMENT_ROOT.'/blockedlog/class/blockedlog.class.php';
416 $b = new BlockedLog($this->db);
417
418 dol_syslog("modBlockedLog::remove option=".$options, LOG_DEBUG);
419
420 $object = new stdClass();
421 $object->id = 1;
422 $object->element = 'module';
423 $object->ref = 'systemevent';
424 $object->entity = $conf->entity;
425 $object->date = dol_now();
426 $object->label = 'Module disabled';
427
428 // Add entry in unalterable Log to track that module was activated
429 $action = 'MODULE_RESET';
430 $result = $b->setObjectData($object, $action, 0, $user, 0);
431 if ($result < 0) {
432 $this->error = $b->error;
433 $this->errors = $b->errors;
434 return 0;
435 }
436
437 if ($b->alreadyUsed(1)) {
438 // Unalterable log was already used.
439 if ($options != 'forcedisable' && !$b->canBeDisabled()) {
440 // Case we refuse to disable it
441 global $langs;
442 $this->error = $langs->trans('DisablingBlockedLogIsNotallowedOnceUsedExceptOnFullreset', $langs->transnoentitiesnoconv('BlockedLog'));
443 return 0;
444 } else {
445 // Case we disable it with a log
446 $res = $b->create($user, '0000000000'); // If already used for something else than SET or UNSET, we log with error
447 }
448 } else {
449 $res = $b->create($user);
450 }
451 if ($res <= 0) {
452 $this->error = $b->error;
453 $this->errors = $b->errors;
454 return $res;
455 }
456
457 return $this->_remove($sql, $options);
458 }
459
460
467 public function getDesc($foruseinpopupdesc = 0)
468 {
469 global $langs, $mysoc;
470 $langs->load("admin");
471
472 // If module description translation exists
473 $s = $langs->transnoentitiesnoconv("Module".$this->numero."Desc");
474
475 if ($foruseinpopupdesc) {
476 $langs->load("blockedlog");
477 $s .= '<br>';
478
479 // Special message for France
480 if ($mysoc->country_code == 'FR') {
481 $islne = isALNEQualifiedVersion(1, 1);
482
483 $versionbadge = '<span class="badge-text badge-secondary">'.getBlockedLogVersionToShow();
484 /*
485 if ($mysoc->country_code == 'FR' && !constant('CERTIF_LNE')) {
486 // Can add an edditional mention
487 $versionbadge .= ' - '.$langs->trans("NeedAThirdPartyStatement");
488 }
489 */
490 $versionbadge .= '</span>';
491 if ($islne) {
492 if (preg_match('/\-/', DOL_VERSION)) {
493 // This is an alpha or beta version
494 $s .= info_admin($langs->trans("LNECandidateVersionForCertificationFR", $versionbadge), 0, 0, 'info');
495 } else {
496 $s .= info_admin($langs->trans("LNECertifiedVersionFR", $versionbadge), 0, 0, 'info');
497 }
498 } else {
499 $s .= info_admin($langs->trans("NotCertifiedVersionFR", $versionbadge), 0, 0, 'warning');
500 }
501 }
502
503 // Add warning to advice users to make regularly archives
504 if (in_array($mysoc->country_code, array('FR'))) {
505 $s .= info_admin($langs->trans("UnalterableLogTool1FR"), 0, 0, 'warning');
506 }
507 }
508
509 return $s;
510 }
511}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
isALNEQualifiedVersion($ignoredev=0, $ignoremodule=0)
Return if the version is a candidate version to get the LNE certification and if the prerequisites ar...
isBlockedLogUsed($ignoresystem=0)
Return if the blocked log was already used to block some events.
isALNERunningVersion($blockedlogtestalreadydone=0, $blockedlogmodulealreadydone=0)
Return if the application is executed with the LNE requirements on.
Class to manage Blocked Log.
Class DolibarrModules.
_init($array_sql, $options='')
Enables a module.
_remove($array_sql, $options='')
Disable function.
Class to describe a BlockedLog module.
init($options='')
Function called when module is enabled.
alreadyUsed()
Check if module was already used before unactivation linked to warnings_unactivation property.
getDesc($foruseinpopupdesc=0)
Overwrite the common getDesc() method.
__construct($db)
Constructor.
global $mysoc
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_now($mode='gmt')
Return date for now.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
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.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:133