Issues (32)

Security Analysis    no vulnerabilities found

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

sources/logs.datatables.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      logs.datatables.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2025 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use TeampassClasses\SessionManager\SessionManager;
33
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
34
use TeampassClasses\Language\Language;
35
use EZimuel\PHPSecureSession;
36
use TeampassClasses\PerformChecks\PerformChecks;
37
use TeampassClasses\ConfigManager\ConfigManager;
38
use TeampassClasses\NestedTree\NestedTree;
39
use voku\helper\AntiXSS;
40
41
// Load functions
42
require_once 'main.functions.php';
43
44
// init
45
loadClasses('DB');
46
$session = SessionManager::getSession();
47
$request = SymfonyRequest::createFromGlobals();
48
$lang = new Language($session->get('user-language') ?? 'english');
49
$antiXss = new AntiXSS();
50
51
// Load config
52
$configManager = new ConfigManager();
53
$SETTINGS = $configManager->getAllSettings();
54
55
// Do checks
56
// Instantiate the class with posted data
57
$checkUserAccess = new PerformChecks(
58
    dataSanitizer(
59
        [
60
            'type' => htmlspecialchars($request->request->get('type', ''), ENT_QUOTES, 'UTF-8'),
61
        ],
62
        [
63
            'type' => 'trim|escape',
64
        ],
65
    ),
66
    [
67
        'user_id' => returnIfSet($session->get('user-id'), null),
68
        'user_key' => returnIfSet($session->get('key'), null),
69
    ]
70
);
71
// Handle the case
72
echo $checkUserAccess->caseHandler();
73
if (
74
    $checkUserAccess->userAccessPage('utilities.logs') === false ||
75
    $checkUserAccess->checkSession() === false
76
) {
77
    // Not allowed page
78
    $session->set('system-error_code', ERR_NOT_ALLOWED);
79
    include $SETTINGS['cpassman_dir'] . '/error.php';
80
    exit;
81
}
82
83
// Define Timezone
84
date_default_timezone_set($SETTINGS['timezone'] ?? 'UTC');
85
86
// Set header properties
87
header('Content-type: text/html; charset=utf-8');
88
header('Cache-Control: no-cache, no-store, must-revalidate');
89
90
// --------------------------------- //
91
92
// Configure AntiXSS to keep double-quotes
93
$antiXss->removeEvilAttributes(['style', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'onmousemove', 'onkeydown', 'onkeyup', 'onkeypress', 'onchange', 'onblur', 'onfocus', 'onabort', 'onerror', 'onscroll']);
94
$antiXss->removeEvilHtmlTags(['script', 'iframe', 'embed', 'object', 'applet', 'link', 'style']);
95
96
// Load tree
97
$tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
98
99
// Get the data
100
$params = $request->query->all();
101
102
// Init
103
$searchValue = $sWhere = $sOrder = $sOutput = '';
104
$aSortTypes = ['ASC', 'DESC'];
105
$sLimitStart = $request->query->has('start') 
106
    ? $request->query->filter('start', 0, FILTER_VALIDATE_INT, ['options' => ['default' => 0, 'min_range' => 0]]) 
107
    : 0;
108
$sLimitLength = $request->query->has('length') 
109
    ? $request->query->filter('length', 0, FILTER_VALIDATE_INT, ['options' => ['default' => 0, 'min_range' => 0]]) 
110
    : 10;
111
112
// Check search parameters
113
if (isset($params['search']['value'])) {
114
    // Case 1: search[value]
115
    $searchValue = (string) $params['search']['value'];
116
} elseif (isset($params['sSearch'])) {
117
    // Case 2: sSearch
118
    $searchValue = (string) $params['sSearch'];
119
}
120
121
// Ordering
122
$order = strtoupper($params['order'][0]['dir'] ?? null);
0 ignored issues
show
It seems like $params['order'][0]['dir'] ?? null can also be of type null; however, parameter $string of strtoupper() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
$order = strtoupper(/** @scrutinizer ignore-type */ $params['order'][0]['dir'] ?? null);
Loading history...
123
$orderDirection = in_array($order, $aSortTypes, true) ? $order : 'DESC';
124
    
125
// Start building the query and output depending on the action
126
if (isset($params['action']) && $params['action'] === 'connections') {
127
    //Columns name
128
    $aColumns = ['l.date', 'l.label', 'l.qui', 'u.login', 'u.name', 'u.lastname'];
129
130
    // Ordering
131
    $orderColumn = $aColumns[0];
132
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
133
        $orderColumn = $aColumns[$params['order'][0]['column']];
134
    }
135
    
136
    // Filtering
137
    $sWhere = new WhereClause('AND');
138
    if ($searchValue !== '') {        
139
        $subclause = $sWhere->addClause('OR');
140
        foreach ($aColumns as $column) {
141
            $subclause->add($column.' LIKE %ss', $searchValue);
142
        }
143
    }
144
    $sWhere->add('l.type = %s', 'user_connection');
145
146
    // Get the total number of records
147
    $iTotal = DB::queryFirstField(
148
        'SELECT COUNT(*)
149
        FROM '.prefixTable('log_system').' as l
150
        INNER JOIN '.prefixTable('users').' as u ON (l.qui=u.id) 
151
        WHERE %l ORDER BY %l %l',
152
        $sWhere,
153
        $orderColumn,
154
        $orderDirection
155
    );
156
157
    // Prepare the SQL query
158
    $sql = 'SELECT l.date as date, l.label as label, l.qui as who, 
159
    u.login as login, u.name AS name, u.lastname AS lastname
160
    FROM '.prefixTable('log_system').' as l
161
    INNER JOIN '.prefixTable('users').' as u ON (l.qui=u.id)
162
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
163
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
164
165
    // Get the records
166
    $rows = DB::query($sql, ...$params);
167
    $iFilteredTotal = DB::count();
168
    
