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