169
    // Output
170
    $sOutput = '{';
171
    $sOutput .= '"sEcho": '. $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
172
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
173
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
174
    $sOutput .= '"aaData": ';
175
    if ($iFilteredTotal > 0) {
176
        $sOutput .= '[';
177
    }
178
    foreach ($rows as $record) {
179
        $sOutput .= '[';
180
        //col1
181
        $sOutput .= '"'.date($SETTINGS['date_format'].' '.$SETTINGS['time_format'], (int) $record['date']).'", ';
182
        //col2
183
        $sOutput .= '"'.str_replace([chr(10), chr(13)], [' ', ' '], htmlspecialchars(stripslashes((string) $record['label']), ENT_QUOTES)).'", ';
184
        //col3
185
        $sOutput .= '"'.htmlspecialchars(stripslashes((string) $record['name']), ENT_QUOTES).' '.htmlspecialchars(stripslashes((string) $record['lastname']), ENT_QUOTES).' ['.htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES).']"';
186
        //Finish the line
187
        $sOutput .= '],';
188
    }
189
190
    if (count($rows) > 0) {
191
        $sOutput = substr_replace($sOutput, '', -1);
192
        $sOutput .= '] }';
193
    } else {
194
        $sOutput .= '[] }';
195
    }
196
197
    /* ERRORS LOG */
198
} elseif (isset($params['action']) && $params['action'] === 'access') {
199
    //Columns name
200
    $aColumns = ['l.date', 'i.label', 'u.login'];
201
202
    // Ordering
203
    $orderColumn = $aColumns[0];
204
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
205
        $orderColumn = $aColumns[$params['order'][0]['column']];
206
    }
207
208
    // Filtering
209
    $sWhere = new WhereClause('AND');
210
    if ($searchValue !== '') {        
211
        $subclause = $sWhere->addClause('OR');
212
        foreach ($aColumns as $column) {
213
            $subclause->add($column.' LIKE %ss', $searchValue);
214
        }
215
    }
216
    $sWhere->add('l.action = %s', 'at_shown');
217
218
    // Get the total number of records
219
    $iTotal = DB::queryFirstField(
220
        'SELECT COUNT(*)
221
        FROM '.prefixTable('log_items').' as l
222
        INNER JOIN '.prefixTable('items').' as i ON (l.id_item=i.id)
223
        INNER JOIN '.prefixTable('users').' as u ON (l.id_user=u.id)
224
        WHERE %l ORDER BY %l %l',
225
        $sWhere,
226
        $orderColumn,
227
        $orderDirection
228
    );
229
230
    // Prepare the SQL query
231
    $sql = 'SELECT l.date as date, u.login as login, i.label as label
232
    FROM '.prefixTable('log_items').' as l
233
    INNER JOIN '.prefixTable('items').' as i ON (l.id_item=i.id)
234
    INNER JOIN '.prefixTable('users').' as u ON (l.id_user=u.id)
235
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
236
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
237
238
    // Get the records
239
    $rows = DB::query($sql, ...$params);
240
    $iFilteredTotal = DB::count();
241
242
    // Output
243
    $sOutput = '{';
244
    $sOutput .= '"sEcho": '. $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
245
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
246
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
247
    $sOutput .= '"aaData": ';
248
    if ($iFilteredTotal > 0) {
249
        $sOutput .= '[';
250
    }
251
    foreach ($rows as $record) {
252
        $sOutput .= '[';
253
        //col1
254
        $sOutput .= '"'.date($SETTINGS['date_format'].' '.$SETTINGS['time_format'], (int) $record['date']).'", ';
255
        //col2
256
        $sOutput .= '"'.str_replace([chr(10), chr(13)], [' ', ' '], htmlspecialchars(stripslashes((string) $record['label']), ENT_QUOTES)).'", ';
257
        //col3
258
        $sOutput .= '"'.htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES).'"';
259
        //Finish the line
260
        $sOutput .= '],';
261
    }
262
263
    if (count($rows) > 0) {
264
        $sOutput = substr_replace($sOutput, '', -1);
265
        $sOutput .= '] }';
266
    } else {
267
        $sOutput .= '[] }';
268
    }
269
270
    /* COPY LOG */
271
} elseif (isset($params['action']) && $params['action'] === 'copy') {
272
    //Columns name
273
    $aColumns = ['l.date', 'i.label', 'u.login'];
274
275
    // Ordering
276
    $orderColumn = $aColumns[0];
277
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
278
        $orderColumn = $aColumns[$params['order'][0]['column']];
279
    }
280
    
281
    // Filtering
282
    $sWhere = new WhereClause('AND');
283
    if ($searchValue !== '') {        
284
        $subclause = $sWhere->addClause('OR');
285
        foreach ($aColumns as $column) {
286
            $subclause->add($column.' LIKE %ss', $searchValue);
287
        }
288
    }
289
    $sWhere->add('l.action = %s', 'at_copy');
290
291
    // Get the total number of records
292
    $iTotal = DB::queryFirstField(
293
        'SELECT COUNT(*)
294
        FROM '.prefixTable('log_items').' as l
295
        INNER JOIN '.prefixTable('items').' as i ON (l.id_item=i.id)
296
        INNER JOIN '.prefixTable('users').' as u ON (l.id_user=u.id)
297
        WHERE %l ORDER BY %l %l',
298
        $sWhere,
299
        $orderColumn,
300
        $orderDirection
301
    );
302
303
    // Prepare the SQL query
304
    $sql = 'SELECT l.date as date, u.login as login, u.name AS name, u.lastname AS lastname, i.label as label
305
    FROM '.prefixTable('log_items').' as l
306
    INNER JOIN '.prefixTable('items').' as i ON (l.id_item=i.id)
307
    INNER JOIN '.prefixTable('users').' as u ON (l.id_user=u.id)
308
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
309
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
310
311
    // Get the records
312
    $rows = DB::query($sql, ...$params);
313
    $iFilteredTotal = DB::count();
314
315
    // Output
316
    $sOutput = '{';
317
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
318
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
319
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
320
    $sOutput .= '"aaData": ';
321
    if ($iFilteredTotal > 0) {
322
        $sOutput .= '[';
323
    }
324
    foreach ($rows as $record) {
325
        $sOutput .= '[';
326
        //col1
327
        $sOutput .= '"'.date($SETTINGS['date_format'].' '.$SETTINGS['time_format'], (int) $record['date']).'", ';
328
        //col2
329
        $sOutput .= '"'.trim(htmlspecialchars(stripslashes((string) $record['label']), ENT_QUOTES)).'", ';
330
        //col3
331
        $sOutput .= '"'.trim(htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES)).'"';
332
        //Finish the line
333
        $sOutput .= '],';
334
    }
335
336
    if (count($rows) > 0) {
337
        $sOutput = substr_replace($sOutput, '', -1);
338
        $sOutput .= '] }';
339
    } else {
340
        $sOutput .= '[] }';
341
    }
342
343
    /*
344
    * ADMIN LOG
345
     */
346
} elseif (isset($params['action']) && $params['action'] === 'admin') {
347
    //Columns name
348
    $aColumns = ['l.date', 'u.login', 'l.label', 'l.field_1'];
349
350
    // Ordering
351
    $orderColumn = $aColumns[0];
352
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
353
        $orderColumn = $aColumns[$params['order'][0]['column']];
354
    }
355
356
    // Filtering
357
    $sWhere = new WhereClause('AND');
358
    if ($searchValue !== '') {        
359
        $subclause = $sWhere->addClause('OR');
360
        foreach ($aColumns as $column) {
361
            $subclause->add($column.' LIKE %ss', $searchValue);
362
        }
363
    }
364
    $sWhere->add('l.type IN %ls', ['admin_action', 'user_mngt']);
365
366
    // Get the total number of records
367
    $iTotal = DB::queryFirstField(
368
        'SELECT COUNT(*)
369
        FROM '.prefixTable('log_system').' as l
370
        INNER JOIN '.prefixTable('users').' as u ON (l.qui=u.id)
371
        WHERE %l ORDER BY %l %l',
372
        $sWhere,
373
        $orderColumn,
374
        $orderDirection
375
    );
376
377
    // Prepare the SQL query
378
    $sql = 'SELECT l.date as date, u.login as login, u.name AS name, u.lastname AS lastname, l.label as label, l.field_1 as field_1
379
    FROM '.prefixTable('log_system').' as l
380
    INNER JOIN '.prefixTable('users').' as u ON (l.qui=u.id)
381
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
382
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
383
384
    // Get the records
385
    $rows = DB::query($sql, ...$params);    
386
    $iFilteredTotal = DB::count();
387
388
    // Output
389
    $sOutput = '{';
390
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
391
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
392
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
393
    $sOutput .= '"aaData": [ ';
394
    foreach ($rows as $record) {
395
        $get_item_in_list = true;
396
        $sOutput_item = '[';
397
        //col1
398
        $sOutput_item .= '"'.date($SETTINGS['date_format'].' '.$SETTINGS['time_format'], (int) $record['date']).'", ';
399
        //col2
400
        $sOutput_item .= '"'.htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES).'", ';
401
        //col3
402
        if ($record['label'] === 'at_user_added') {
403
            $cell = $lang->get('user_creation');
404
        } elseif ($record['label'] === 'at_user_deleted' || $record['label'] === 'user_deleted') {
405
            $cell = $lang->get('user_deletion');
406
        } elseif ($record['label'] === 'at_user_updated') {
407
            $cell = $lang->get('user_updated');
408
        } elseif (strpos($record['label'], 'at_user_email_changed') !== false) {
409
            $change = explode(':', $record['label']);
410
            $cell = $lang->get('log_user_email_changed').' '.$change[1];
411
        } elseif ($record['label'] === 'at_user_new_keys') {
412
            $cell = $lang->get('new_keys_generated');
413
        } elseif ($record['label'] === 'at_user_keys_download') {
414
            $cell = $lang->get('user_keys_downloaded');
415
        } elseif ($record['label'] === 'at_2fa_google_code_send_by_email') {
416
            $cell = $lang->get('mfa_code_send_by_email');
417
        } else {
418
            $cell = htmlspecialchars(stripslashes((string) $record['label']), ENT_QUOTES);
419
        }
420
        $sOutput_item .= '"'.$cell.'" ';
421
        //col4
422
        if (empty($record['field_1']) === false) {
423
            // get user name
424
            $info = DB::queryFirstRow(
425
                'SELECT u.login as login, u.name AS name, u.lastname AS lastname
426
                    FROM '.prefixTable('users').' as u
427
                    WHERE u.id = %i',
428
                    $record['field_1']
429
            );
430
            $sOutput_item .= ', "'.(empty($info['name']) === false ? htmlspecialchars(stripslashes((string) $info['name'].' '.$info['lastname']), ENT_QUOTES) : 'Removed user ('.$record['field_1'].')').'" ';
431
        } else {
432
            $sOutput_item .= ', "" ';
433
        }
434
        //Finish the line
435
        $sOutput_item .= '], ';
436
        if ($get_item_in_list === true) {
437
            $sOutput .= $sOutput_item;
438
        }
439
    }
440
    if ($iFilteredTotal > 0) {
441
        $sOutput = substr_replace($sOutput, '', -2);
442
    }
443
    $sOutput .= '] }';
444
/* ITEMS */
445
} elseif (isset($params['action']) && $params['action'] === 'items') {
446
    require_once $SETTINGS['cpassman_dir'].'/sources/main.functions.php';
447
    //Columns name
448
    $aColumns = ['l.date', 'i.label', 'u.login', 'l.action', 'i.perso', 'i.id', 't.title'];
449
450
    // Ordering
451
    $orderColumn = $aColumns[0];
452
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
453
        $orderColumn = $aColumns[$params['order'][0]['column']];
454
    }
455
456
    // Filtering
457
    $sWhere = new WhereClause('OR');
458
    if ($searchValue !== '') {        
459
        foreach ($aColumns as $column) {
460
            $sWhere->add($column.' LIKE %ss', $searchValue);
461
        }
462
    }
463
464
    // Get the total number of records
465
    $iTotal = DB::queryFirstField(
466
        'SELECT COUNT(*)
467
        FROM '.prefixTable('log_items').' AS l
468
        INNER JOIN '.prefixTable('items').' AS i ON (l.id_item=i.id)
469
        INNER JOIN '.prefixTable('users').' AS u ON (l.id_user=u.id)
470
        INNER JOIN '.prefixTable('nested_tree').' AS t ON (i.id_tree=t.id)
471
        WHERE %l ORDER BY %l %l',
472
        $sWhere,
473
        $orderColumn,
474
        $orderDirection
475
    );
476
477
    // Prepare the SQL query
478
    $sql = 'SELECT l.date AS date, u.login AS login, u.name AS name, u.lastname AS lastname, i.label AS label,
479
    i.perso AS perso, l.action AS action, t.title AS folder, i.id AS id
480
    FROM '.prefixTable('log_items').' AS l
481
    INNER JOIN '.prefixTable('items').' AS i ON (l.id_item=i.id)
482
    INNER JOIN '.prefixTable('users').' AS u ON (l.id_user=u.id)
483
    INNER JOIN '.prefixTable('nested_tree').' AS t ON (i.id_tree=t.id)
484
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
485
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
486
487
    // Get the records
488
    $rows = DB::query($sql, ...$params);
489
    $iFilteredTotal = DB::count();
490
491
    // Output
492
    $sOutput = '{';
493
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
494
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
495
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
496
    $sOutput .= '"aaData": [ ';
497
    foreach ($rows as $record) {
498
        $get_item_in_list = true;
499
        $sOutput_item = '[';
500
        //col1
501
        $sOutput_item .= '"'.date($SETTINGS['date_format'].' '.$SETTINGS['time_format'], (int) $record['date']).'", ';
502
        //col3
503
        $sOutput_item .= '"'.trim(htmlspecialchars(stripslashes((string) $record['id']), ENT_QUOTES)).'", ';
504
        //col3
505
        $sOutput_item .= '"'.trim(htmlspecialchars(stripslashes((string) $record['label']), ENT_QUOTES)).'", ';
506
        //col2
507
        $sOutput_item .= '"'.trim(htmlspecialchars(stripslashes((string) $record['folder']), ENT_QUOTES)).'", ';
508
        //col2
509
        $sOutput_item .= '"'.trim(htmlspecialchars(stripslashes((string) $record['name']), ENT_QUOTES)).' '.trim(htmlspecialchars(stripslashes((string) $record['lastname']), ENT_QUOTES)).' ['.trim(htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES)).']", ';
510
        //col4
511
        $sOutput_item .= '"'.trim(htmlspecialchars(stripslashes($lang->get($record['action'])), ENT_QUOTES)).'", ';
512
        //col5
513
        if ($record['perso'] === 1) {
514
            $sOutput_item .= '"'.trim(htmlspecialchars(stripslashes($lang->get('yes')), ENT_QUOTES)).'"';
515
        } else {
516
            $sOutput_item .= '"'.trim(htmlspecialchars(stripslashes($lang->get('no')), ENT_QUOTES)).'"';
517
        }
518
519
        //Finish the line
520
        $sOutput_item .= '], ';
521
        if ($get_item_in_list === true) {
522
            $sOutput .= $sOutput_item;
523
        }
524
    }
525
    if ($iFilteredTotal > 0) {
526
        $sOutput = substr_replace($sOutput, '', -2);
527
    }
528
    $sOutput .= '] }';
529
/* FAILED AUTHENTICATION */
530
} elseif (isset($params['action']) && $params['action'] === 'failed_auth') {
531
    //Columns name
532
    $aColumns = ['l.date', 'l.label', 'l.qui', 'l.field_1'];
533
534
    // Ordering
535
    $orderColumn = $aColumns[0];
536
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
537
        $orderColumn = $aColumns[$params['order'][0]['column']];
538
    }
539
540
    // Filtering
541
    $sWhere = new WhereClause('AND');
542
    if ($searchValue !== '') {        
543
        $subclause = $sWhere->addClause('OR');
544
        foreach ($aColumns as $column) {
545
            $subclause->add($column.' LIKE %ss', $searchValue);
546
        }
547
    }
548
    $sWhere->add('l.type IN %ls', ['failed_auth', 'user_connection']);
549
    $sWhere->add('l.label IN %ls', ['password_is_not_correct', 'user_not_exists']);
550
551
    // Get the total number of records
552
    $iTotal = DB::queryFirstField(
553
        'SELECT COUNT(*)
554
        FROM '.prefixTable('log_system').' as l
555
        WHERE %l ORDER BY %l %l',
556
        $sWhere,
557
        $orderColumn,
558
        $orderDirection
559
    );
560
561
    // Prepare the SQL query
562
    $sql = 'SELECT l.date as auth_date, l.label as label, l.qui as who, l.field_1
563
    FROM '.prefixTable('log_system').' as l
564
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
565
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
566
567
    // Get the records
568
    $rows = DB::query($sql, ...$params);
569
    $iFilteredTotal = DB::count(); 
570
571
    // Output
572
    if ($iTotal === '') {
573
        $iTotal = 0;
574
    }
575
    $sOutput = '{';
576
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
577
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
578
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
579
    $sOutput .= '"aaData": ';
580
    if ($iFilteredTotal > 0) {
581
        $sOutput .= '[';
582
    }
583
    foreach ($rows as $record) {
584
        $sOutput .= '[';
585
        //col1
586
        $sOutput .= '"'.date($SETTINGS['date_format'].' '.$SETTINGS['time_format'], (int) $record['auth_date']).'", ';
587
        //col2 - 3
588
        if ($record['label'] === 'password_is_not_correct' || $record['label'] === 'user_not_exists') {
589
            $sOutput .= '"'.$lang->get($record['label']).'", "'.$record['field_1'].'", ';
590
        } else {
591
            $sOutput .= '"'.$lang->get($record['label']).'", "", ';
592
        }
593
594
        //col3
595
        $sOutput .= '"'.htmlspecialchars(stripslashes((string) $record['who']), ENT_QUOTES).'"';
596
        //Finish the line
597
        $sOutput .= '],';
598
    }
599
600
    if (count($rows) > 0) {
601
        $sOutput = substr_replace($sOutput, '', -1);
602
        $sOutput .= '] }';
603
    } else {
604
        $sOutput .= '[] }';
605
    }
606
} elseif (isset($params['action']) && $params['action'] === 'errors') {
607
    //Columns name
608
    $aColumns = ['l.date', 'l.label', 'l.qui', 'u.login', 'u.name', 'u.lastname'];
609
610
    // Ordering
611
    $orderColumn = $aColumns[0];
612
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
613
        $orderColumn = $aColumns[$params['order'][0]['column']];
614
    }
615
616
    // Filtering
617
    $sWhere = new WhereClause('AND');
618
    if ($searchValue !== '') {        
619
        $subclause = $sWhere->addClause('OR');
620
        foreach ($aColumns as $column) {
621
            $subclause->add($column.' LIKE %ss', $searchValue);
622
        }
623
    }
624
    $sWhere->add('l.type = %s', 'error');
625
626
    // Get the total number of records
627
    $iTotal = DB::queryFirstField(
628
        'SELECT COUNT(*)
629
            FROM '.prefixTable('log_system').' as l
630
            INNER JOIN '.prefixTable('users').' as u ON (l.qui=u.id) 
631
            WHERE %l ORDER BY %l %l',
632
            $sWhere,
633
            $orderColumn,
634
            $orderDirection
635
    );
636
    $iTotal = DB::count();
637
638
    // Prepare the SQL query
639
    $sql = 'SELECT l.date as date, l.label as label, l.qui as who,
640
    u.login as login, u.name AS name, u.lastname AS lastname
641
    FROM '.prefixTable('log_system').' as l
642
    INNER JOIN '.prefixTable('users').' as u ON (l.qui=u.id) 
643
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
644
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
645
646
    // Get the records
647
    $rows = DB::query($sql, ...$params);
648
    $iFilteredTotal = DB::count();
649
650
    // Output
651
    $sOutput = '{';
652
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
653
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
654
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
655
    $sOutput .= '"aaData": ';
656
    if ($iFilteredTotal > 0) {
657
        $sOutput .= '[';
658
    }
659
    foreach ($rows as $record) {
660
        $sOutput .= '[';
661
        //col1
662
        $sOutput .= '"'.date($SETTINGS['date_format'].' '.$SETTINGS['time_format'], (int) $record['date']).'", ';
663
        //col2
664
        $sOutput .= '"'.addslashes(str_replace([chr(10), chr(13), '`', '<br />@', "'"], ['<br>', '<br>', "'", '', '&#39;'], $record['label'])).'", ';
665
        //col3
666
        $sOutput .= '"'.htmlspecialchars(stripslashes((string) $record['name']), ENT_QUOTES).' '.htmlspecialchars(stripslashes((string) $record['lastname']), ENT_QUOTES).' ['.htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES).']"';
667
        //Finish the line
668
        $sOutput .= '],';
669
    }
670
671
    if (count($rows) > 0) {
672
        $sOutput = substr_replace($sOutput, '', -1);
673
        $sOutput .= '] }';
674
    } else {
675
        $sOutput .= '[] }';
676
    }
677
} elseif (isset($params['action']) && $params['action'] === 'items_in_edition') {
678
    //Columns name
679
    $aColumns = ['e.timestamp', 'u.login', 'i.label', 'u.name', 'u.lastname'];
680
681
    // Ordering
682
    $orderColumn = $aColumns[0];
683
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
684
        $orderColumn = $aColumns[$params['order'][0]['column']];
685
    }
686
687
    // Filtering
688
    $sWhere = new WhereClause('OR');
689
    if ($searchValue !== '') {        
690
        foreach ($aColumns as $column) {
691
            $sWhere->add($column.' LIKE %ss', $searchValue);
692
        }
693
    }
694
695
    // Get the total number of records
696
    $iTotal = DB::queryFirstField(
697
        'SELECT COUNT(*)
698
        FROM '.prefixTable('items_edition').' AS e
699
        INNER JOIN '.prefixTable('items').' as i ON (e.item_id=i.id)
700
        INNER JOIN '.prefixTable('users').' as u ON (e.user_id=u.id)
701
        WHERE %l ORDER BY %l %l',
702
        $sWhere,
703
        $orderColumn,
704
        $orderDirection
705
    );
706
707
    // Prepare the SQL query
708
    $sql = 'SELECT e.timestamp, e.item_id, e.user_id, u.login, u.name, u.lastname, i.label
709
    FROM '.prefixTable('items_edition').' AS e
710
    INNER JOIN '.prefixTable('items').' as i ON (e.item_id=i.id)
711
    INNER JOIN '.prefixTable('users').' as u ON (e.user_id=u.id)
712
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
713
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
714
715
    // Get the records
716
    $rows = DB::query($sql, ...$params);
717
    $iFilteredTotal = DB::count();
718
719
    // Output
720
    $sOutput = '{';
721
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
722
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
723
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
724
    $sOutput .= '"aaData": ';
725
    if ($iFilteredTotal > 0) {
726
        $sOutput .= '[';
727
    }
728
    foreach ($rows as $record) {
729
        $sOutput .= '[';
730
        //col1
731
        $sOutput .= '"<span data-id=\"'.$record['item_id'].'\">", ';
732
        //col2
733
        $time_diff = intval(time() - $record['timestamp']);
734
        $hoursDiff = round($time_diff / 3600, 0, PHP_ROUND_HALF_DOWN);
735
        $minutesDiffRemainder = floor($time_diff % 3600 / 60);
736
        $sOutput .= '"'.$hoursDiff.'h '.$minutesDiffRemainder.'m'.'", ';
737
        //col3
738
        $sOutput .= '"'.htmlspecialchars(stripslashes((string) $record['name']), ENT_QUOTES).' '.htmlspecialchars(stripslashes((string) $record['lastname']), ENT_QUOTES).' ['.htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES).']", ';
739
        //col5 - TAGS
740
        $sOutput .= '"'.htmlspecialchars(stripslashes((string) $record['label']), ENT_QUOTES).' ['.$record['item_id'].']"';
741
        //Finish the line
742
        $sOutput .= '],';
743
    }
744
745
    if (count($rows) > 0) {
746
        $sOutput = substr_replace($sOutput, '', -1);
747
        $sOutput .= '] }';
748
    } else {
749
        $sOutput .= '[] }';
750
    }
751
} elseif (isset($params['action']) && $params['action'] === 'users_logged_in') {
752
    //Columns name
753
    $aColumns = ['login', 'name', 'lastname', 'timestamp', 'last_connexion'];
754
755
    // Ordering
756
    $orderColumn = $aColumns[0];
757
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
758
        $orderColumn = $aColumns[$params['order'][0]['column']];
759
    }    
760
761
    // Filtering
762
    $sWhere = new WhereClause('AND');
763
    if ($searchValue !== '') {        
764
        $subclause = $sWhere->addClause('OR');
765
        foreach ($aColumns as $column) {
766
            $subclause->add($column.' LIKE %ss', $searchValue);
767
        }
768
    }
769
    $subclause2 = $sWhere->addClause('OR');
770
    $subclause2->add('session_end >= %i', time());
771
772
    // Get the total number of records
773
    $iTotal = DB::queryFirstField(
774
        'SELECT COUNT(*)
775
        FROM '.prefixTable('users').'
776
        WHERE %l ORDER BY %l %l',
777
        $sWhere,
778
        $orderColumn,
779
        $orderDirection
780
    );
781
782
    // Prepare the SQL query
783
    $sql = 'SELECT *
784
    FROM '.prefixTable('users').'
785
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
786
    $params = [$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
787
788
    // Get the records
789
    $rows = DB::query($sql, ...$params);
790
    $iFilteredTotal = DB::count();
791
792
    // Output
793
    $sOutput = '{';
794
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
795
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
796
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
797
    $sOutput .= '"aaData": ';
798
    if ($iFilteredTotal > 0) {
799
        $sOutput .= '[';
800
    }
801
    foreach ($rows as $record) {
802
        $sOutput .= '[';
803
        //col1
804
        $sOutput .= '"<span data-id=\"'.$record['id'].'\">", ';
805
        //col2
806
        $sOutput .= '"'.htmlspecialchars(stripslashes((string) $record['name']), ENT_QUOTES).' '.htmlspecialchars(stripslashes((string) $record['lastname']), ENT_QUOTES).' ['.htmlspecialchars(stripslashes((string) $record['login']), ENT_QUOTES).']", ';
807
        //col3
808
        if ($record['admin'] === '1') {
809
            $user_role = $lang->get('god');
810
        } elseif ($lang->get('gestionnaire') === 1) {
811
            $user_role = $lang->get('gestionnaire');
812
        } else {
813
            $user_role = $lang->get('user');
814
        }
815
        $sOutput .= '"'.$user_role.'", ';
816
        //col4
817
        $time_diff = time() - (int) $record['timestamp'];
818
        $hoursDiff = round($time_diff / 3600, 0, PHP_ROUND_HALF_DOWN);
819
        $minutesDiffRemainder = floor($time_diff % 3600 / 60);
820
        $sOutput .= '"'.$hoursDiff.'h '.$minutesDiffRemainder.'m" ';
821
        //Finish the line
822
        $sOutput .= '],';
823
    }
824
825
    if (count($rows) > 0) {
826
        $sOutput = substr_replace($sOutput, '', -1);
827
        $sOutput .= '] }';
828
    } else {
829
        $sOutput .= '[] }';
830
    }
831
} elseif (isset($params['action']) && $params['action'] === 'tasks_in_progress') {
832
    //Columns name
833
    $aColumns = ['p.increment_id', 'p.created_at', 'p.updated_at', 'p.process_type', 'p.is_in_progress'];
834
835
    // Ordering
836
    $orderColumn = $aColumns[0];
837
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
838
        $orderColumn = $aColumns[$params['order'][0]['column']];
839
    }    
840
841
    // Filtering
842
    $sWhere = new WhereClause('AND');
843
    if ($searchValue !== '') {        
844
        $subclause = $sWhere->addClause('OR');
845
        foreach ($aColumns as $column) {
846
            $subclause->add($column.' LIKE %ss', $searchValue);
847
        }
848
    }
849
    $subclause2 = $sWhere->addClause('OR');
850
    $subclause2->add('p.finished_at = ""');
851
    $subclause2->add('p.finished_at IS NULL');
852
853
    // Get the total number of records
854
    $iTotal = DB::queryFirstField(
855
        'SELECT COUNT(*)
856
        FROM '.prefixTable('background_tasks').' AS p 
857
        LEFT JOIN '.prefixTable('users').' AS u ON %l
858
        WHERE %l ORDER BY %l %l',
859
        'u.id = json_extract(p.arguments, "$[0]")',
860
        $sWhere,
861
        $orderColumn,
862
        $orderDirection
863
    );
864
865
    // Prepare the SQL query
866
    $sql = 'SELECT p.increment_id, p.created_at, p.updated_at, p.process_type,
867
                p.is_in_progress, p.arguments
868
            FROM '.prefixTable('background_tasks').' AS p 
869
            LEFT JOIN '.prefixTable('users').' AS u ON %l
870
            WHERE %l ORDER BY %l %l LIMIT %i, %i';
871
    $params = ['u.id = json_extract(p.arguments, "$[0]")',$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
872
873
    // Get the records
874
    $rows = DB::query($sql, ...$params);
875
    $iFilteredTotal = DB::count();
876
877
    // Output
878
    $sOutput = '{';
879
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
880
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
881
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
882
    $sOutput .= '"aaData": ';
883
    if ($iFilteredTotal > 0) {
884
        $sOutput .= '[';
885
    }
886
    foreach ($rows as $record) {
887
        // Get subtask progress
888
        $subtaskProgress = getSubtaskProgress($record['increment_id']);        
889
890
        $sOutput .= '[';
891
        //col1
892
        $sOutput .= '"<span data-done=\"'.$record['is_in_progress'].'\" data-type=\"'.$record['process_type'].'\" data-process-id=\"'.$record['increment_id'].'\"></span>", ';
893
        //col2
894
        $sOutput .= '"'.date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['created_at']).'", ';
895
        //col3
896
        //$sOutput .= '"'.($record['updated_at'] === '' ? '-' : date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['updated_at'])).'", ';
897
        $sOutput .= '"<div class=\"progress mt-2\"><div class=\"progress-bar\" style=\"width: '.$subtaskProgress.'\">'.$subtaskProgress.'</div></div>", ';
898
        //col4
899
        $sOutput .= '"'.$record['process_type'].'", ';
900
901
        // col5
902
        if (in_array($record['process_type'], array('create_user_keys', 'item_copy')) === true) {
903
            $data_user = DB::queryFirstRow(
904
                'SELECT name, lastname FROM ' . prefixTable('users') . '
905
                WHERE id = %i',
906
                json_decode($record['arguments'], true)['new_user_id']
907
            );
908
            $sOutput .= '"'.$data_user['name'].' '.$data_user['lastname'].'", ';
909
        } elseif ($record['process_type'] === 'send_email') {
910
            $sOutput .= '"'.json_decode($record['arguments'], true)['receiver_name'].'", ';
911
        }
912
        // col6
913
        $sOutput .= '""';
914
        //Finish the line
915
        $sOutput .= '],';
916
    }
917
918
    if (count($rows) > 0) {
919
        $sOutput = substr_replace($sOutput, '', -1);
920
        $sOutput .= '] }';
921
    } else {
922
        $sOutput .= '[] }';
923
    }
924
} elseif (isset($params['action']) && $params['action'] === 'tasks_finished') {
925
    //Columns name
926
    $aColumns = ['p.created_at', 'p.finished_at', 'p.process_type', 'u.name'];
927
928
    // Ordering
929
    $orderColumn = $aColumns[0];
930
    if (isset($aColumns[$params['order'][0]['column']]) === true) {
931
        $orderColumn = $aColumns[$params['order'][0]['column']];
932
    }
933
934
    // Filtering
935
    $sWhere = new WhereClause('AND');
936
    if ($searchValue !== '') {        
937
        $subclause = $sWhere->addClause('OR');
938
        foreach ($aColumns as $column) {
939
            $subclause->add($column.' LIKE %ss', $searchValue);
940
        }
941
    }
942
    $sWhere->add('finished_at != ""');
943
944
    // Get the total number of records
945
    $iTotal = DB::queryFirstField(
946
        'SELECT COUNT(*)
947
        FROM '.prefixTable('background_tasks').' AS p 
948
        LEFT JOIN '.prefixTable('users').' AS u ON u.id = json_extract(p.arguments, "$[0]")
949
        WHERE %l ORDER BY %l %l',
950
        $sWhere,
951
        $orderColumn,
952
        $orderDirection
953
    );
954
955
    // Prepare the SQL query
956
    $sql = 'SELECT p.*
957
    FROM '.prefixTable('background_tasks').' AS p 
958
    LEFT JOIN '.prefixTable('users').' AS u ON %l
959
    WHERE %l ORDER BY %l %l LIMIT %i, %i';
960
    $params = ['u.id = json_extract(p.arguments, "$[0]")',$sWhere, $orderColumn, $orderDirection, $sLimitStart, $sLimitLength];
961
962
    // Get the records
963
    $rows = DB::query($sql, ...$params);
964
    $iFilteredTotal = DB::count();
965
966
    // Output
967
    $sOutput = '{';
968
    $sOutput .= '"sEcho": '. (int) $request->query->filter('draw', FILTER_SANITIZE_NUMBER_INT) . ', ';
969
    $sOutput .= '"iTotalRecords": '.$iTotal.', ';
970
    $sOutput .= '"iTotalDisplayRecords": '.$iTotal.', ';
971
    $sOutput .= '"aaData": ';
972
    if ($iFilteredTotal > 0) {
973
        $sOutput .= '[';
974
    }
975
    foreach ($rows as $record) {
976
        // play with dates
977
        $start = strtotime(date('Y-m-d H:i:s', (int) $record['created_at']));
978
        $end = strtotime(date('Y-m-d H:i:s', (int) $record['finished_at']));
979
        
980
        $sOutput .= '[';
981
        //col1
982
        $sOutput .= '"'.(is_null($record['error_message']) ? '' : addslashes($record['error_message'])).'", ';
983
        //col2
984
        $sOutput .= '"'.date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['created_at']).'", ';
985
        //col3
986
        $sOutput .= is_null($record['started_at']) === false ?
987
            ('"'.date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['started_at']).'", ') :
988
                ('"'.date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['created_at']).'", ');
989
        //col4
990
        $sOutput .= '"'.date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) $record['finished_at']).'", '; 
991
        // col7
992
        $sOutput .= '"'.gmdate('H:i:s', (int) $record['finished_at'] - (is_null($record['started_at']) === false ? (int) $record['started_at'] : (int) $record['created_at'])).'",';
993
        //col5
994
        if ($record['process_type'] === 'create_user_keys') {
995
            $processIcon = '<i class=\"fa-solid fa-user-plus infotip\" style=\"cursor: pointer;\" title=\"'.$lang->get('user_creation').'\"></i>';
996
        } else if ($record['process_type'] === 'send_email') {
997
            $processIcon = '<i class=\"fa-solid fa-envelope-circle-check infotip\" style=\"cursor: pointer;\" title=\"'.$lang->get('send_email_to_user').'\"></i>';
998
        } else if ($record['process_type'] === 'user_build_cache_tree') {
999
            $processIcon = '<i class=\"fa-solid fa-folder-tree infotip\" style=\"cursor: pointer;\" title=\"'.$lang->get('reload_user_cache_table').'\"></i>';
1000
        } else if ($record['process_type'] === 'item_copy') {
1001
            $processIcon = '<i class=\"fa-solid fa-copy infotip\" style=\"cursor: pointer;\" title=\"'.$lang->get('item_copied').'\"></i>';
1002
        } else if ($record['process_type'] === 'item_update_create_keys') {
1003
            $processIcon = '<i class=\"fa-solid fa-pencil infotip\" style=\"cursor: pointer;\" title=\"'.$lang->get('item_updated').'\"></i>';
1004
        } else if ($record['process_type'] === 'new_item') {
1005
            $processIcon = '<i class=\"fa-solid fa-square-plus infotip\" style=\"cursor: pointer;\" title=\"'.$lang->get('new_item').'\"></i>';
1006
        } else {
1007
            $processIcon = '<i class=\"fa-solid fa-question\"></i> ('.$record['process_type'].')';
1008
        }
1009
        $sOutput .= '"'.$processIcon.'", ';
1010
        // col6
1011
        $arguments = json_decode($record['arguments'], true);
1012
        $newUserId = array_key_exists('new_user_id', $arguments) ? 
1013
            $arguments['new_user_id'] : 
1014
            (array_key_exists('user_id', $arguments) ? $arguments['user_id'] : null);
1015
        if ($record['process_type'] === 'create_user_keys' && is_null($newUserId) === false && empty($newUserId) === false) {
1016
            $data_user = DB::queryFirstRow(
1017
                'SELECT name, lastname, login FROM ' . prefixTable('users') . '
1018
                WHERE id = %i',
1019
                $newUserId
1020
            );
1021
            if (DB::count() > 0) {
1022
                $txt = (isset($data_user['name']) === true ? $data_user['name'] : '').(isset($data_user['lastname']) === true ? ' '.$data_user['lastname'] : '');
1023
                $sOutput .= '"'.(empty($txt) === false ? $txt : $data_user['login']).'"';
1024
            } else {
1025
                $sOutput .= '"<i class=\"fa-solid fa-user-slash\"></i>"';
1026
            }
1027
        } elseif ($record['process_type'] === 'send_email') {
1028
            $user = json_decode($record['arguments'], true)['receiver_name'];
1029
            $sOutput .= '"'.(is_null($user) === true || empty($user) === true ? '<i class=\"fa-solid fa-user-slash\"></i>' : $user).'"';
1030
        } elseif ($record['process_type'] === 'user_build_cache_tree') {
1031
            $user = json_decode($record['arguments'], true)['user_id'];
1032
            $data_user = DB::queryFirstRow(
1033
                'SELECT name, lastname, login FROM ' . prefixTable('users') . '
1034
                WHERE id = %i',
1035
                $user
1036
            );
1037
            if (DB::count() > 0) {
1038
                $txt = (isset($data_user['name']) === true ? $data_user['name'] : '').(isset($data_user['lastname']) === true ? ' '.$data_user['lastname'] : '');
1039
                $sOutput .= '"'.(empty($txt) === false ? $txt : $data_user['login']).'"';
1040
            } else {
1041
                $sOutput .= '"<i class=\"fa-solid fa-user-slash\"></i>"';
1042
            }
1043
        } else {
1044
            $sOutput .= '"<i class=\"fa-solid fa-user-slash\"></i>"';
1045
        }
1046
        //Finish the line
1047
        $sOutput .= '],';
1048
    }
1049
1050
    if (count($rows) > 0) {
1051
        $sOutput = substr_replace($sOutput, '', -1);
1052
        $sOutput .= '] }';
1053
    } else {
1054
        $sOutput .= '[] }';
1055
    }
1056
}
1057
1058
// deepcode ignore XSS: data comes from database. Before being stored it is clean with feature antiXss->xss_clean
1059
echo (string) $sOutput;
1060
1061
1062
1063
function getSubtaskProgress($id)
1064
{
1065
    $subtasks = DB::query(
1066
        'SELECT *
1067
        FROM ' . prefixTable('background_subtasks') . '
1068
        WHERE task_id = %i',
1069
        $id
1070
    );
1071
1072
    $i = 0;
1073
    $nb = count($subtasks);
1074
    $finished_nb = 0;
1075
    foreach ($subtasks as $task) {
1076
        if (is_null($task['finished_at']) === false) {
1077
            $finished_nb++;
1078
        }
1079
1080
        $i++;
1081
    }
1082
1083
    return ($finished_nb !== 0 ? pourcentage($finished_nb, $nb, 100) : 0) .'%';
1084
}
1085