Passed
Push — wip_sessions ( 2f1ef6...1e15ba )
by Nils
06:51
created

buildEmail()   C

Complexity

Conditions 9
Paths 436

Size

Total Lines 66
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 37
c 1
b 0
f 0
nc 436
nop 7
dl 0
loc 66
rs 5.0657

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 * @file      main.functions.php
15
 * ---
16
 *
17
 * @author    Nils Laumaillé ([email protected])
18
 *
19
 * @copyright 2009-2023 Teampass.net
20
 *
21
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
22
 * ---
23
 *
24
 * @see       https://www.teampass.net
25
 */
26
27
use LdapRecord\Connection;
28
use ForceUTF8\Encoding;
29
use Elegant\Sanitizer\Sanitizer;
30
use voku\helper\AntiXSS;
31
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
32
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
33
use TeampassClasses\SuperGlobal\SuperGlobal;
34
use TeampassClasses\SessionManager\SessionManager;
35
use TeampassClasses\Language\Language;
36
use TeampassClasses\NestedTree\NestedTree;
37
use Defuse\Crypto\Key;
38
use Defuse\Crypto\Crypto;
39
use Defuse\Crypto\KeyProtectedByPassword;
40
use Defuse\Crypto\File as CryptoFile;
41
use Defuse\Crypto\Exception as CryptoException;
42
use PHPMailer\PHPMailer\PHPMailer;
43
use PasswordLib\PasswordLib;
44
use Symfony\Component\Process\Exception\ProcessFailedException;
45
use Symfony\Component\Process\Process;
46
use Symfony\Component\Process\PhpExecutableFinder;
47
use TeampassClasses\Encryption\Encryption;
48
49
50
// Load config if $SETTINGS not defined
51
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
52
    include_once __DIR__ . '/../includes/config/tp.config.php';
53
}
54
55
header('Content-type: text/html; charset=utf-8');
56
header('Cache-Control: no-cache, must-revalidate');
57
58
59
60
loadClasses('DB');
61
62
63
/**
64
 * genHash().
65
 *
66
 * Generate a hash for user login
67
 *
68
 * @param string $password What password
69
 * @param string $cost     What cost
70
 *
71
 * @return string|void
72
 */
73
function bCrypt(
74
    string $password,
75
    string $cost
76
): ?string
77
{
78
    $salt = sprintf('$2y$%02d$', $cost);
79
    if (function_exists('openssl_random_pseudo_bytes')) {
80
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
81
    } else {
82
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
83
        for ($i = 0; $i < 22; ++$i) {
84
            $salt .= $chars[mt_rand(0, 63)];
85
        }
86
    }
87
88
    return crypt($password, $salt);
89
}
90
91
/**
92
 * Checks if a string is hex encoded
93
 *
94
 * @param string $str
95
 * @return boolean
96
 */
97
function isHex(string $str): bool
98
{
99
    if (str_starts_with(strtolower($str), '0x')) {
100
        $str = substr($str, 2);
101
    }
102
103
    return ctype_xdigit($str);
104
}
105
106
/**
107
 * Defuse cryption function.
108
 *
109
 * @param string $message   what to de/crypt
110
 * @param string $ascii_key key to use
111
 * @param string $type      operation to perform
112
 * @param array  $SETTINGS  Teampass settings
113
 *
114
 * @return array
115
 */
116
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
117
{
118
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
119
    $err = false;
120
    
121
    // convert KEY
122
    $key = Key::loadFromAsciiSafeString($ascii_key);
123
    try {
124
        if ($type === 'encrypt') {
125
            $text = Crypto::encrypt($message, $key);
126
        } elseif ($type === 'decrypt') {
127
            $text = Crypto::decrypt($message, $key);
128
        }
129
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
130
        $err = 'an attack! either the wrong key was loaded, or the ciphertext has changed since it was created either corrupted in the database or intentionally modified by someone trying to carry out an attack.';
131
    } catch (CryptoException\BadFormatException $ex) {
132
        $err = $ex;
133
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
134
        $err = $ex;
135
    } catch (CryptoException\CryptoException $ex) {
136
        $err = $ex;
137
    } catch (CryptoException\IOException $ex) {
138
        $err = $ex;
139
    }
140
141
    return [
142
        'string' => $text ?? '',
143
        'error' => $err,
144
    ];
145
}
146
147
/**
148
 * Generating a defuse key.
149
 *
150
 * @return string
151
 */
152
function defuse_generate_key()
153
{
154
    $key = Key::createNewRandomKey();
155
    $key = $key->saveToAsciiSafeString();
156
    return $key;
157
}
158
159
/**
160
 * Generate a Defuse personal key.
161
 *
162
 * @param string $psk psk used
163
 *
164
 * @return string
165
 */
166
function defuse_generate_personal_key(string $psk): string
167
{
168
    $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
169
    return $protected_key->saveToAsciiSafeString(); // save this in user table
170
}
171
172
/**
173
 * Validate persoanl key with defuse.
174
 *
175
 * @param string $psk                   the user's psk
176
 * @param string $protected_key_encoded special key
177
 *
178
 * @return string
179
 */
180
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
181
{
182
    try {
183
        $protected_key_encoded = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
184
        $user_key = $protected_key_encoded->unlockKey($psk);
185
        $user_key_encoded = $user_key->saveToAsciiSafeString();
186
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
187
        return 'Error - Major issue as the encryption is broken.';
188
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
189
        return 'Error - The saltkey is not the correct one.';
190
    }
191
192
    return $user_key_encoded;
193
    // store it in session once user has entered his psk
194
}
195
196
/**
197
 * Decrypt a defuse string if encrypted.
198
 *
199
 * @param string $value Encrypted string
200
 *
201
 * @return string Decrypted string
202
 */
203
function defuseReturnDecrypted(string $value, $SETTINGS): string
204
{
205
    if (substr($value, 0, 3) === 'def') {
206
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
207
    }
208
209
    return $value;
210
}
211
212
/**
213
 * Trims a string depending on a specific string.
214
 *
215
 * @param string|array $chaine  what to trim
216
 * @param string       $element trim on what
217
 *
218
 * @return string
219
 */
220
function trimElement($chaine, string $element): string
221
{
222
    if (! empty($chaine)) {
223
        if (is_array($chaine) === true) {
224
            $chaine = implode(';', $chaine);
225
        }
226
        $chaine = trim($chaine);
227
        if (substr($chaine, 0, 1) === $element) {
228
            $chaine = substr($chaine, 1);
229
        }
230
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
231
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
232
        }
233
    }
234
235
    return $chaine;
236
}
237
238
/**
239
 * Permits to suppress all "special" characters from string.
240
 *
241
 * @param string $string  what to clean
242
 * @param bool   $special use of special chars?
243
 *
244
 * @return string
245
 */
246
function cleanString(string $string, bool $special = false): string
247
{
248
    // Create temporary table for special characters escape
249
    $tabSpecialChar = [];
250
    for ($i = 0; $i <= 31; ++$i) {
251
        $tabSpecialChar[] = chr($i);
252
    }
253
    array_push($tabSpecialChar, '<br />');
254
    if ((int) $special === 1) {
255
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
256
    }
257
258
    return str_replace($tabSpecialChar, "\n", $string);
259
}
260
261
/**
262
 * Erro manager for DB.
263
 *
264
 * @param array $params output from query
265
 *
266
 * @return void
267
 */
268
function db_error_handler(array $params): void
269
{
270
    echo 'Error: ' . $params['error'] . "<br>\n";
271
    echo 'Query: ' . $params['query'] . "<br>\n";
272
    throw new Exception('Error - Query', 1);
273
}
274
275
/**
276
 * Identify user's rights
277
 *
278
 * @param string|array $groupesVisiblesUser  [description]
279
 * @param string|array $groupesInterditsUser [description]
280
 * @param string       $isAdmin              [description]
281
 * @param string       $idFonctions          [description]
282
 *
283
 * @return bool
284
 */
285
function identifyUserRights(
286
    $groupesVisiblesUser,
287
    $groupesInterditsUser,
288
    $isAdmin,
289
    $idFonctions,
290
    $SETTINGS
291
) {
292
    $session = SessionManager::getSession();
293
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
294
295
    // Check if user is ADMINISTRATOR    
296
    (int) $isAdmin === 1 ?
297
        identAdmin(
298
            $idFonctions,
299
            $SETTINGS, /** @scrutinizer ignore-type */
300
            $tree
301
        )
302
        :
303
        identUser(
304
            $groupesVisiblesUser,
305
            $groupesInterditsUser,
306
            $idFonctions,
307
            $SETTINGS, /** @scrutinizer ignore-type */
308
            $tree
309
        );
310
311
    // update user's timestamp
312
    DB::update(
313
        prefixTable('users'),
314
        [
315
            'timestamp' => time(),
316
        ],
317
        'id=%i',
318
        $session->get('user-id')
319
    );
320
321
    return true;
322
}
323
324
/**
325
 * Identify administrator.
326
 *
327
 * @param string $idFonctions Roles of user
328
 * @param array  $SETTINGS    Teampass settings
329
 * @param object $tree        Tree of folders
330
 *
331
 * @return bool
332
 */
333
function identAdmin($idFonctions, $SETTINGS, $tree)
334
{
335
    
336
    $session = SessionManager::getSession();
337
    $groupesVisibles = [];
338
    $session->set('user-personal_folders', []);
339
    $session->set('user-accessible_folders', []);
340
    $session->set('user-no_access_folders', []);
341
    $session->set('user-personal_visible_folders', []);
342
    $session->set('user-read_only_folders', []);
343
    $session->set('system-list_restricted_folders_for_items', []);
344
    $session->set('system-list_folders_editable_by_role', []);
345
    $session->set('user-list_folders_limited', []);
346
    $session->set('user-forbiden_personal_folders', []);
347
    $globalsUserId = $session->get('user-id');
348
    $globalsVisibleFolders = $session->get('user-accessible_folders');
349
    $globalsPersonalVisibleFolders = $session->get('user-personal_visible_folders');
350
    // Get list of Folders
351
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
352
    foreach ($rows as $record) {
353
        array_push($groupesVisibles, $record['id']);
354
    }
355
    $session->set('user-accessible_folders', $groupesVisibles);
356
    $session->set('user-all_non_personal_folders', $groupesVisibles);
357
    // Exclude all PF
358
    $where = new WhereClause('and');
359
    // create a WHERE statement of pieces joined by ANDs
360
    $where->add('personal_folder=%i', 1);
361
    if (
362
        isset($SETTINGS['enable_pf_feature']) === true
363
        && (int) $SETTINGS['enable_pf_feature'] === 1
364
    ) {
365
        $where->add('title=%s', $globalsUserId);
366
        $where->negateLast();
367
    }
368
    // Get ID of personal folder
369
    $persfld = DB::queryfirstrow(
370
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
371
        $globalsUserId
372
    );
373
    if (empty($persfld['id']) === false) {
374
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
375
            array_push($globalsVisibleFolders, $persfld['id']);
376
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
377
            // get all descendants
378
            $tree->rebuild();
379
            $tst = $tree->getDescendants($persfld['id']);
380
            foreach ($tst as $t) {
381
                array_push($globalsVisibleFolders, $t->id);
382
                array_push($globalsPersonalVisibleFolders, $t->id);
383
            }
384
        }
385
    }
386
387
    // get complete list of ROLES
388
    $tmp = explode(';', $idFonctions);
389
    $rows = DB::query(
390
        'SELECT * FROM ' . prefixTable('roles_title') . '
391
        ORDER BY title ASC'
392
    );
393
    foreach ($rows as $record) {
394
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
395
            array_push($tmp, $record['id']);
396
        }
397
    }
398
    $session->set('user-roles', implode(';', $tmp));
399
    $session->set('user-admin', 1);
400
    // Check if admin has created Folders and Roles
401
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
402
    $session->set('user-nb_folders', DB::count());
403
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
404
    $session->set('user-nb_roles', DB::count());
405
406
    return true;
407
}
408
409
/**
410
 * Permits to convert an element to array.
411
 *
412
 * @param string|array $element Any value to be returned as array
413
 *
414
 * @return array
415
 */
416
function convertToArray($element): array
417
{
418
    if (is_string($element) === true) {
419
        if (empty($element) === true) {
420
            return [];
421
        }
422
        return explode(
423
            ';',
424
            trimElement($element, ';')
425
        );
426
    }
427
    return $element;
428
}
429
430
/**
431
 * Defines the rights the user has.
432
 *
433
 * @param string|array $allowedFolders  Allowed folders
434
 * @param string|array $noAccessFolders Not allowed folders
435
 * @param string|array $userRoles       Roles of user
436
 * @param array        $SETTINGS        Teampass settings
437
 * @param object       $tree            Tree of folders
438
 * 
439
 * @return bool
440
 */
441
function identUser(
442
    $allowedFolders,
443
    $noAccessFolders,
444
    $userRoles,
445
    array $SETTINGS,
446
    object $tree
447
) {
448
    
449
    $session = SessionManager::getSession();
450
    // Init
451
    $session->set('user-accessible_folders', []);
452
    $session->set('user-personal_folders', []);
453
    $session->set('user-no_access_folders', []);
454
    $session->set('user-personal_visible_folders', []);
455
    $session->set('user-read_only_folders', []);
456
    $session->set('user-user-roles', $userRoles);
457
    $session->set('user-admin', 0);
458
    // init
459
    $personalFolders = [];
460
    $readOnlyFolders = [];
461
    $noAccessPersonalFolders = [];
462
    $restrictedFoldersForItems = [];
463
    $foldersLimited = [];
464
    $foldersLimitedFull = [];
465
    $allowedFoldersByRoles = [];
466
    $globalsUserId = $session->get('user-id');
467
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
468
    // Ensure consistency in array format
469
    $noAccessFolders = convertToArray($noAccessFolders);
470
    $userRoles = convertToArray($userRoles);
471
    $allowedFolders = convertToArray($allowedFolders);
472
    
473
    // Get list of folders depending on Roles
474
    $arrays = identUserGetFoldersFromRoles(
475
        $userRoles,
476
        $allowedFoldersByRoles,
477
        $readOnlyFolders,
478
        $allowedFolders
479
    );
480
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
481
    $readOnlyFolders = $arrays['readOnlyFolders'];
482
483
    // Does this user is allowed to see other items
484
    $inc = 0;
485
    $rows = DB::query(
486
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
487
            WHERE restricted_to LIKE %ss AND inactif = %s'.
488
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
489
        $globalsUserId,
490
        '0'
491
    );
492
    foreach ($rows as $record) {
493
        // Exclude restriction on item if folder is fully accessible
494
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
495
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
496
            ++$inc;
497
        //}
498
    }
499
500
    // Check for the users roles if some specific rights exist on items
501
    $rows = DB::query(
502
        'SELECT i.id_tree, r.item_id
503
        FROM ' . prefixTable('items') . ' as i
504
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
505
        WHERE i.id_tree <> "" '.
506
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
507
        'ORDER BY i.id_tree ASC',
508
        $userRoles
509
    );
510
    $inc = 0;
511
    foreach ($rows as $record) {
512
        //if (isset($record['id_tree'])) {
513
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
514
            array_push($foldersLimitedFull, $record['id_tree']);
515
            ++$inc;
516
        //}
517
    }
518
519
    // Get list of Personal Folders
520
    $arrays = identUserGetPFList(
521
        $globalsPersonalFolders,
522
        $allowedFolders,
523
        $globalsUserId,
524
        $personalFolders,
525
        $noAccessPersonalFolders,
526
        $foldersLimitedFull,
527
        $allowedFoldersByRoles,
528
        array_keys($restrictedFoldersForItems),
529
        $readOnlyFolders,
530
        $noAccessFolders,
531
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
532
        $tree
533
    );
534
    $allowedFolders = $arrays['allowedFolders'];
535
    $personalFolders = $arrays['personalFolders'];
536
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
537
538
    // Return data
539
    $session->set('user-all_non_personal_folders', $allowedFolders);
540
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
541
    $session->set('user-read_only_folders', $readOnlyFolders);
542
    $session->set('user-no_access_folders', $noAccessFolders);
543
    $session->set('user-personal_folders', $personalFolders);
544
    $session->set('user-list_folders_limited', $foldersLimited);
545
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
546
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
547
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
548
    $session->set(
549
        'all_folders_including_no_access',
550
        array_unique(array_merge(
551
            $allowedFolders,
552
            $personalFolders,
553
            $noAccessFolders,
554
            $readOnlyFolders
555
        ), SORT_NUMERIC)
556
    );
557
    // Folders and Roles numbers
558
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
559
    $session->set('user-nb_folders', DB::count());
560
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
561
    $session->set('user-nb_roles', DB::count());
562
    // check if change proposals on User's items
563
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
564
        $countNewItems = DB::query(
565
            'SELECT COUNT(*)
566
            FROM ' . prefixTable('items_change') . ' AS c
567
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
568
            WHERE i.action = %s AND i.id_user = %i',
569
            'at_creation',
570
            $globalsUserId
571
        );
572
        $session->set('user-nb_item_change_proposals', $countNewItems);
573
    } else {
574
        $session->set('user-nb_item_change_proposals', 0);
575
    }
576
577
    return true;
578
}
579
580
/**
581
 * Get list of folders depending on Roles
582
 * 
583
 * @param array $userRoles
584
 * @param array $allowedFoldersByRoles
585
 * @param array $readOnlyFolders
586
 * @param array $allowedFolders
587
 * 
588
 * @return array
589
 */
590
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
591
{
592
    $rows = DB::query(
593
        'SELECT *
594
        FROM ' . prefixTable('roles_values') . '
595
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
596
        ['W', 'ND', 'NE', 'NDNE', 'R'],
597
        $userRoles,
598
    );
599
    foreach ($rows as $record) {
600
        if ($record['type'] === 'R') {
601
            array_push($readOnlyFolders, $record['folder_id']);
602
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
603
            array_push($allowedFoldersByRoles, $record['folder_id']);
604
        }
605
    }
606
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
607
    $readOnlyFolders = array_unique($readOnlyFolders);
608
    
609
    // Clean arrays
610
    foreach ($allowedFoldersByRoles as $value) {
611
        $key = array_search($value, $readOnlyFolders);
612
        if ($key !== false) {
613
            unset($readOnlyFolders[$key]);
614
        }
615
    }
616
    return [
617
        'readOnlyFolders' => $readOnlyFolders,
618
        'allowedFoldersByRoles' => $allowedFoldersByRoles
619
    ];
620
}
621
622
/**
623
 * Get list of Personal Folders
624
 * 
625
 * @param int $globalsPersonalFolders
626
 * @param array $allowedFolders
627
 * @param int $globalsUserId
628
 * @param array $personalFolders
629
 * @param array $noAccessPersonalFolders
630
 * @param array $foldersLimitedFull
631
 * @param array $allowedFoldersByRoles
632
 * @param array $restrictedFoldersForItems
633
 * @param array $readOnlyFolders
634
 * @param array $noAccessFolders
635
 * @param int $enablePfFeature
636
 * @param object $tree
637
 * 
638
 * @return array
639
 */
640
function identUserGetPFList(
641
    $globalsPersonalFolders,
642
    $allowedFolders,
643
    $globalsUserId,
644
    $personalFolders,
645
    $noAccessPersonalFolders,
646
    $foldersLimitedFull,
647
    $allowedFoldersByRoles,
648
    $restrictedFoldersForItems,
649
    $readOnlyFolders,
650
    $noAccessFolders,
651
    $enablePfFeature,
652
    $tree
653
)
654
{
655
    if (
656
        (int) $enablePfFeature === 1
657
        && (int) $globalsPersonalFolders === 1
658
    ) {
659
        $persoFld = DB::queryfirstrow(
660
            'SELECT id
661
            FROM ' . prefixTable('nested_tree') . '
662
            WHERE title = %s AND personal_folder = %i'.
663
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
664
            $globalsUserId,
665
            1
666
        );
667
        if (empty($persoFld['id']) === false) {
668
            array_push($personalFolders, $persoFld['id']);
669
            array_push($allowedFolders, $persoFld['id']);
670
            // get all descendants
671
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
672
            foreach ($ids as $id) {
673
                //array_push($allowedFolders, $id);
674
                array_push($personalFolders, $id);
675
            }
676
        }
677
    }
678
    
679
    // Exclude all other PF
680
    $where = new WhereClause('and');
681
    $where->add('personal_folder=%i', 1);
682
    if (count($personalFolders) > 0) {
683
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
684
    }
685
    if (
686
        (int) $enablePfFeature === 1
687
        && (int) $globalsPersonalFolders === 1
688
    ) {
689
        $where->add('title=%s', $globalsUserId);
690
        $where->negateLast();
691
    }
692
    $persoFlds = DB::query(
693
        'SELECT id
694
        FROM ' . prefixTable('nested_tree') . '
695
        WHERE %l',
696
        $where
697
    );
698
    foreach ($persoFlds as $persoFldId) {
699
        array_push($noAccessPersonalFolders, $persoFldId['id']);
700
    }
701
702
    // All folders visibles
703
    $allowedFolders = array_unique(array_merge(
704
        $allowedFolders,
705
        $foldersLimitedFull,
706
        $allowedFoldersByRoles,
707
        $restrictedFoldersForItems,
708
        $readOnlyFolders
709
    ), SORT_NUMERIC);
710
    // Exclude from allowed folders all the specific user forbidden folders
711
    if (count($noAccessFolders) > 0) {
712
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
713
    }
714
715
    return [
716
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
717
        'personalFolders' => $personalFolders,
718
        'noAccessPersonalFolders' => $noAccessPersonalFolders
719
    ];
720
}
721
722
723
/**
724
 * Update the CACHE table.
725
 *
726
 * @param string $action   What to do
727
 * @param array  $SETTINGS Teampass settings
728
 * @param int    $ident    Ident format
729
 * 
730
 * @return void
731
 */
732
function updateCacheTable(string $action, ?int $ident = null): void
733
{
734
    if ($action === 'reload') {
735
        // Rebuild full cache table
736
        cacheTableRefresh();
737
    } elseif ($action === 'update_value' && is_null($ident) === false) {
738
        // UPDATE an item
739
        cacheTableUpdate($ident);
740
    } elseif ($action === 'add_value' && is_null($ident) === false) {
741
        // ADD an item
742
        cacheTableAdd($ident);
743
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
744
        // DELETE an item
745
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
746
    }
747
}
748
749
/**
750
 * Cache table - refresh.
751
 *
752
 * @return void
753
 */
754
function cacheTableRefresh(): void
755
{
756
    // Load class DB
757
    loadClasses('DB');
758
759
    //Load Tree
760
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
761
    // truncate table
762
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
763
    // reload date
764
    $rows = DB::query(
765
        'SELECT *
766
        FROM ' . prefixTable('items') . ' as i
767
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
768
        AND l.action = %s
769
        AND i.inactif = %i',
770
        'at_creation',
771
        0
772
    );
773
    foreach ($rows as $record) {
774
        if (empty($record['id_tree']) === false) {
775
            // Get all TAGS
776
            $tags = '';
777
            $itemTags = DB::query(
778
                'SELECT tag
779
                FROM ' . prefixTable('tags') . '
780
                WHERE item_id = %i AND tag != ""',
781
                $record['id']
782
            );
783
            foreach ($itemTags as $itemTag) {
784
                $tags .= $itemTag['tag'] . ' ';
785
            }
786
787
            // Get renewal period
788
            $resNT = DB::queryfirstrow(
789
                'SELECT renewal_period
790
                FROM ' . prefixTable('nested_tree') . '
791
                WHERE id = %i',
792
                $record['id_tree']
793
            );
794
            // form id_tree to full foldername
795
            $folder = [];
796
            $arbo = $tree->getPath($record['id_tree'], true);
797
            foreach ($arbo as $elem) {
798
                // Check if title is the ID of a user
799
                if (is_numeric($elem->title) === true) {
800
                    // Is this a User id?
801
                    $user = DB::queryfirstrow(
802
                        'SELECT id, login
803
                        FROM ' . prefixTable('users') . '
804
                        WHERE id = %i',
805
                        $elem->title
806
                    );
807
                    if (count($user) > 0) {
808
                        $elem->title = $user['login'];
809
                    }
810
                }
811
                // Build path
812
                array_push($folder, stripslashes($elem->title));
813
            }
814
            // store data
815
            DB::insert(
816
                prefixTable('cache'),
817
                [
818
                    'id' => $record['id'],
819
                    'label' => $record['label'],
820
                    'description' => $record['description'] ?? '',
821
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
822
                    'tags' => $tags,
823
                    'id_tree' => $record['id_tree'],
824
                    'perso' => $record['perso'],
825
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
826
                    'login' => $record['login'] ?? '',
827
                    'folder' => implode(' > ', $folder),
828
                    'author' => $record['id_user'],
829
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
830
                    'timestamp' => $record['date'],
831
                ]
832
            );
833
        }
834
    }
835
}
836
837
/**
838
 * Cache table - update existing value.
839
 *
840
 * @param int    $ident    Ident format
841
 * 
842
 * @return void
843
 */
844
function cacheTableUpdate(?int $ident = null): void
845
{
846
    $session = SessionManager::getSession();
847
    loadClasses('DB');
848
849
    //Load Tree
850
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
851
    // get new value from db
852
    $data = DB::queryfirstrow(
853
        'SELECT label, description, id_tree, perso, restricted_to, login, url
854
        FROM ' . prefixTable('items') . '
855
        WHERE id=%i',
856
        $ident
857
    );
858
    // Get all TAGS
859
    $tags = '';
860
    $itemTags = DB::query(
861
        'SELECT tag
862
            FROM ' . prefixTable('tags') . '
863
            WHERE item_id = %i AND tag != ""',
864
        $ident
865
    );
866
    foreach ($itemTags as $itemTag) {
867
        $tags .= $itemTag['tag'] . ' ';
868
    }
869
    // form id_tree to full foldername
870
    $folder = [];
871
    $arbo = $tree->getPath($data['id_tree'], true);
872
    foreach ($arbo as $elem) {
873
        // Check if title is the ID of a user
874
        if (is_numeric($elem->title) === true) {
875
            // Is this a User id?
876
            $user = DB::queryfirstrow(
877
                'SELECT id, login
878
                FROM ' . prefixTable('users') . '
879
                WHERE id = %i',
880
                $elem->title
881
            );
882
            if (count($user) > 0) {
883
                $elem->title = $user['login'];
884
            }
885
        }
886
        // Build path
887
        array_push($folder, stripslashes($elem->title));
888
    }
889
    // finaly update
890
    DB::update(
891
        prefixTable('cache'),
892
        [
893
            'label' => $data['label'],
894
            'description' => $data['description'],
895
            'tags' => $tags,
896
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
897
            'id_tree' => $data['id_tree'],
898
            'perso' => $data['perso'],
899
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
900
            'login' => $data['login'] ?? '',
901
            'folder' => implode(' » ', $folder),
902
            'author' => $session->get('user-id'),
903
        ],
904
        'id = %i',
905
        $ident
906
    );
907
}
908
909
/**
910
 * Cache table - add new value.
911
 *
912
 * @param int    $ident    Ident format
913
 * 
914
 * @return void
915
 */
916
function cacheTableAdd(?int $ident = null): void
917
{
918
    $session = SessionManager::getSession();
919
    $globalsUserId = $session->get('user-id');
920
921
    // Load class DB
922
    loadClasses('DB');
923
924
    //Load Tree
925
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
926
    // get new value from db
927
    $data = DB::queryFirstRow(
928
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
929
        FROM ' . prefixTable('items') . ' as i
930
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
931
        WHERE i.id = %i
932
        AND l.action = %s',
933
        $ident,
934
        'at_creation'
935
    );
936
    // Get all TAGS
937
    $tags = '';
938
    $itemTags = DB::query(
939
        'SELECT tag
940
            FROM ' . prefixTable('tags') . '
941
            WHERE item_id = %i AND tag != ""',
942
        $ident
943
    );
944
    foreach ($itemTags as $itemTag) {
945
        $tags .= $itemTag['tag'] . ' ';
946
    }
947
    // form id_tree to full foldername
948
    $folder = [];
949
    $arbo = $tree->getPath($data['id_tree'], true);
950
    foreach ($arbo as $elem) {
951
        // Check if title is the ID of a user
952
        if (is_numeric($elem->title) === true) {
953
            // Is this a User id?
954
            $user = DB::queryfirstrow(
955
                'SELECT id, login
956
                FROM ' . prefixTable('users') . '
957
                WHERE id = %i',
958
                $elem->title
959
            );
960
            if (count($user) > 0) {
961
                $elem->title = $user['login'];
962
            }
963
        }
964
        // Build path
965
        array_push($folder, stripslashes($elem->title));
966
    }
967
    // finaly update
968
    DB::insert(
969
        prefixTable('cache'),
970
        [
971
            'id' => $data['id'],
972
            'label' => $data['label'],
973
            'description' => $data['description'],
974
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
975
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
976
            'id_tree' => $data['id_tree'],
977
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
978
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
979
            'login' => $data['login'] ?? '',
980
            'folder' => implode(' » ', $folder),
981
            'author' => $globalsUserId,
982
            'timestamp' => $data['date'],
983
        ]
984
    );
985
}
986
987
/**
988
 * Do statistics.
989
 *
990
 * @param array $SETTINGS Teampass settings
991
 *
992
 * @return array
993
 */
994
function getStatisticsData(array $SETTINGS): array
995
{
996
    DB::query(
997
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
998
        0
999
    );
1000
    $counter_folders = DB::count();
1001
    DB::query(
1002
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1003
        1
1004
    );
1005
    $counter_folders_perso = DB::count();
1006
    DB::query(
1007
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1008
        0
1009
    );
1010
    $counter_items = DB::count();
1011
        DB::query(
1012
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1013
        1
1014
    );
1015
    $counter_items_perso = DB::count();
1016
        DB::query(
1017
        'SELECT id FROM ' . prefixTable('users') . ''
1018
    );
1019
    $counter_users = DB::count();
1020
        DB::query(
1021
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1022
        1
1023
    );
1024
    $admins = DB::count();
1025
    DB::query(
1026
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1027
        1
1028
    );
1029
    $managers = DB::count();
1030
    DB::query(
1031
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1032
        1
1033
    );
1034
    $readOnly = DB::count();
1035
    // list the languages
1036
    $usedLang = [];
1037
    $tp_languages = DB::query(
1038
        'SELECT name FROM ' . prefixTable('languages')
1039
    );
1040
    foreach ($tp_languages as $tp_language) {
1041
        DB::query(
1042
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1043
            $tp_language['name']
1044
        );
1045
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1046
    }
1047
1048
    // get list of ips
1049
    $usedIp = [];
1050
    $tp_ips = DB::query(
1051
        'SELECT user_ip FROM ' . prefixTable('users')
1052
    );
1053
    foreach ($tp_ips as $ip) {
1054
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1055
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1056
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1057
            $usedIp[$ip['user_ip']] = 1;
1058
        }
1059
    }
1060
1061
    return [
1062
        'error' => '',
1063
        'stat_phpversion' => phpversion(),
1064
        'stat_folders' => $counter_folders,
1065
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1066
        'stat_items' => $counter_items,
1067
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1068
        'stat_users' => $counter_users,
1069
        'stat_admins' => $admins,
1070
        'stat_managers' => $managers,
1071
        'stat_ro' => $readOnly,
1072
        'stat_kb' => $SETTINGS['enable_kb'],
1073
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1074
        'stat_fav' => $SETTINGS['enable_favourites'],
1075
        'stat_teampassversion' => TP_VERSION,
1076
        'stat_ldap' => $SETTINGS['ldap_mode'],
1077
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1078
        'stat_duo' => $SETTINGS['duo'],
1079
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1080
        'stat_api' => $SETTINGS['api'],
1081
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1082
        'stat_syslog' => $SETTINGS['syslog_enable'],
1083
        'stat_2fa' => $SETTINGS['google_authentication'],
1084
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1085
        'stat_mysqlversion' => DB::serverVersion(),
1086
        'stat_languages' => $usedLang,
1087
        'stat_country' => $usedIp,
1088
    ];
1089
}
1090
1091
/**
1092
 * Permits to prepare the way to send the email
1093
 * 
1094
 * @param string $subject       email subject
1095
 * @param string $body          email message
1096
 * @param string $email         email
1097
 * @param string $receiverName  Receiver name
1098
 * @param array  $SETTINGS      settings
1099
 *
1100
 * @return void
1101
 */
1102
function prepareSendingEmail(
1103
    $subject,
1104
    $body,
1105
    $email,
1106
    $receiverName = ''
1107
): void 
1108
{
1109
    DB::insert(
1110
        prefixTable('processes'),
1111
        array(
1112
            'created_at' => time(),
1113
            'process_type' => 'send_email',
1114
            'arguments' => json_encode([
1115
                'subject' => $subject,
1116
                'receivers' => $email,
1117
                'body' => $body,
1118
                'receiver_name' => $receiverName,
1119
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1120
            'updated_at' => '',
1121
            'finished_at' => '',
1122
            'output' => '',
1123
        )
1124
    );
1125
}
1126
1127
/**
1128
 * Permits to send an email.
1129
 *
1130
 * @param string $subject     email subject
1131
 * @param string $textMail    email message
1132
 * @param string $email       email
1133
 * @param array  $SETTINGS    settings
1134
 * @param string $textMailAlt email message alt
1135
 * @param bool   $silent      no errors
1136
 *
1137
 * @return string some json info
1138
 */
1139
function sendEmail(
1140
    $subject,
1141
    $textMail,
1142
    $email,
1143
    $SETTINGS,
1144
    $textMailAlt = null,
1145
    $silent = true,
1146
    $cron = false
1147
) {
1148
    $lang = new Language(); 
1149
1150
    // CAse where email not defined
1151
    if ($email === 'none' || empty($email) === true) {
1152
        return json_encode(
1153
            [
1154
                'error' => true,
1155
                'message' => $lang->get('forgot_my_pw_email_sent'),
1156
            ]
1157
        );
1158
    }
1159
1160
    // Build and send email
1161
    $email = buildEmail(
1162
        $subject,
1163
        $textMail,
1164
        $email,
1165
        $SETTINGS,
1166
        $textMailAlt = null,
1167
        $silent = true,
1168
        $cron
1169
    );
1170
1171
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1172
        return json_encode(
1173
            [
1174
                'error' => false,
1175
                'message' => $lang->get('forgot_my_pw_email_sent'),
1176
            ]
1177
        );
1178
    }
1179
    // Debug purpose
1180
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1181
        return json_encode(
1182
            [
1183
                'error' => true,
1184
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1185
            ]
1186
        );
1187
    }
1188
    return json_encode(
1189
        [
1190
            'error' => false,
1191
            'message' => $lang->get('share_sent_ok'),
1192
        ]
1193
    );
1194
}
1195
1196
1197
function buildEmail(
1198
    $subject,
1199
    $textMail,
1200
    $email,
1201
    $SETTINGS,
1202
    $textMailAlt = null,
1203
    $silent = true,
1204
    $cron = false
1205
)
1206
{
1207
    // Load PHPMailer
1208
    $mail = new PHPMailer(true);
1209
    $languageDir = $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/';
1210
1211
    try {
1212
        // Set language and SMTPDebug
1213
        $mail->setLanguage('en', $languageDir);
1214
        $mail->SMTPDebug = ($cron || $silent) ? 0 : $SETTINGS['email_debug_level'];
1215
1216
        // Configure SMTP
1217
        $mail->isSMTP();
1218
        $mail->Host = $SETTINGS['email_smtp_server'];
1219
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1;
1220
        $mail->Username = $SETTINGS['email_auth_username'];
1221
        $mail->Password = $SETTINGS['email_auth_pwd'];
1222
        $mail->Port = (int) $SETTINGS['email_port'];
1223
        $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1224
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none';
1225
        $mail->SMTPOptions = [
1226
            'ssl' => [
1227
                'verify_peer' => false,
1228
                'verify_peer_name' => false,
1229
                'allow_self_signed' => true,
1230
            ],
1231
        ];
1232
1233
        // Set From and FromName
1234
        $mail->From = $SETTINGS['email_from'];
1235
        $mail->FromName = $SETTINGS['email_from_name'];
1236
1237
        // Prepare recipients
1238
        foreach (array_filter(explode(',', $email)) as $dest) {
1239
            $mail->addAddress($dest);
1240
        }
1241
        
1242
        // Prepare HTML and AltBody
1243
        $text_html = emailBody($textMail);
1244
        $mail->WordWrap = 80;
1245
        $mail->isHtml(true);
1246
        $mail->Subject = $subject;
1247
        $mail->Body = $text_html;
1248
        $mail->AltBody = is_null($textMailAlt) ? '' : $textMailAlt;
1249
1250
        // Send email
1251
        $mail->send();
1252
        $mail->smtpClose();
1253
1254
        return '';
1255
    } catch (Exception $e) {
1256
        if (!$silent || (int) $SETTINGS['email_debug_level'] !== 0) {
1257
            return json_encode([
1258
                'error' => true,
1259
                'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1260
            ]);
1261
        }
1262
        return '';
1263
    }
1264
    /*
1265
    // load PHPMailer
1266
    $mail = new PHPMailer(true);
1267
1268
    // send to user
1269
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/');
1270
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true && $cron === false && $silent === false ? $SETTINGS['email_debug_level'] : 0;
1271
    $mail->Port = (int) $SETTINGS['email_port'];
1272
    //COULD BE USED
1273
    $mail->CharSet = 'utf-8';
1274
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1275
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1276
    $mail->SMTPOptions = [
1277
        'ssl' => [
1278
            'verify_peer' => false,
1279
            'verify_peer_name' => false,
1280
            'allow_self_signed' => true,
1281
        ],
1282
    ];
1283
    $mail->isSmtp();
1284
    // send via SMTP
1285
    $mail->Host = $SETTINGS['email_smtp_server'];
1286
    // SMTP servers
1287
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1288
    // turn on SMTP authentication
1289
    $mail->Username = $SETTINGS['email_auth_username'];
1290
    // SMTP username
1291
    $mail->Password = $SETTINGS['email_auth_pwd'];
1292
    // SMTP password
1293
    $mail->From = $SETTINGS['email_from'];
1294
    $mail->FromName = $SETTINGS['email_from_name'];
1295
    // Prepare for each person
1296
    foreach (array_filter(explode(',', $email)) as $dest) {
1297
        $mail->addAddress($dest);
1298
    }
1299
    
1300
    // Prepare HTML
1301
    $text_html = emailBody($textMail);
1302
    $mail->WordWrap = 80;
1303
    // set word wrap
1304
    $mail->isHtml(true);
1305
    // send as HTML
1306
    $mail->Subject = $subject;
1307
    $mail->Body = $text_html;
1308
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1309
1310
    try {
1311
        // send email
1312
        $mail->send();
1313
    } catch (Exception $e) {
1314
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1315
            return json_encode(
1316
                [
1317
                    'error' => true,
1318
                    'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1319
                ]
1320
            );
1321
        }
1322
        return '';
1323
    }
1324
    $mail->smtpClose();
1325
1326
    return json_encode(
1327
        [
1328
            'error' => true,
1329
            'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1330
        ]
1331
    );
1332
    */
1333
}
1334
1335
/**
1336
 * Returns the email body.
1337
 *
1338
 * @param string $textMail Text for the email
1339
 */
1340
function emailBody(string $textMail): string
1341
{
1342
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1343
    w3.org/TR/html4/loose.dtd"><html>
1344
    <head><title>Email Template</title>
1345
    <style type="text/css">
1346
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1347
    </style></head>
1348
    <body style="-ms-text-size-adjust: none; size-adjust: none; margin: 0; padding: 10px 0; background-color: #f0f0f0;" bgcolor="#f0f0f0" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
1349
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1350
    <tr><td style="border-collapse: collapse;"><br>
1351
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1352
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1353
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1354
        </td></tr></table></td>
1355
    </tr>
1356
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1357
        <table width="600" cellpadding="0" cellspacing="0" border="0" class="container" bgcolor="#ffffff" style="border-spacing: 0; border-bottom: 1px solid #e0e0e0; box-shadow: 0 0 3px #ddd; color: #434343; font-family: Helvetica, Verdana, sans-serif;">
1358
        <tr><td class="container-padding" bgcolor="#ffffff" style="border-collapse: collapse; border-left: 1px solid #e0e0e0; background-color: #ffffff; padding-left: 30px; padding-right: 30px;">
1359
        <br><div style="float:right;">' .
1360
        $textMail .
1361
        '<br><br></td></tr></table>
1362
    </td></tr></table>
1363
    <br></body></html>';
1364
}
1365
1366
/**
1367
 * Generate a Key.
1368
 * 
1369
 * @return string
1370
 */
1371
function generateKey(): string
1372
{
1373
    return substr(md5(rand() . rand()), 0, 15);
1374
}
1375
1376
/**
1377
 * Convert date to timestamp.
1378
 *
1379
 * @param string $date        The date
1380
 * @param string $date_format Date format
1381
 *
1382
 * @return int
1383
 */
1384
function dateToStamp(string $date, string $date_format): int
1385
{
1386
    $date = date_parse_from_format($date_format, $date);
1387
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1388
        return mktime(
1389
            empty($date['hour']) === false ? $date['hour'] : 23,
1390
            empty($date['minute']) === false ? $date['minute'] : 59,
1391
            empty($date['second']) === false ? $date['second'] : 59,
1392
            $date['month'],
1393
            $date['day'],
1394
            $date['year']
1395
        );
1396
    }
1397
    return 0;
1398
}
1399
1400
/**
1401
 * Is this a date.
1402
 *
1403
 * @param string $date Date
1404
 *
1405
 * @return bool
1406
 */
1407
function isDate(string $date): bool
1408
{
1409
    return strtotime($date) !== false;
1410
}
1411
1412
/**
1413
 * Check if isUTF8().
1414
 *
1415
 * @param string|array $string Is the string
1416
 *
1417
 * @return int is the string in UTF8 format
1418
 */
1419
function isUTF8($string): int
1420
{
1421
    if (is_array($string) === true) {
1422
        $string = $string['string'];
1423
    }
1424
1425
    return preg_match(
1426
        '%^(?:
1427
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1428
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1429
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1430
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1431
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1432
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1433
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1434
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1435
        )*$%xs',
1436
        $string
1437
    );
1438
}
1439
1440
/**
1441
 * Prepare an array to UTF8 format before JSON_encode.
1442
 *
1443
 * @param array $array Array of values
1444
 *
1445
 * @return array
1446
 */
1447
function utf8Converter(array $array): array
1448
{
1449
    array_walk_recursive(
1450
        $array,
1451
        static function (&$item): void {
1452
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1453
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1454
            }
1455
        }
1456
    );
1457
    return $array;
1458
}
1459
1460
/**
1461
 * Permits to prepare data to be exchanged.
1462
 *
1463
 * @param array|string $data Text
1464
 * @param string       $type Parameter
1465
 * @param string       $key  Optional key
1466
 *
1467
 * @return string|array
1468
 */
1469
function prepareExchangedData($data, string $type, ?string $key = null)
1470
{
1471
    $session = SessionManager::getSession();
1472
    
1473
    // get session
1474
    if ($key !== null) {
1475
        $session->set('key', $key);
1476
        $globalsKey = $key;
1477
    } else {
1478
        $globalsKey = $session->get('key');
1479
    }
1480
    
1481
    // Perform
1482
    if ($type === 'encode' && is_array($data) === true) {
1483
        // Now encode
1484
        return Encryption::encrypt(
1485
            json_encode(
1486
                $data,
1487
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1488
            ),
1489
            $globalsKey
1490
        );
1491
    }
1492
    if ($type === 'decode' && is_array($data) === false) {
1493
        // check if key exists
1494
        return json_decode(
1495
            (string) Encryption::decrypt(
1496
                (string) $data,
1497
                $globalsKey
1498
            ),
1499
            true
1500
        );
1501
    }
1502
    return '';
1503
}
1504
1505
1506
/**
1507
 * Create a thumbnail.
1508
 *
1509
 * @param string  $src           Source
1510
 * @param string  $dest          Destination
1511
 * @param int $desired_width Size of width
1512
 * 
1513
 * @return void|string|bool
1514
 */
1515
function makeThumbnail(string $src, string $dest, int $desired_width)
1516
{
1517
    /* read the source image */
1518
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1519
        $source_image = imagecreatefrompng($src);
1520
        if ($source_image === false) {
1521
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1522
        }
1523
    } else {
1524
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1525
    }
1526
1527
    // Get height and width
1528
    $width = imagesx($source_image);
1529
    $height = imagesy($source_image);
1530
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1531
    $desired_height = (int) floor($height * $desired_width / $width);
1532
    /* create a new, "virtual" image */
1533
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1534
    if ($virtual_image === false) {
1535
        return false;
1536
    }
1537
    /* copy source image at a resized size */
1538
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1539
    /* create the physical thumbnail image to its destination */
1540
    imagejpeg($virtual_image, $dest);
1541
}
1542
1543
/**
1544
 * Check table prefix in SQL query.
1545
 *
1546
 * @param string $table Table name
1547
 * 
1548
 * @return string
1549
 */
1550
function prefixTable(string $table): string
1551
{
1552
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1553
    if (empty($safeTable) === false) {
1554
        // sanitize string
1555
        return $safeTable;
1556
    }
1557
    // stop error no table
1558
    return 'table_not_exists';
1559
}
1560
1561
/**
1562
 * GenerateCryptKey
1563
 *
1564
 * @param int     $size      Length
1565
 * @param bool $secure Secure
1566
 * @param bool $numerals Numerics
1567
 * @param bool $uppercase Uppercase letters
1568
 * @param bool $symbols Symbols
1569
 * @param bool $lowercase Lowercase
1570
 * @param array   $SETTINGS  SETTINGS
1571
 * 
1572
 * @return string
1573
 */
1574
function GenerateCryptKey(
1575
    int $size = 20,
1576
    bool $secure = false,
1577
    bool $numerals = false,
1578
    bool $uppercase = false,
1579
    bool $symbols = false,
1580
    bool $lowercase = false,
1581
    array $SETTINGS = []
1582
): string {
1583
    $generator = new ComputerPasswordGenerator();
1584
    $generator->setRandomGenerator(new Php7RandomGenerator());
1585
    
1586
    // Manage size
1587
    $generator->setLength((int) $size);
1588
    if ($secure === true) {
1589
        $generator->setSymbols(true);
1590
        $generator->setLowercase(true);
1591
        $generator->setUppercase(true);
1592
        $generator->setNumbers(true);
1593
    } else {
1594
        $generator->setLowercase($lowercase);
1595
        $generator->setUppercase($uppercase);
1596
        $generator->setNumbers($numerals);
1597
        $generator->setSymbols($symbols);
1598
    }
1599
1600
    return $generator->generatePasswords()[0];
1601
}
1602
1603
/**
1604
 * Send sysLOG message
1605
 *
1606
 * @param string    $message
1607
 * @param string    $host
1608
 * @param int       $port
1609
 * @param string    $component
1610
 * 
1611
 * @return void
1612
*/
1613
function send_syslog($message, $host, $port, $component = 'teampass'): void
1614
{
1615
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1616
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1617
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1618
    socket_close($sock);
1619
}
1620
1621
/**
1622
 * Permits to log events into DB
1623
 *
1624
 * @param array  $SETTINGS Teampass settings
1625
 * @param string $type     Type
1626
 * @param string $label    Label
1627
 * @param string $who      Who
1628
 * @param string $login    Login
1629
 * @param string|int $field_1  Field
1630
 * 
1631
 * @return void
1632
 */
1633
function logEvents(
1634
    array $SETTINGS, 
1635
    string $type, 
1636
    string $label, 
1637
    string $who, 
1638
    ?string $login = null, 
1639
    $field_1 = null
1640
): void
1641
{
1642
    if (empty($who)) {
1643
        $who = getClientIpServer();
1644
    }
1645
1646
    // Load class DB
1647
    loadClasses('DB');
1648
1649
    DB::insert(
1650
        prefixTable('log_system'),
1651
        [
1652
            'type' => $type,
1653
            'date' => time(),
1654
            'label' => $label,
1655
            'qui' => $who,
1656
            'field_1' => $field_1 === null ? '' : $field_1,
1657
        ]
1658
    );
1659
    // If SYSLOG
1660
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1661
        if ($type === 'user_mngt') {
1662
            send_syslog(
1663
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1664
                $SETTINGS['syslog_host'],
1665
                $SETTINGS['syslog_port'],
1666
                'teampass'
1667
            );
1668
        } else {
1669
            send_syslog(
1670
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1671
                $SETTINGS['syslog_host'],
1672
                $SETTINGS['syslog_port'],
1673
                'teampass'
1674
            );
1675
        }
1676
    }
1677
}
1678
1679
/**
1680
 * Log events.
1681
 *
1682
 * @param array  $SETTINGS        Teampass settings
1683
 * @param int    $item_id         Item id
1684
 * @param string $item_label      Item label
1685
 * @param int    $id_user         User id
1686
 * @param string $action          Code for reason
1687
 * @param string $login           User login
1688
 * @param string $raison          Code for reason
1689
 * @param string $encryption_type Encryption on
1690
 * @param string $time Encryption Time
1691
 * @param string $old_value       Old value
1692
 * 
1693
 * @return void
1694
 */
1695
function logItems(
1696
    array $SETTINGS,
1697
    int $item_id,
1698
    string $item_label,
1699
    int $id_user,
1700
    string $action,
1701
    ?string $login = null,
1702
    ?string $raison = null,
1703
    ?string $encryption_type = null,
1704
    ?string $time = null,
1705
    ?string $old_value = null
1706
): void {
1707
    // Load class DB
1708
    loadClasses('DB');
1709
1710
    // Insert log in DB
1711
    DB::insert(
1712
        prefixTable('log_items'),
1713
        [
1714
            'id_item' => $item_id,
1715
            'date' => is_null($time) === true ? time() : $time,
1716
            'id_user' => $id_user,
1717
            'action' => $action,
1718
            'raison' => $raison,
1719
            'old_value' => $old_value,
1720
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1721
        ]
1722
    );
1723
    // Timestamp the last change
1724
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1725
        DB::update(
1726
            prefixTable('misc'),
1727
            [
1728
                'valeur' => time(),
1729
            ],
1730
            'type = %s AND intitule = %s',
1731
            'timestamp',
1732
            'last_item_change'
1733
        );
1734
    }
1735
1736
    // SYSLOG
1737
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1738
        // Extract reason
1739
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1740
        // Get item info if not known
1741
        if (empty($item_label) === true) {
1742
            $dataItem = DB::queryfirstrow(
1743
                'SELECT id, id_tree, label
1744
                FROM ' . prefixTable('items') . '
1745
                WHERE id = %i',
1746
                $item_id
1747
            );
1748
            $item_label = $dataItem['label'];
1749
        }
1750
1751
        send_syslog(
1752
            'action=' . str_replace('at_', '', $action) .
1753
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1754
                ' itemno=' . $item_id .
1755
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1756
                ' itemname="' . addslashes($item_label) . '"',
1757
            $SETTINGS['syslog_host'],
1758
            $SETTINGS['syslog_port'],
1759
            'teampass'
1760
        );
1761
    }
1762
1763
    // send notification if enabled
1764
    //notifyOnChange($item_id, $action, $SETTINGS);
1765
}
1766
1767
/**
1768
 * Prepare notification email to subscribers.
1769
 *
1770
 * @param int    $item_id  Item id
1771
 * @param string $label    Item label
1772
 * @param array  $changes  List of changes
1773
 * @param array  $SETTINGS Teampass settings
1774
 * 
1775
 * @return void
1776
 */
1777
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1778
{
1779
    $session = SessionManager::getSession();
1780
    $lang = new Language(); 
1781
    $globalsUserId = $session->get('user-id');
1782
    $globalsLastname = $session->get('user-lastname');
1783
    $globalsName = $session->get('user-name');
1784
    // send email to user that what to be notified
1785
    $notification = DB::queryOneColumn(
1786
        'email',
1787
        'SELECT *
1788
        FROM ' . prefixTable('notification') . ' AS n
1789
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1790
        WHERE n.item_id = %i AND n.user_id != %i',
1791
        $item_id,
1792
        $globalsUserId
1793
    );
1794
    if (DB::count() > 0) {
1795
        // Prepare path
1796
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1797
        // Get list of changes
1798
        $htmlChanges = '<ul>';
1799
        foreach ($changes as $change) {
1800
            $htmlChanges .= '<li>' . $change . '</li>';
1801
        }
1802
        $htmlChanges .= '</ul>';
1803
        // send email
1804
        DB::insert(
1805
            prefixTable('emails'),
1806
            [
1807
                'timestamp' => time(),
1808
                'subject' => $lang->get('email_subject_item_updated'),
1809
                'body' => str_replace(
1810
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1811
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1812
                    $lang->get('email_body_item_updated')
1813
                ),
1814
                'receivers' => implode(',', $notification),
1815
                'status' => '',
1816
            ]
1817
        );
1818
    }
1819
}
1820
1821
/**
1822
 * Returns the Item + path.
1823
 *
1824
 * @param int    $id_tree  Node id
1825
 * @param string $label    Label
1826
 * @param array  $SETTINGS TP settings
1827
 * 
1828
 * @return string
1829
 */
1830
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1831
{
1832
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1833
    $arbo = $tree->getPath($id_tree, true);
1834
    $path = '';
1835
    foreach ($arbo as $elem) {
1836
        if (empty($path) === true) {
1837
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1838
        } else {
1839
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1840
        }
1841
    }
1842
1843
    // Build text to show user
1844
    if (empty($label) === false) {
1845
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1846
    }
1847
    return empty($path) === true ? '' : $path;
1848
}
1849
1850
/**
1851
 * Get the client ip address.
1852
 *
1853
 * @return string IP address
1854
 */
1855
function getClientIpServer(): string
1856
{
1857
    if (getenv('HTTP_CLIENT_IP')) {
1858
        $ipaddress = getenv('HTTP_CLIENT_IP');
1859
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1860
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1861
    } elseif (getenv('HTTP_X_FORWARDED')) {
1862
        $ipaddress = getenv('HTTP_X_FORWARDED');
1863
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1864
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1865
    } elseif (getenv('HTTP_FORWARDED')) {
1866
        $ipaddress = getenv('HTTP_FORWARDED');
1867
    } elseif (getenv('REMOTE_ADDR')) {
1868
        $ipaddress = getenv('REMOTE_ADDR');
1869
    } else {
1870
        $ipaddress = 'UNKNOWN';
1871
    }
1872
1873
    return $ipaddress;
1874
}
1875
1876
/**
1877
 * Escape all HTML, JavaScript, and CSS.
1878
 *
1879
 * @param string $input    The input string
1880
 * @param string $encoding Which character encoding are we using?
1881
 * 
1882
 * @return string
1883
 */
1884
function noHTML(string $input, string $encoding = 'UTF-8'): string
1885
{
1886
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1887
}
1888
1889
/**
1890
 * Permits to handle the Teampass config file
1891
 * $action accepts "rebuild" and "update"
1892
 *
1893
 * @param string $action   Action to perform
1894
 * @param array  $SETTINGS Teampass settings
1895
 * @param string $field    Field to refresh
1896
 * @param string $value    Value to set
1897
 *
1898
 * @return string|bool
1899
 */
1900
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1901
{
1902
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1903
1904
    // Load class DB
1905
    loadClasses('DB');
1906
1907
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1908
        // perform a copy
1909
        if (file_exists($tp_config_file)) {
1910
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1911
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1912
            }
1913
        }
1914
1915
        // regenerate
1916
        $data = [];
1917
        $data[0] = "<?php\n";
1918
        $data[1] = "global \$SETTINGS;\n";
1919
        $data[2] = "\$SETTINGS = array (\n";
1920
        $rows = DB::query(
1921
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1922
            'admin'
1923
        );
1924
        foreach ($rows as $record) {
1925
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
1926
        }
1927
        array_push($data, ");\n");
1928
        $data = array_unique($data);
1929
    // ---
1930
    } elseif ($action === 'update' && empty($field) === false) {
1931
        $data = file($tp_config_file);
1932
        $inc = 0;
1933
        $bFound = false;
1934
        foreach ($data as $line) {
1935
            if (stristr($line, ');')) {
1936
                break;
1937
            }
1938
1939
            if (stristr($line, "'" . $field . "' => '")) {
1940
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT) . "',\n";
1941
                $bFound = true;
1942
                break;
1943
            }
1944
            ++$inc;
1945
        }
1946
        if ($bFound === false) {
1947
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT). "',\n);\n";
1948
        }
1949
    }
1950
1951
    // update file
1952
    file_put_contents($tp_config_file, implode('', $data ?? []));
1953
    return true;
1954
}
1955
1956
/**
1957
 * Permits to replace &#92; to permit correct display
1958
 *
1959
 * @param string $input Some text
1960
 * 
1961
 * @return string
1962
 */
1963
function handleBackslash(string $input): string
1964
{
1965
    return str_replace('&amp;#92;', '&#92;', $input);
1966
}
1967
1968
/**
1969
 * Permits to load settings
1970
 * 
1971
 * @return void
1972
*/
1973
function loadSettings(): void
1974
{
1975
    global $SETTINGS;
1976
    /* LOAD CPASSMAN SETTINGS */
1977
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1978
        $SETTINGS = [];
1979
        $SETTINGS['duplicate_folder'] = 0;
1980
        //by default, this is set to 0;
1981
        $SETTINGS['duplicate_item'] = 0;
1982
        //by default, this is set to 0;
1983
        $SETTINGS['number_of_used_pw'] = 5;
1984
        //by default, this value is set to 5;
1985
        $settings = [];
1986
        $rows = DB::query(
1987
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1988
            [
1989
                'type' => 'admin',
1990
                'type2' => 'settings',
1991
            ]
1992
        );
1993
        foreach ($rows as $record) {
1994
            if ($record['type'] === 'admin') {
1995
                $SETTINGS[$record['intitule']] = $record['valeur'];
1996
            } else {
1997
                $settings[$record['intitule']] = $record['valeur'];
1998
            }
1999
        }
2000
        $SETTINGS['loaded'] = 1;
2001
        $SETTINGS['default_session_expiration_time'] = 5;
2002
    }
2003
}
2004
2005
/**
2006
 * check if folder has custom fields.
2007
 * Ensure that target one also has same custom fields
2008
 * 
2009
 * @param int $source_id
2010
 * @param int $target_id 
2011
 * 
2012
 * @return bool
2013
*/
2014
function checkCFconsistency(int $source_id, int $target_id): bool
2015
{
2016
    $source_cf = [];
2017
    $rows = DB::QUERY(
2018
        'SELECT id_category
2019
            FROM ' . prefixTable('categories_folders') . '
2020
            WHERE id_folder = %i',
2021
        $source_id
2022
    );
2023
    foreach ($rows as $record) {
2024
        array_push($source_cf, $record['id_category']);
2025
    }
2026
2027
    $target_cf = [];
2028
    $rows = DB::QUERY(
2029
        'SELECT id_category
2030
            FROM ' . prefixTable('categories_folders') . '
2031
            WHERE id_folder = %i',
2032
        $target_id
2033
    );
2034
    foreach ($rows as $record) {
2035
        array_push($target_cf, $record['id_category']);
2036
    }
2037
2038
    $cf_diff = array_diff($source_cf, $target_cf);
2039
    if (count($cf_diff) > 0) {
2040
        return false;
2041
    }
2042
2043
    return true;
2044
}
2045
2046
/**
2047
 * Will encrypte/decrypt a fil eusing Defuse.
2048
 *
2049
 * @param string $type        can be either encrypt or decrypt
2050
 * @param string $source_file path to source file
2051
 * @param string $target_file path to target file
2052
 * @param array  $SETTINGS    Settings
2053
 * @param string $password    A password
2054
 *
2055
 * @return string|bool
2056
 */
2057
function prepareFileWithDefuse(
2058
    string $type,
2059
    string $source_file,
2060
    string $target_file,
2061
    array $SETTINGS,
2062
    string $password = null
2063
) {
2064
    // Load AntiXSS
2065
    $antiXss = new AntiXSS();
2066
    // Protect against bad inputs
2067
    if (is_array($source_file) === true || is_array($target_file) === true) {
2068
        return 'error_cannot_be_array';
2069
    }
2070
2071
    // Sanitize
2072
    $source_file = $antiXss->xss_clean($source_file);
2073
    $target_file = $antiXss->xss_clean($target_file);
2074
    if (empty($password) === true || is_null($password) === true) {
2075
        // get KEY to define password
2076
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2077
        $password = Key::loadFromAsciiSafeString($ascii_key);
2078
    }
2079
2080
    $err = '';
2081
    if ($type === 'decrypt') {
2082
        // Decrypt file
2083
        $err = defuseFileDecrypt(
2084
            $source_file,
2085
            $target_file,
2086
            $SETTINGS, /** @scrutinizer ignore-type */
2087
            $password
2088
        );
2089
    } elseif ($type === 'encrypt') {
2090
        // Encrypt file
2091
        $err = defuseFileEncrypt(
2092
            $source_file,
2093
            $target_file,
2094
            $SETTINGS, /** @scrutinizer ignore-type */
2095
            $password
2096
        );
2097
    }
2098
2099
    // return error
2100
    return $err === true ? '' : $err;
2101
}
2102
2103
/**
2104
 * Encrypt a file with Defuse.
2105
 *
2106
 * @param string $source_file path to source file
2107
 * @param string $target_file path to target file
2108
 * @param array  $SETTINGS    Settings
2109
 * @param string $password    A password
2110
 *
2111
 * @return string|bool
2112
 */
2113
function defuseFileEncrypt(
2114
    string $source_file,
2115
    string $target_file,
2116
    array $SETTINGS,
2117
    string $password = null
2118
) {
2119
    try {
2120
        CryptoFile::encryptFileWithPassword(
2121
            $source_file,
2122
            $target_file,
2123
            $password
2124
        );
2125
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2126
        $err = 'wrong_key';
2127
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2128
        $err = $ex;
2129
    } catch (CryptoException\IOException $ex) {
2130
        $err = $ex;
2131
    }
2132
2133
    // return error
2134
    return empty($err) === false ? $err : true;
2135
}
2136
2137
/**
2138
 * Decrypt a file with Defuse.
2139
 *
2140
 * @param string $source_file path to source file
2141
 * @param string $target_file path to target file
2142
 * @param array  $SETTINGS    Settings
2143
 * @param string $password    A password
2144
 *
2145
 * @return string|bool
2146
 */
2147
function defuseFileDecrypt(
2148
    string $source_file,
2149
    string $target_file,
2150
    array $SETTINGS,
2151
    string $password = null
2152
) {
2153
    try {
2154
        CryptoFile::decryptFileWithPassword(
2155
            $source_file,
2156
            $target_file,
2157
            $password
2158
        );
2159
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2160
        $err = 'wrong_key';
2161
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2162
        $err = $ex;
2163
    } catch (CryptoException\IOException $ex) {
2164
        $err = $ex;
2165
    }
2166
2167
    // return error
2168
    return empty($err) === false ? $err : true;
2169
}
2170
2171
/*
2172
* NOT TO BE USED
2173
*/
2174
/**
2175
 * Undocumented function.
2176
 *
2177
 * @param string $text Text to debug
2178
 */
2179
function debugTeampass(string $text): void
2180
{
2181
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2182
    if ($debugFile !== false) {
2183
        fputs($debugFile, $text);
2184
        fclose($debugFile);
2185
    }
2186
}
2187
2188
/**
2189
 * DELETE the file with expected command depending on server type.
2190
 *
2191
 * @param string $file     Path to file
2192
 * @param array  $SETTINGS Teampass settings
2193
 *
2194
 * @return void
2195
 */
2196
function fileDelete(string $file, array $SETTINGS): void
2197
{
2198
    // Load AntiXSS
2199
    $antiXss = new AntiXSS();
2200
    $file = $antiXss->xss_clean($file);
2201
    if (is_file($file)) {
2202
        unlink($file);
2203
    }
2204
}
2205
2206
/**
2207
 * Permits to extract the file extension.
2208
 *
2209
 * @param string $file File name
2210
 *
2211
 * @return string
2212
 */
2213
function getFileExtension(string $file): string
2214
{
2215
    if (strpos($file, '.') === false) {
2216
        return $file;
2217
    }
2218
2219
    return substr($file, strrpos($file, '.') + 1);
2220
}
2221
2222
/**
2223
 * Chmods files and folders with different permissions.
2224
 *
2225
 * This is an all-PHP alternative to using: \n
2226
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2227
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2228
 *
2229
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2230
  *
2231
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2232
 * @param int    $filePerm The permissions any found files should get.
2233
 * @param int    $dirPerm  The permissions any found folder should get.
2234
 *
2235
 * @return bool Returns TRUE if the path if found and FALSE if not.
2236
 *
2237
 * @warning The permission levels has to be entered in octal format, which
2238
 * normally means adding a zero ("0") in front of the permission level. \n
2239
 * More info at: http://php.net/chmod.
2240
*/
2241
2242
function recursiveChmod(
2243
    string $path,
2244
    int $filePerm = 0644,
2245
    int  $dirPerm = 0755
2246
) {
2247
    // Check if the path exists
2248
    if (! file_exists($path)) {
2249
        return false;
2250
    }
2251
2252
    // See whether this is a file
2253
    if (is_file($path)) {
2254
        // Chmod the file with our given filepermissions
2255
        try {
2256
            chmod($path, $filePerm);
2257
        } catch (Exception $e) {
2258
            return false;
2259
        }
2260
    // If this is a directory...
2261
    } elseif (is_dir($path)) {
2262
        // Then get an array of the contents
2263
        $foldersAndFiles = scandir($path);
2264
        // Remove "." and ".." from the list
2265
        $entries = array_slice($foldersAndFiles, 2);
2266
        // Parse every result...
2267
        foreach ($entries as $entry) {
2268
            // And call this function again recursively, with the same permissions
2269
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2270
        }
2271
2272
        // When we are done with the contents of the directory, we chmod the directory itself
2273
        try {
2274
            chmod($path, $filePerm);
2275
        } catch (Exception $e) {
2276
            return false;
2277
        }
2278
    }
2279
2280
    // Everything seemed to work out well, return true
2281
    return true;
2282
}
2283
2284
/**
2285
 * Check if user can access to this item.
2286
 *
2287
 * @param int   $item_id ID of item
2288
 * @param array $SETTINGS
2289
 *
2290
 * @return bool|string
2291
 */
2292
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2293
{
2294
    
2295
    $session = SessionManager::getSession();
2296
    $session_groupes_visibles = $session->get('user-accessible_folders');
2297
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2298
    // Load item data
2299
    $data = DB::queryFirstRow(
2300
        'SELECT id_tree
2301
        FROM ' . prefixTable('items') . '
2302
        WHERE id = %i',
2303
        $item_id
2304
    );
2305
    // Check if user can access this folder
2306
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2307
        // Now check if this folder is restricted to user
2308
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2309
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2310
        ) {
2311
            return 'ERR_FOLDER_NOT_ALLOWED';
2312
        }
2313
    }
2314
2315
    return true;
2316
}
2317
2318
/**
2319
 * Creates a unique key.
2320
 *
2321
 * @param int $lenght Key lenght
2322
 *
2323
 * @return string
2324
 */
2325
function uniqidReal(int $lenght = 13): string
2326
{
2327
    if (function_exists('random_bytes')) {
2328
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2329
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2330
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2331
    } else {
2332
        throw new Exception('no cryptographically secure random function available');
2333
    }
2334
2335
    return substr(bin2hex($bytes), 0, $lenght);
2336
}
2337
2338
/**
2339
 * Obfuscate an email.
2340
 *
2341
 * @param string $email Email address
2342
 *
2343
 * @return string
2344
 */
2345
function obfuscateEmail(string $email): string
2346
{
2347
    $email = explode("@", $email);
2348
    $name = $email[0];
2349
    if (strlen($name) > 3) {
2350
        $name = substr($name, 0, 2);
2351
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2352
            $name .= "*";
2353
        }
2354
        $name .= substr($email[0], -1, 1);
2355
    }
2356
    $host = explode(".", $email[1])[0];
2357
    if (strlen($host) > 3) {
2358
        $host = substr($host, 0, 1);
2359
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2360
            $host .= "*";
2361
        }
2362
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2363
    }
2364
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2365
    return $email;
2366
}
2367
2368
/**
2369
 * Perform a Query.
2370
 *
2371
 * @param array  $SETTINGS Teamapss settings
2372
 * @param string $fields   Fields to use
2373
 * @param string $table    Table to use
2374
 *
2375
 * @return array
2376
 */
2377
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2378
{
2379
    // include librairies & connect to DB
2380
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2381
2382
    // Load class DB
2383
    loadClasses('DB');
2384
    
2385
    // Insert log in DB
2386
    return DB::query(
2387
        'SELECT ' . $fields . '
2388
        FROM ' . prefixTable($table)
2389
    );
2390
}
2391
2392
/**
2393
 * Undocumented function.
2394
 *
2395
 * @param int $bytes Size of file
2396
 *
2397
 * @return string
2398
 */
2399
function formatSizeUnits(int $bytes): string
2400
{
2401
    if ($bytes >= 1073741824) {
2402
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2403
    } elseif ($bytes >= 1048576) {
2404
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2405
    } elseif ($bytes >= 1024) {
2406
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2407
    } elseif ($bytes > 1) {
2408
        $bytes .= ' bytes';
2409
    } elseif ($bytes === 1) {
2410
        $bytes .= ' byte';
2411
    } else {
2412
        $bytes = '0 bytes';
2413
    }
2414
2415
    return $bytes;
2416
}
2417
2418
/**
2419
 * Generate user pair of keys.
2420
 *
2421
 * @param string $userPwd User password
2422
 *
2423
 * @return array
2424
 */
2425
function generateUserKeys(string $userPwd): array
2426
{
2427
    //if (WIP === false) {
2428
        // Load classes
2429
        $rsa = new Crypt_RSA();
2430
        $cipher = new Crypt_AES();
2431
        // Create the private and public key
2432
        $res = $rsa->createKey(4096);
2433
        // Encrypt the privatekey
2434
        $cipher->setPassword($userPwd);
2435
        $privatekey = $cipher->encrypt($res['privatekey']);
2436
        return [
2437
            'private_key' => base64_encode($privatekey),
2438
            'public_key' => base64_encode($res['publickey']),
2439
            'private_key_clear' => base64_encode($res['privatekey']),
2440
        ];
2441
    /*} else {
2442
        // Create the keys
2443
        $keys = RSA::createKey();
2444
2445
        return [
2446
            'private_key' => base64_encode($keys->withPassword($userPwd)->toString('PKCS8')),
2447
            'public_key' => base64_encode($keys->getPublicKey()),
2448
            'private_key_clear' => base64_encode($keys->toString('PKCS8')),
2449
        ];
2450
    }*/
2451
}
2452
2453
/**
2454
 * Permits to decrypt the user's privatekey.
2455
 *
2456
 * @param string $userPwd        User password
2457
 * @param string $userPrivateKey User private key
2458
 *
2459
 * @return string|object
2460
 */
2461
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2462
{
2463
    if (empty($userPwd) === false) {
2464
        //if (WIP === false) {
2465
            // Load classes
2466
            $cipher = new Crypt_AES();
2467
            // Encrypt the privatekey
2468
            $cipher->setPassword($userPwd);
2469
            try {
2470
                return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2471
            } catch (Exception $e) {
2472
                return $e;
2473
            }
2474
        /*} else {
2475
            //echo $userPrivateKey." ;; ".($userPwd)." ;;";
2476
            // Load and decrypt the private key
2477
            try {
2478
                $privateKey = PublicKeyLoader::loadPrivateKey(base64_decode($userPrivateKey), $userPwd)->withHash('sha1')->withMGFHash('sha1');
2479
                print_r($privateKey);
2480
                return base64_encode((string) $$privateKey);
2481
            } catch (NoKeyLoadedException $e) {
2482
                print_r($e);
2483
                return $e;
2484
            }
2485
        }*/
2486
    }
2487
    return '';
2488
}
2489
2490
/**
2491
 * Permits to encrypt the user's privatekey.
2492
 *
2493
 * @param string $userPwd        User password
2494
 * @param string $userPrivateKey User private key
2495
 *
2496
 * @return string
2497
 */
2498
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2499
{
2500
    if (empty($userPwd) === false) {
2501
        //if (WIP === false) {
2502
            // Load classes
2503
            $cipher = new Crypt_AES();
2504
            // Encrypt the privatekey
2505
            $cipher->setPassword($userPwd);        
2506
            try {
2507
                return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2508
            } catch (Exception $e) {
2509
                return $e;
2510
            }
2511
        /*} else {
2512
            // Load the private key
2513
            $privateKey = PublicKeyLoader::load(base64_decode($userPrivateKey));
2514
2515
            try {
2516
                return base64_encode($privateKey->withPassword($userPwd));
2517
            } catch (Exception $e) {
2518
                return $e;
2519
            }
2520
        }*/
2521
    }
2522
    return '';
2523
}
2524
2525
/**
2526
 * Encrypts a string using AES.
2527
 *
2528
 * @param string $data String to encrypt
2529
 * @param string $key
2530
 *
2531
 * @return array
2532
 */
2533
function doDataEncryption(string $data, string $key = NULL): array
2534
{
2535
    //if (WIP === false) {
2536
        // Load classes
2537
        $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2538
        // Generate an object key
2539
        $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $key;
2540
        // Set it as password
2541
        $cipher->setPassword($objectKey);
2542
        return [
2543
            'encrypted' => base64_encode($cipher->encrypt($data)),
2544
            'objectKey' => base64_encode($objectKey),
2545
        ];
2546
    /*} else {
2547
2548
    }*/
2549
}
2550
2551
/**
2552
 * Decrypts a string using AES.
2553
 *
2554
 * @param string $data Encrypted data
2555
 * @param string $key  Key to uncrypt
2556
 *
2557
 * @return string
2558
 */
2559
function doDataDecryption(string $data, string $key): string
2560
{
2561
    //if (WIP === false) {
2562
        // Load classes
2563
        $cipher = new Crypt_AES();
2564
        // Set the object key
2565
        $cipher->setPassword(base64_decode($key));
2566
        return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2567
    /*} else {
2568
2569
    }*/
2570
}
2571
2572
/**
2573
 * Encrypts using RSA a string using a public key.
2574
 *
2575
 * @param string $key       Key to be encrypted
2576
 * @param string $publicKey User public key
2577
 *
2578
 * @return string
2579
 */
2580
function encryptUserObjectKey(string $key, string $publicKey): string
2581
{
2582
    //if (WIP === false) {
2583
        // Load classes
2584
        $rsa = new Crypt_RSA();
2585
        $rsa->loadKey(base64_decode($publicKey));
2586
        // Encrypt
2587
        return base64_encode($rsa->encrypt(base64_decode($key)));
2588
    /*} else {
2589
2590
    }*/
2591
}
2592
2593
/**
2594
 * Decrypts using RSA an encrypted string using a private key.
2595
 *
2596
 * @param string $key        Encrypted key
2597
 * @param string $privateKey User private key
2598
 *
2599
 * @return string
2600
 */
2601
function decryptUserObjectKey(string $key, string $privateKey): string
2602
{
2603
    //if (WIP === false) {
2604
        // Load classes
2605
        $rsa = new Crypt_RSA();
2606
        $rsa->loadKey(base64_decode($privateKey));
2607
        // Decrypt
2608
        try {
2609
            $tmpValue = $rsa->decrypt(base64_decode($key));
2610
            if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2611
                $ret = base64_encode((string) /** @scrutinizer ignore-type */$tmpValue);
2612
            } else {
2613
                $ret = '';
2614
            }
2615
        } catch (Exception $e) {
2616
            return $e;
2617
        }
2618
        /*} else {
2619
2620
        }*/
2621
2622
    return $ret;
2623
}
2624
2625
/**
2626
 * Encrypts a file.
2627
 *
2628
 * @param string $fileInName File name
2629
 * @param string $fileInPath Path to file
2630
 *
2631
 * @return array
2632
 */
2633
function encryptFile(string $fileInName, string $fileInPath): array
2634
{
2635
    if (defined('FILE_BUFFER_SIZE') === false) {
2636
        define('FILE_BUFFER_SIZE', 128 * 1024);
2637
    }
2638
    //if (WIP === false) {
2639
        // Load classes
2640
        $cipher = new Crypt_AES();
2641
        // Generate an object key
2642
        $objectKey = uniqidReal(32);
2643
        // Set it as password
2644
        $cipher->setPassword($objectKey);
2645
        // Prevent against out of memory
2646
        $cipher->enableContinuousBuffer();
2647
        //$cipher->disablePadding();
2648
2649
        // Encrypt the file content
2650
        $plaintext = file_get_contents(
2651
            filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2652
        );
2653
        $ciphertext = $cipher->encrypt($plaintext);
2654
        // Save new file
2655
        $hash = md5($plaintext);
2656
        $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2657
        file_put_contents($fileOut, $ciphertext);
2658
        unlink($fileInPath . '/' . $fileInName);
2659
        return [
2660
            'fileHash' => base64_encode($hash),
2661
            'objectKey' => base64_encode($objectKey),
2662
        ];
2663
    /*} else {
2664
2665
    }*/
2666
}
2667
2668
/**
2669
 * Decrypt a file.
2670
 *
2671
 * @param string $fileName File name
2672
 * @param string $filePath Path to file
2673
 * @param string $key      Key to use
2674
 *
2675
 * @return string
2676
 */
2677
function decryptFile(string $fileName, string $filePath, string $key): string
2678
{
2679
    if (! defined('FILE_BUFFER_SIZE')) {
2680
        define('FILE_BUFFER_SIZE', 128 * 1024);
2681
    }
2682
    
2683
    // Get file name
2684
    $fileName = base64_decode($fileName);
2685
2686
    //if (WIP === false) {
2687
        // Load classes
2688
        $cipher = new Crypt_AES();
2689
        // Set the object key
2690
        $cipher->setPassword(base64_decode($key));
2691
        // Prevent against out of memory
2692
        $cipher->enableContinuousBuffer();
2693
        $cipher->disablePadding();
2694
        // Get file content
2695
        $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2696
        // Decrypt file content and return
2697
        return base64_encode($cipher->decrypt($ciphertext));
2698
    /*} else {
2699
        
2700
    }*/
2701
}
2702
2703
/**
2704
 * Generate a simple password
2705
 *
2706
 * @param int $length Length of string
2707
 * @param bool $symbolsincluded Allow symbols
2708
 *
2709
 * @return string
2710
 */
2711
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2712
{
2713
    // Generate new user password
2714
    $small_letters = range('a', 'z');
2715
    $big_letters = range('A', 'Z');
2716
    $digits = range(0, 9);
2717
    $symbols = $symbolsincluded === true ?
2718
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2719
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2720
    $count = count($res);
2721
    // first variant
2722
2723
    $random_string = '';
2724
    for ($i = 0; $i < $length; ++$i) {
2725
        $random_string .= $res[random_int(0, $count - 1)];
2726
    }
2727
2728
    return $random_string;
2729
}
2730
2731
/**
2732
 * Permit to store the sharekey of an object for users.
2733
 *
2734
 * @param string $object_name             Type for table selection
2735
 * @param int    $post_folder_is_personal Personal
2736
 * @param int    $post_folder_id          Folder
2737
 * @param int    $post_object_id          Object
2738
 * @param string $objectKey               Object key
2739
 * @param array  $SETTINGS                Teampass settings
2740
 * @param int    $user_id                 User ID if needed
2741
 * @param bool   $onlyForUser                 User ID if needed
2742
 * @param bool   $deleteAll                 User ID if needed
2743
 * @param array  $objectKeyArray                 User ID if needed
2744
 *
2745
 * @return void
2746
 */
2747
function storeUsersShareKey(
2748
    string $object_name,
2749
    int $post_folder_is_personal,
2750
    int $post_folder_id,
2751
    int $post_object_id,
2752
    string $objectKey,
2753
    array $SETTINGS,
2754
    bool $onlyForUser = false,
2755
    bool $deleteAll = true,
2756
    array $objectKeyArray = []
2757
): void {
2758
    
2759
    $session = SessionManager::getSession();
2760
    loadClasses('DB');
2761
2762
    // Delete existing entries for this object
2763
    if ($deleteAll === true) {
2764
        DB::delete(
2765
            $object_name,
2766
            'object_id = %i',
2767
            $post_object_id
2768
        );
2769
    }
2770
    
2771
    if (
2772
        //((int) $post_folder_is_personal === 1 && in_array($post_folder_id, $superGlobal->get('personal_folders', 'SESSION')) === true) ||
2773
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2774
    ) {
2775
        // Only create the sharekey for a user
2776
        $user = DB::queryFirstRow(
2777
            'SELECT public_key
2778
            FROM ' . prefixTable('users') . '
2779
            WHERE id = ' . (int) $session->get('user-id') . '
2780
            AND public_key != ""'
2781
        );
2782
2783
        if (empty($objectKey) === false) {
2784
            DB::insert(
2785
                $object_name,
2786
                [
2787
                    'object_id' => (int) $post_object_id,
2788
                    'user_id' => (int) $session->get('user-id'),
2789
                    'share_key' => encryptUserObjectKey(
2790
                        $objectKey,
2791
                        $user['public_key']
2792
                    ),
2793
                ]
2794
            );
2795
        } else if (count($objectKeyArray) > 0) {
2796
            foreach ($objectKeyArray as $object) {
2797
                DB::insert(
2798
                    $object_name,
2799
                    [
2800
                        'object_id' => (int) $object['objectId'],
2801
                        'user_id' => (int) $session->get('user-id'),
2802
                        'share_key' => encryptUserObjectKey(
2803
                            $object['objectKey'],
2804
                            $user['public_key']
2805
                        ),
2806
                    ]
2807
                );
2808
            }
2809
        }
2810
    } else {
2811
        // Create sharekey for each user
2812
        //DB::debugmode(true);
2813
        $users = DB::query(
2814
            'SELECT id, public_key
2815
            FROM ' . prefixTable('users') . '
2816
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2817
                'id IN ("' . TP_USER_ID . '","' . $session->get('user-id') . '") ' : 
2818
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '") ') . '
2819
            AND public_key != ""'
2820
        );
2821
        //DB::debugmode(false);
2822
        foreach ($users as $user) {
2823
            // Insert in DB the new object key for this item by user
2824
            if (count($objectKeyArray) === 0) {
2825
                DB::insert(
2826
                    $object_name,
2827
                    [
2828
                        'object_id' => $post_object_id,
2829
                        'user_id' => (int) $user['id'],
2830
                        'share_key' => encryptUserObjectKey(
2831
                            $objectKey,
2832
                            $user['public_key']
2833
                        ),
2834
                    ]
2835
                );
2836
            } else {
2837
                foreach ($objectKeyArray as $object) {
2838
                    DB::insert(
2839
                        $object_name,
2840
                        [
2841
                            'object_id' => (int) $object['objectId'],
2842
                            'user_id' => (int) $user['id'],
2843
                            'share_key' => encryptUserObjectKey(
2844
                                $object['objectKey'],
2845
                                $user['public_key']
2846
                            ),
2847
                        ]
2848
                    );
2849
                }
2850
            }
2851
        }
2852
    }
2853
}
2854
2855
/**
2856
 * Is this string base64 encoded?
2857
 *
2858
 * @param string $str Encoded string?
2859
 *
2860
 * @return bool
2861
 */
2862
function isBase64(string $str): bool
2863
{
2864
    $str = (string) trim($str);
2865
    if (! isset($str[0])) {
2866
        return false;
2867
    }
2868
2869
    $base64String = (string) base64_decode($str, true);
2870
    if ($base64String && base64_encode($base64String) === $str) {
2871
        return true;
2872
    }
2873
2874
    return false;
2875
}
2876
2877
/**
2878
 * Undocumented function
2879
 *
2880
 * @param string $field Parameter
2881
 *
2882
 * @return array|bool|resource|string
2883
 */
2884
function filterString(string $field)
2885
{
2886
    // Sanitize string
2887
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2888
    if (empty($field) === false) {
2889
        // Load AntiXSS
2890
        $antiXss = new AntiXSS();
2891
        // Return
2892
        return $antiXss->xss_clean($field);
2893
    }
2894
2895
    return false;
2896
}
2897
2898
/**
2899
 * CHeck if provided credentials are allowed on server
2900
 *
2901
 * @param string $login    User Login
2902
 * @param string $password User Pwd
2903
 * @param array  $SETTINGS Teampass settings
2904
 *
2905
 * @return bool
2906
 */
2907
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2908
{
2909
    // Build ldap configuration array
2910
    $config = [
2911
        // Mandatory Configuration Options
2912
        'hosts' => [$SETTINGS['ldap_hosts']],
2913
        'base_dn' => $SETTINGS['ldap_bdn'],
2914
        'username' => $SETTINGS['ldap_username'],
2915
        'password' => $SETTINGS['ldap_password'],
2916
2917
        // Optional Configuration Options
2918
        'port' => $SETTINGS['ldap_port'],
2919
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2920
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2921
        'version' => 3,
2922
        'timeout' => 5,
2923
        'follow_referrals' => false,
2924
2925
        // Custom LDAP Options
2926
        'options' => [
2927
            // See: http://php.net/ldap_set_option
2928
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2929
        ],
2930
    ];
2931
    
2932
    $connection = new Connection($config);
2933
    // Connect to LDAP
2934
    try {
2935
        $connection->connect();
2936
    } catch (\LdapRecord\Auth\BindException $e) {
2937
        $error = $e->getDetailedError();
2938
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2939
        return false;
2940
    }
2941
2942
    // Authenticate user
2943
    try {
2944
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2945
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2946
        } else {
2947
            $connection->auth()->attempt($SETTINGS['ldap_user_attribute'].'='.$login.','.(isset($SETTINGS['ldap_dn_additional_user_dn']) && !empty($SETTINGS['ldap_dn_additional_user_dn']) ? $SETTINGS['ldap_dn_additional_user_dn'].',' : '').$SETTINGS['ldap_bdn'], $password, $stayAuthenticated = true);
2948
        }
2949
    } catch (\LdapRecord\Auth\BindException $e) {
2950
        $error = $e->getDetailedError();
2951
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2952
        return false;
2953
    }
2954
2955
    return true;
2956
}
2957
2958
/**
2959
 * Removes from DB all sharekeys of this user
2960
 *
2961
 * @param int $userId User's id
2962
 * @param array   $SETTINGS Teampass settings
2963
 *
2964
 * @return bool
2965
 */
2966
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2967
{
2968
    // Load class DB
2969
    loadClasses('DB');
2970
2971
    // Remove all item sharekeys items
2972
    // expect if personal item
2973
    DB::delete(
2974
        prefixTable('sharekeys_items'),
2975
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2976
        $userId
2977
    );
2978
    // Remove all item sharekeys files
2979
    DB::delete(
2980
        prefixTable('sharekeys_files'),
2981
        'user_id = %i AND object_id NOT IN (
2982
            SELECT f.id 
2983
            FROM ' . prefixTable('items') . ' AS i 
2984
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2985
            WHERE i.perso = 1
2986
        )',
2987
        $userId
2988
    );
2989
    // Remove all item sharekeys fields
2990
    DB::delete(
2991
        prefixTable('sharekeys_fields'),
2992
        'user_id = %i AND object_id NOT IN (
2993
            SELECT c.id 
2994
            FROM ' . prefixTable('items') . ' AS i 
2995
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2996
            WHERE i.perso = 1
2997
        )',
2998
        $userId
2999
    );
3000
    // Remove all item sharekeys logs
3001
    DB::delete(
3002
        prefixTable('sharekeys_logs'),
3003
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
3004
        $userId
3005
    );
3006
    // Remove all item sharekeys suggestions
3007
    DB::delete(
3008
        prefixTable('sharekeys_suggestions'),
3009
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
3010
        $userId
3011
    );
3012
    return false;
3013
}
3014
3015
/**
3016
 * Manage list of timezones   $SETTINGS Teampass settings
3017
 *
3018
 * @return array
3019
 */
3020
function timezone_list()
3021
{
3022
    static $timezones = null;
3023
    if ($timezones === null) {
3024
        $timezones = [];
3025
        $offsets = [];
3026
        $now = new DateTime('now', new DateTimeZone('UTC'));
3027
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3028
            $now->setTimezone(new DateTimeZone($timezone));
3029
            $offsets[] = $offset = $now->getOffset();
3030
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3031
        }
3032
3033
        array_multisort($offsets, $timezones);
3034
    }
3035
3036
    return $timezones;
3037
}
3038
3039
/**
3040
 * Provide timezone offset
3041
 *
3042
 * @param int $offset Timezone offset
3043
 *
3044
 * @return string
3045
 */
3046
function format_GMT_offset($offset): string
3047
{
3048
    $hours = intval($offset / 3600);
3049
    $minutes = abs(intval($offset % 3600 / 60));
3050
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3051
}
3052
3053
/**
3054
 * Provides timezone name
3055
 *
3056
 * @param string $name Timezone name
3057
 *
3058
 * @return string
3059
 */
3060
function format_timezone_name($name): string
3061
{
3062
    $name = str_replace('/', ', ', $name);
3063
    $name = str_replace('_', ' ', $name);
3064
3065
    return str_replace('St ', 'St. ', $name);
3066
}
3067
3068
/**
3069
 * Provides info if user should use MFA based on roles
3070
 *
3071
 * @param string $userRolesIds  User roles ids
3072
 * @param string $mfaRoles      Roles for which MFA is requested
3073
 *
3074
 * @return bool
3075
 */
3076
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3077
{
3078
    if (empty($mfaRoles) === true) {
3079
        return true;
3080
    }
3081
3082
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3083
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3084
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3085
        return true;
3086
    }
3087
3088
    return false;
3089
}
3090
3091
/**
3092
 * Permits to clean a string for export purpose
3093
 *
3094
 * @param string $text
3095
 * @param bool $emptyCheckOnly
3096
 * 
3097
 * @return string
3098
 */
3099
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3100
{
3101
    if (is_null($text) === true || empty($text) === true) {
3102
        return '';
3103
    }
3104
    // only expected to check if $text was empty
3105
    elseif ($emptyCheckOnly === true) {
3106
        return $text;
3107
    }
3108
3109
    return strip_tags(
3110
        cleanString(
3111
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3112
            true)
3113
        );
3114
}
3115
3116
/**
3117
 * Permits to check if user ID is valid
3118
 *
3119
 * @param integer $post_user_id
3120
 * @return bool
3121
 */
3122
function isUserIdValid($userId): bool
3123
{
3124
    if (is_null($userId) === false
3125
        && isset($userId) === true
3126
        && empty($userId) === false
3127
    ) {
3128
        return true;
3129
    }
3130
    return false;
3131
}
3132
3133
/**
3134
 * Check if a key exists and if its value equal the one expected
3135
 *
3136
 * @param string $key
3137
 * @param integer|string $value
3138
 * @param array $array
3139
 * 
3140
 * @return boolean
3141
 */
3142
function isKeyExistingAndEqual(
3143
    string $key,
3144
    /*PHP8 - integer|string*/$value,
3145
    array $array
3146
): bool
3147
{
3148
    if (isset($array[$key]) === true
3149
        && (is_int($value) === true ?
3150
            (int) $array[$key] === $value :
3151
            (string) $array[$key] === $value)
3152
    ) {
3153
        return true;
3154
    }
3155
    return false;
3156
}
3157
3158
/**
3159
 * Check if a variable is not set or equal to a value
3160
 *
3161
 * @param string|null $var
3162
 * @param integer|string $value
3163
 * 
3164
 * @return boolean
3165
 */
3166
function isKeyNotSetOrEqual(
3167
    /*PHP8 - string|null*/$var,
3168
    /*PHP8 - integer|string*/$value
3169
): bool
3170
{
3171
    if (isset($var) === false
3172
        || (is_int($value) === true ?
3173
            (int) $var === $value :
3174
            (string) $var === $value)
3175
    ) {
3176
        return true;
3177
    }
3178
    return false;
3179
}
3180
3181
/**
3182
 * Check if a key exists and if its value < to the one expected
3183
 *
3184
 * @param string $key
3185
 * @param integer $value
3186
 * @param array $array
3187
 * 
3188
 * @return boolean
3189
 */
3190
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3191
{
3192
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3193
        return true;
3194
    }
3195
    return false;
3196
}
3197
3198
/**
3199
 * Check if a key exists and if its value > to the one expected
3200
 *
3201
 * @param string $key
3202
 * @param integer $value
3203
 * @param array $array
3204
 * 
3205
 * @return boolean
3206
 */
3207
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3208
{
3209
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3210
        return true;
3211
    }
3212
    return false;
3213
}
3214
3215
/**
3216
 * Check if values in array are set
3217
 * Return true if all set
3218
 * Return false if one of them is not set
3219
 *
3220
 * @param array $arrayOfValues
3221
 * @return boolean
3222
 */
3223
function isSetArrayOfValues(array $arrayOfValues): bool
3224
{
3225
    foreach($arrayOfValues as $value) {
3226
        if (isset($value) === false) {
3227
            return false;
3228
        }
3229
    }
3230
    return true;
3231
}
3232
3233
/**
3234
 * Check if values in array are set
3235
 * Return true if all set
3236
 * Return false if one of them is not set
3237
 *
3238
 * @param array $arrayOfValues
3239
 * @param integer|string $value
3240
 * @return boolean
3241
 */
3242
function isArrayOfVarsEqualToValue(
3243
    array $arrayOfVars,
3244
    /*PHP8 - integer|string*/$value
3245
) : bool
3246
{
3247
    foreach($arrayOfVars as $variable) {
3248
        if ($variable !== $value) {
3249
            return false;
3250
        }
3251
    }
3252
    return true;
3253
}
3254
3255
/**
3256
 * Checks if at least one variable in array is equal to value
3257
 *
3258
 * @param array $arrayOfValues
3259
 * @param integer|string $value
3260
 * @return boolean
3261
 */
3262
function isOneVarOfArrayEqualToValue(
3263
    array $arrayOfVars,
3264
    /*PHP8 - integer|string*/$value
3265
) : bool
3266
{
3267
    foreach($arrayOfVars as $variable) {
3268
        if ($variable === $value) {
3269
            return true;
3270
        }
3271
    }
3272
    return false;
3273
}
3274
3275
/**
3276
 * Checks is value is null, not set OR empty
3277
 *
3278
 * @param string|int|null $value
3279
 * @return boolean
3280
 */
3281
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3282
{
3283
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3284
        return true;
3285
    }
3286
    return false;
3287
}
3288
3289
/**
3290
 * Checks if value is set and if empty is equal to passed boolean
3291
 *
3292
 * @param string|int $value
3293
 * @param boolean $boolean
3294
 * @return boolean
3295
 */
3296
function isValueSetEmpty($value, $boolean = true) : bool
3297
{
3298
    if (isset($value) === true && empty($value) === $boolean) {
3299
        return true;
3300
    }
3301
    return false;
3302
}
3303
3304
/**
3305
 * Ensure Complexity is translated
3306
 *
3307
 * @return void
3308
 */
3309
function defineComplexity() : void
3310
{
3311
    // Load user's language
3312
    $lang = new Language(); 
3313
    
3314
    if (defined('TP_PW_COMPLEXITY') === false) {
3315
        define(
3316
            'TP_PW_COMPLEXITY',
3317
            [
3318
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3319
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3320
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3321
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3322
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3323
            ]
3324
        );
3325
    }
3326
}
3327
3328
/**
3329
 * Uses Sanitizer to perform data sanitization
3330
 *
3331
 * @param array     $data
3332
 * @param array     $filters
3333
 * @return array|string
3334
 */
3335
function dataSanitizer(array $data, array $filters): array|string
3336
{
3337
    // Load Sanitizer library
3338
    $sanitizer = new Sanitizer($data, $filters);
3339
3340
    // Load AntiXSS
3341
    $antiXss = new AntiXSS();
3342
3343
    // Sanitize post and get variables
3344
    return $antiXss->xss_clean($sanitizer->sanitize());
3345
}
3346
3347
/**
3348
 * Permits to manage the cache tree for a user
3349
 *
3350
 * @param integer $user_id
3351
 * @param string $data
3352
 * @param array $SETTINGS
3353
 * @param string $field_update
3354
 * @return void
3355
 */
3356
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3357
{
3358
    // Load class DB
3359
    loadClasses('DB');
3360
3361
    // Exists ?
3362
    $userCacheId = DB::queryfirstrow(
3363
        'SELECT increment_id
3364
        FROM ' . prefixTable('cache_tree') . '
3365
        WHERE user_id = %i',
3366
        $user_id
3367
    );
3368
    
3369
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3370
        // insert in table
3371
        DB::insert(
3372
            prefixTable('cache_tree'),
3373
            array(
3374
                'data' => $data,
3375
                'timestamp' => time(),
3376
                'user_id' => $user_id,
3377
                'visible_folders' => '',
3378
            )
3379
        );
3380
    } else {
3381
        if (empty($field_update) === true) {
3382
            DB::update(
3383
                prefixTable('cache_tree'),
3384
                [
3385
                    'timestamp' => time(),
3386
                    'data' => $data,
3387
                ],
3388
                'increment_id = %i',
3389
                $userCacheId['increment_id']
3390
            );
3391
        /* USELESS
3392
        } else {
3393
            DB::update(
3394
                prefixTable('cache_tree'),
3395
                [
3396
                    $field_update => $data,
3397
                ],
3398
                'increment_id = %i',
3399
                $userCacheId['increment_id']
3400
            );*/
3401
        }
3402
    }
3403
}
3404
3405
/**
3406
 * Permits to calculate a %
3407
 *
3408
 * @param float $nombre
3409
 * @param float $total
3410
 * @param float $pourcentage
3411
 * @return float
3412
 */
3413
function pourcentage(float $nombre, float $total, float $pourcentage): float
3414
{ 
3415
    $resultat = ($nombre/$total) * $pourcentage;
3416
    return round($resultat);
3417
}
3418
3419
/**
3420
 * Load the folders list from the cache
3421
 *
3422
 * @param string $fieldName
3423
 * @param string $sessionName
3424
 * @param boolean $forceRefresh
3425
 * @return array
3426
 */
3427
function loadFoldersListByCache(
3428
    string $fieldName,
3429
    string $sessionName,
3430
    bool $forceRefresh = false
3431
): array
3432
{
3433
    // Case when refresh is EXPECTED / MANDATORY
3434
    if ($forceRefresh === true) {
3435
        return [
3436
            'state' => false,
3437
            'data' => [],
3438
        ];
3439
    }
3440
    
3441
    $session = SessionManager::getSession();
3442
3443
    // Get last folder update
3444
    $lastFolderChange = DB::queryfirstrow(
3445
        'SELECT valeur FROM ' . prefixTable('misc') . '
3446
        WHERE type = %s AND intitule = %s',
3447
        'timestamp',
3448
        'last_folder_change'
3449
    );
3450
    if (DB::count() === 0) {
3451
        $lastFolderChange['valeur'] = 0;
3452
    }
3453
3454
    // Case when an update in the tree has been done
3455
    // Refresh is then mandatory
3456
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3457
        return [
3458
            'state' => false,
3459
            'data' => [],
3460
        ];
3461
    }
3462
3463
    // Does this user has the tree structure in session?
3464
    // If yes then use it
3465
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3466
        return [
3467
            'state' => true,
3468
            'data' => json_encode($session->get('user-folders_list')),
3469
        ];
3470
    }
3471
3472
    // Does this user has a tree cache
3473
    $userCacheTree = DB::queryfirstrow(
3474
        'SELECT '.$fieldName.'
3475
        FROM ' . prefixTable('cache_tree') . '
3476
        WHERE user_id = %i',
3477
        $session->get('user-id')
3478
    );
3479
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3480
        SessionManager::addRemoveFromSessionAssociativeArray(
3481
            'user-folders_list',
3482
            [$userCacheTree[$fieldName]],
3483
            'add'
3484
        );
3485
        return [
3486
            'state' => true,
3487
            'data' => $userCacheTree[$fieldName],
3488
        ];
3489
    }
3490
3491
    return [
3492
        'state' => false,
3493
        'data' => [],
3494
    ];
3495
}
3496
3497
3498
/**
3499
 * Permits to refresh the categories of folders
3500
 *
3501
 * @param array $folderIds
3502
 * @return void
3503
 */
3504
function handleFoldersCategories(
3505
    array $folderIds
3506
)
3507
{
3508
    // Load class DB
3509
    loadClasses('DB');
3510
3511
    $arr_data = array();
3512
3513
    // force full list of folders
3514
    if (count($folderIds) === 0) {
3515
        $folderIds = DB::queryFirstColumn(
3516
            'SELECT id
3517
            FROM ' . prefixTable('nested_tree') . '
3518
            WHERE personal_folder=%i',
3519
            0
3520
        );
3521
    }
3522
3523
    // Get complexity
3524
    defineComplexity();
3525
3526
    // update
3527
    foreach ($folderIds as $folder) {
3528
        // Do we have Categories
3529
        // get list of associated Categories
3530
        $arrCatList = array();
3531
        $rows_tmp = DB::query(
3532
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3533
            f.id_category AS category_id
3534
            FROM ' . prefixTable('categories_folders') . ' AS f
3535
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3536
            WHERE id_folder=%i',
3537
            $folder
3538
        );
3539
        if (DB::count() > 0) {
3540
            foreach ($rows_tmp as $row) {
3541
                $arrCatList[$row['id']] = array(
3542
                    'id' => $row['id'],
3543
                    'title' => $row['title'],
3544
                    'level' => $row['level'],
3545
                    'type' => $row['type'],
3546
                    'masked' => $row['masked'],
3547
                    'order' => $row['order'],
3548
                    'encrypted_data' => $row['encrypted_data'],
3549
                    'role_visibility' => $row['role_visibility'],
3550
                    'is_mandatory' => $row['is_mandatory'],
3551
                    'category_id' => $row['category_id'],
3552
                );
3553
            }
3554
        }
3555
        $arr_data['categories'] = $arrCatList;
3556
3557
        // Now get complexity
3558
        $valTemp = '';
3559
        $data = DB::queryFirstRow(
3560
            'SELECT valeur
3561
            FROM ' . prefixTable('misc') . '
3562
            WHERE type = %s AND intitule=%i',
3563
            'complex',
3564
            $folder
3565
        );
3566
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3567
            $valTemp = array(
3568
                'value' => $data['valeur'],
3569
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3570
            );
3571
        }
3572
        $arr_data['complexity'] = $valTemp;
3573
3574
        // Now get Roles
3575
        $valTemp = '';
3576
        $rows_tmp = DB::query(
3577
            'SELECT t.title
3578
            FROM ' . prefixTable('roles_values') . ' as v
3579
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3580
            WHERE v.folder_id = %i
3581
            GROUP BY title',
3582
            $folder
3583
        );
3584
        foreach ($rows_tmp as $record) {
3585
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3586
        }
3587
        $arr_data['visibilityRoles'] = $valTemp;
3588
3589
        // now save in DB
3590
        DB::update(
3591
            prefixTable('nested_tree'),
3592
            array(
3593
                'categories' => json_encode($arr_data),
3594
            ),
3595
            'id = %i',
3596
            $folder
3597
        );
3598
    }
3599
}
3600
3601
/**
3602
 * List all users that have specific roles
3603
 *
3604
 * @param array $roles
3605
 * @return array
3606
 */
3607
function getUsersWithRoles(
3608
    array $roles
3609
): array
3610
{
3611
    $session = SessionManager::getSession();
3612
    $arrUsers = array();
3613
3614
    foreach ($roles as $role) {
3615
        // loop on users and check if user has this role
3616
        $rows = DB::query(
3617
            'SELECT id, fonction_id
3618
            FROM ' . prefixTable('users') . '
3619
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3620
            $session->get('user-id')
3621
        );
3622
        foreach ($rows as $user) {
3623
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3624
            if (in_array($role, $userRoles, true) === true) {
3625
                array_push($arrUsers, $user['id']);
3626
            }
3627
        }
3628
    }
3629
3630
    return $arrUsers;
3631
}
3632
3633
3634
/**
3635
 * Get all users informations
3636
 *
3637
 * @param integer $userId
3638
 * @return array
3639
 */
3640
function getFullUserInfos(
3641
    int $userId
3642
): array
3643
{
3644
    if (empty($userId) === true) {
3645
        return array();
3646
    }
3647
3648
    $val = DB::queryfirstrow(
3649
        'SELECT *
3650
        FROM ' . prefixTable('users') . '
3651
        WHERE id = %i',
3652
        $userId
3653
    );
3654
3655
    return $val;
3656
}
3657
3658
/**
3659
 * Is required an upgrade
3660
 *
3661
 * @return boolean
3662
 */
3663
function upgradeRequired(): bool
3664
{
3665
    // Get settings.php
3666
    include_once __DIR__. '/../includes/config/settings.php';
3667
3668
    // Get timestamp in DB
3669
    $val = DB::queryfirstrow(
3670
        'SELECT valeur
3671
        FROM ' . prefixTable('misc') . '
3672
        WHERE type = %s AND intitule = %s',
3673
        'admin',
3674
        'upgrade_timestamp'
3675
    );
3676
    
3677
    // if not exists then error
3678
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3679
3680
    // if empty or too old then error
3681
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3682
        return true;
3683
    }
3684
3685
    return false;
3686
}
3687
3688
/**
3689
 * Permits to change the user keys on his demand
3690
 *
3691
 * @param integer $userId
3692
 * @param string $passwordClear
3693
 * @param integer $nbItemsToTreat
3694
 * @param string $encryptionKey
3695
 * @param boolean $deleteExistingKeys
3696
 * @param boolean $sendEmailToUser
3697
 * @param boolean $encryptWithUserPassword
3698
 * @param boolean $generate_user_new_password
3699
 * @param string $emailBody
3700
 * @param boolean $user_self_change
3701
 * @param string $recovery_public_key
3702
 * @param string $recovery_private_key
3703
 * @return string
3704
 */
3705
function handleUserKeys(
3706
    int $userId,
3707
    string $passwordClear,
3708
    int $nbItemsToTreat,
3709
    string $encryptionKey = '',
3710
    bool $deleteExistingKeys = false,
3711
    bool $sendEmailToUser = true,
3712
    bool $encryptWithUserPassword = false,
3713
    bool $generate_user_new_password = false,
3714
    string $emailBody = '',
3715
    bool $user_self_change = false,
3716
    string $recovery_public_key = '',
3717
    string $recovery_private_key = ''
3718
): string
3719
{
3720
    $session = SessionManager::getSession();
3721
    $lang = new Language(); 
3722
3723
    // prepapre background tasks for item keys generation        
3724
    $userTP = DB::queryFirstRow(
3725
        'SELECT pw, public_key, private_key
3726
        FROM ' . prefixTable('users') . '
3727
        WHERE id = %i',
3728
        TP_USER_ID
3729
    );
3730
    if (DB::count() > 0) {
3731
        // Do we need to generate new user password
3732
        if ($generate_user_new_password === true) {
3733
            // Generate a new password
3734
            $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3735
        }
3736
3737
        // Hash the password
3738
        $pwdlib = new PasswordLib();
3739
        $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3740
        if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3741
            return prepareExchangedData(
3742
                array(
3743
                    'error' => true,
3744
                    'message' => $lang->get('pw_hash_not_correct'),
3745
                ),
3746
                'encode'
3747
            );
3748
        }
3749
3750
        // Generate new keys
3751
        if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3752
            $userKeys = [
3753
                'public_key' => $recovery_public_key,
3754
                'private_key_clear' => $recovery_private_key,
3755
                'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3756
            ];
3757
        } else {
3758
            $userKeys = generateUserKeys($passwordClear);
3759
        }
3760
3761
        // Save in DB
3762
        DB::update(
3763
            prefixTable('users'),
3764
            array(
3765
                'pw' => $hashedPassword,
3766
                'public_key' => $userKeys['public_key'],
3767
                'private_key' => $userKeys['private_key'],
3768
            ),
3769
            'id=%i',
3770
            $userId
3771
        );
3772
3773
        // update session too
3774
        if ($userId === $session->get('user-id')) {
3775
            $session->set('user-private_key', $userKeys['private_key_clear']);
3776
            $session->set('user-public_key', $userKeys['public_key']);
3777
        }
3778
3779
        // Manage empty encryption key
3780
        // Let's take the user's password if asked and if no encryption key provided
3781
        $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3782
3783
        // Create process
3784
        DB::insert(
3785
            prefixTable('processes'),
3786
            array(
3787
                'created_at' => time(),
3788
                'process_type' => 'create_user_keys',
3789
                'arguments' => json_encode([
3790
                    'new_user_id' => (int) $userId,
3791
                    'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3792
                    'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3793
                    'owner_id' => (int) TP_USER_ID,
3794
                    'creator_pwd' => $userTP['pw'],
3795
                    'send_email' => $sendEmailToUser === true ? 1 : 0,
3796
                    'otp_provided_new_value' => 1,
3797
                    'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3798
                    'user_self_change' => $user_self_change === true ? 1 : 0,
3799
                ]),
3800
                'updated_at' => '',
3801
                'finished_at' => '',
3802
                'output' => '',
3803
            )
3804
        );
3805
        $processId = DB::insertId();
3806
3807
        // Delete existing keys
3808
        if ($deleteExistingKeys === true) {
3809
            deleteUserObjetsKeys(
3810
                (int) $userId,
3811
            );
3812
        }
3813
3814
        // Create tasks
3815
        DB::insert(
3816
            prefixTable('processes_tasks'),
3817
            array(
3818
                'process_id' => $processId,
3819
                'created_at' => time(),
3820
                'task' => json_encode([
3821
                    'step' => 'step0',
3822
                    'index' => 0,
3823
                    'nb' => $nbItemsToTreat,
3824
                ]),
3825
            )
3826
        );
3827
3828
        DB::insert(
3829
            prefixTable('processes_tasks'),
3830
            array(
3831
                'process_id' => $processId,
3832
                'created_at' => time(),
3833
                'task' => json_encode([
3834
                    'step' => 'step10',
3835
                    'index' => 0,
3836
                    'nb' => $nbItemsToTreat,
3837
                ]),
3838
            )
3839
        );
3840
3841
        DB::insert(
3842
            prefixTable('processes_tasks'),
3843
            array(
3844
                'process_id' => $processId,
3845
                'created_at' => time(),
3846
                'task' => json_encode([
3847
                    'step' => 'step20',
3848
                    'index' => 0,
3849
                    'nb' => $nbItemsToTreat,
3850
                ]),
3851
            )
3852
        );
3853
3854
        DB::insert(
3855
            prefixTable('processes_tasks'),
3856
            array(
3857
                'process_id' => $processId,
3858
                'created_at' => time(),
3859
                'task' => json_encode([
3860
                    'step' => 'step30',
3861
                    'index' => 0,
3862
                    'nb' => $nbItemsToTreat,
3863
                ]),
3864
            )
3865
        );
3866
3867
        DB::insert(
3868
            prefixTable('processes_tasks'),
3869
            array(
3870
                'process_id' => $processId,
3871
                'created_at' => time(),
3872
                'task' => json_encode([
3873
                    'step' => 'step40',
3874
                    'index' => 0,
3875
                    'nb' => $nbItemsToTreat,
3876
                ]),
3877
            )
3878
        );
3879
3880
        DB::insert(
3881
            prefixTable('processes_tasks'),
3882
            array(
3883
                'process_id' => $processId,
3884
                'created_at' => time(),
3885
                'task' => json_encode([
3886
                    'step' => 'step50',
3887
                    'index' => 0,
3888
                    'nb' => $nbItemsToTreat,
3889
                ]),
3890
            )
3891
        );
3892
3893
        DB::insert(
3894
            prefixTable('processes_tasks'),
3895
            array(
3896
                'process_id' => $processId,
3897
                'created_at' => time(),
3898
                'task' => json_encode([
3899
                    'step' => 'step60',
3900
                    'index' => 0,
3901
                    'nb' => $nbItemsToTreat,
3902
                ]),
3903
            )
3904
        );
3905
3906
        // update user's new status
3907
        DB::update(
3908
            prefixTable('users'),
3909
            [
3910
                'is_ready_for_usage' => 0,
3911
                'otp_provided' => 1,
3912
                'ongoing_process_id' => $processId,
3913
                'special' => 'generate-keys',
3914
            ],
3915
            'id=%i',
3916
            $userId
3917
        );
3918
    }
3919
3920
    return prepareExchangedData(
3921
        array(
3922
            'error' => false,
3923
            'message' => '',
3924
        ),
3925
        'encode'
3926
    );
3927
}
3928
3929
/**
3930
 * Permeits to check the consistency of date versus columns definition
3931
 *
3932
 * @param string $table
3933
 * @param array $dataFields
3934
 * @return array
3935
 */
3936
function validateDataFields(
3937
    string $table,
3938
    array $dataFields
3939
): array
3940
{
3941
    // Get table structure
3942
    $result = DB::query(
3943
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3944
        DB_NAME,
3945
        $table
3946
    );
3947
3948
    foreach ($result as $row) {
3949
        $field = $row['COLUMN_NAME'];
3950
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3951
3952
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3953
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3954
                return [
3955
                    'state' => false,
3956
                    'field' => $field,
3957
                    'maxLength' => $maxLength,
3958
                    'currentLength' => strlen((string) $dataFields[$field]),
3959
                ];
3960
            }
3961
        }
3962
    }
3963
    
3964
    return [
3965
        'state' => true,
3966
        'message' => '',
3967
    ];
3968
}
3969
3970
/**
3971
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3972
 *
3973
 * @param string $string
3974
 * @return string
3975
 */
3976
function filterVarBack(string $string): string
3977
{
3978
    $arr = [
3979
        '&#060;' => '<',
3980
        '&#062;' => '>',
3981
        '&#034;' => '"',
3982
        '&#039;' => "'",
3983
        '&#038;' => '&',
3984
    ];
3985
3986
    foreach ($arr as $key => $value) {
3987
        $string = str_replace($key, $value, $string);
3988
    }
3989
3990
    return $string;
3991
}
3992
3993
/**
3994
 * 
3995
 */
3996
function storeTask(
3997
    string $taskName,
3998
    int $user_id,
3999
    int $is_personal_folder,
4000
    int $folder_destination_id,
4001
    int $item_id,
4002
    string $object_keys
4003
)
4004
{
4005
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
4006
        // Create process
4007
        DB::insert(
4008
            prefixTable('processes'),
4009
            array(
4010
                'created_at' => time(),
4011
                'process_type' => $taskName,
4012
                'arguments' => json_encode([
4013
                    'item_id' => $item_id,
4014
                    'object_key' => $object_keys,
4015
                ]),
4016
                'updated_at' => '',
4017
                'finished_at' => '',
4018
                'output' => '',
4019
                'item_id' => $item_id,
4020
            )
4021
        );
4022
        $processId = DB::insertId();
4023
4024
        // Create tasks
4025
        // 1- Create password sharekeys for users of this new ITEM
4026
        DB::insert(
4027
            prefixTable('processes_tasks'),
4028
            array(
4029
                'process_id' => $processId,
4030
                'created_at' => time(),
4031
                'task' => json_encode([
4032
                    'step' => 'create_users_pwd_key',
4033
                    'index' => 0,
4034
                ]),
4035
            )
4036
        );
4037
4038
        // 2- Create fields sharekeys for users of this new ITEM
4039
        DB::insert(
4040
            prefixTable('processes_tasks'),
4041
            array(
4042
                'process_id' => $processId,
4043
                'created_at' => time(),
4044
                'task' => json_encode([
4045
                    'step' => 'create_users_fields_key',
4046
                    'index' => 0,
4047
                ]),
4048
            )
4049
        );
4050
4051
        // 3- Create files sharekeys for users of this new ITEM
4052
        DB::insert(
4053
            prefixTable('processes_tasks'),
4054
            array(
4055
                'process_id' => $processId,
4056
                'created_at' => time(),
4057
                'task' => json_encode([
4058
                    'step' => 'create_users_files_key',
4059
                    'index' => 0,
4060
                ]),
4061
            )
4062
        );
4063
    }
4064
}
4065
4066
/**
4067
 * Return PHP binary path
4068
 *
4069
 * @return string
4070
 */
4071
function getPHPBinary(): string
4072
{
4073
    // Get PHP binary path
4074
    $phpBinaryFinder = new PhpExecutableFinder();
4075
    $phpBinaryPath = $phpBinaryFinder->find();
4076
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4077
}
4078
4079
4080
4081
/**
4082
 * Delete unnecessary keys for personal items
4083
 *
4084
 * @param boolean $allUsers
4085
 * @param integer $user_id
4086
 * @return void
4087
 */
4088
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4089
{
4090
    if ($allUsers === true) {
4091
        // Load class DB
4092
        if (class_exists('DB') === false) {
4093
            loadClasses('DB');
4094
        }
4095
4096
        $users = DB::query(
4097
            'SELECT id
4098
            FROM ' . prefixTable('users') . '
4099
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4100
            ORDER BY login ASC'
4101
        );
4102
        foreach ($users as $user) {
4103
            purgeUnnecessaryKeysForUser((int) $user['id']);
4104
        }
4105
    } else {
4106
        purgeUnnecessaryKeysForUser((int) $user_id);
4107
    }
4108
}
4109
4110
/**
4111
 * Delete unnecessary keys for personal items
4112
 *
4113
 * @param integer $user_id
4114
 * @return void
4115
 */
4116
function purgeUnnecessaryKeysForUser(int $user_id=0)
4117
{
4118
    if ($user_id === 0) {
4119
        return;
4120
    }
4121
4122
    // Load class DB
4123
    loadClasses('DB');
4124
4125
    $personalItems = DB::queryFirstColumn(
4126
        'SELECT id
4127
        FROM ' . prefixTable('items') . ' AS i
4128
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4129
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4130
        $user_id
4131
    );
4132
    if (count($personalItems) > 0) {
4133
        // Item keys
4134
        DB::delete(
4135
            prefixTable('sharekeys_items'),
4136
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4137
            $personalItems,
4138
            $user_id
4139
        );
4140
        // Files keys
4141
        DB::delete(
4142
            prefixTable('sharekeys_files'),
4143
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4144
            $personalItems,
4145
            $user_id
4146
        );
4147
        // Fields keys
4148
        DB::delete(
4149
            prefixTable('sharekeys_fields'),
4150
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4151
            $personalItems,
4152
            $user_id
4153
        );
4154
        // Logs keys
4155
        DB::delete(
4156
            prefixTable('sharekeys_logs'),
4157
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4158
            $personalItems,
4159
            $user_id
4160
        );
4161
    }
4162
}
4163
4164
/**
4165
 * Generate recovery keys file
4166
 *
4167
 * @param integer $userId
4168
 * @param array $SETTINGS
4169
 * @return string
4170
 */
4171
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4172
{
4173
    $session = SessionManager::getSession();
4174
    // Check if user exists
4175
    $userInfo = DB::queryFirstRow(
4176
        'SELECT pw, public_key, private_key, login, name
4177
        FROM ' . prefixTable('users') . '
4178
        WHERE id = %i',
4179
        $userId
4180
    );
4181
4182
    if (DB::count() > 0) {
4183
        $now = (int) time();
4184
4185
        // Prepare file content
4186
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4187
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4188
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4189
            "Public Key:\n".$userInfo['public_key']."\n\n".
4190
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4191
4192
        // Update user's keys_recovery_time
4193
        DB::update(
4194
            prefixTable('users'),
4195
            [
4196
                'keys_recovery_time' => $now,
4197
            ],
4198
            'id=%i',
4199
            $userId
4200
        );
4201
        $session->set('user-keys_recovery_time', $now);
4202
4203
        //Log into DB the user's disconnection
4204
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4205
        
4206
        // Return data
4207
        return prepareExchangedData(
4208
            array(
4209
                'error' => false,
4210
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4211
                'timestamp' => $now,
4212
                'content' => base64_encode($export_value),
4213
                'login' => $userInfo['login'],
4214
            ),
4215
            'encode'
4216
        );
4217
    }
4218
4219
    return prepareExchangedData(
4220
        array(
4221
            'error' => true,
4222
            'datetime' => '',
4223
        ),
4224
        'encode'
4225
    );
4226
}
4227
4228
/**
4229
 * Permits to load expected classes
4230
 *
4231
 * @param string $className
4232
 * @return void
4233
 */
4234
function loadClasses(string $className = ''): void
4235
{
4236
    require_once __DIR__. '/../includes/config/include.php';
4237
    require_once __DIR__. '/../includes/config/settings.php';
4238
    require_once __DIR__.'/../vendor/autoload.php';
4239
4240
    if (defined('DB_PASSWD_CLEAR') === false) {
4241
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4242
    }
4243
4244
    if (empty($className) === false) {
4245
        // Load class DB
4246
        if ((string) $className === 'DB') {
4247
            //Connect to DB
4248
            DB::$host = DB_HOST;
4249
            DB::$user = DB_USER;
4250
            DB::$password = DB_PASSWD_CLEAR;
4251
            DB::$dbName = DB_NAME;
4252
            DB::$port = DB_PORT;
4253
            DB::$encoding = DB_ENCODING;
4254
            DB::$ssl = DB_SSL;
4255
            DB::$connect_options = DB_CONNECT_OPTIONS;
4256
        }
4257
    }
4258
4259
}
4260
4261
/**
4262
 * Returns the page the user is visiting.
4263
 *
4264
 * @return string The page name
4265
 */
4266
function getCurrectPage($SETTINGS)
4267
{
4268
    $superGlobal = new SuperGlobal();
4269
4270
    // Parse the url
4271
    parse_str(
4272
        substr(
4273
            (string) $superGlobal->get('REQUEST_URI', 'SERVER'),
4274
            strpos((string) $superGlobal->get('REQUEST_URI', 'SERVER'), '?') + 1
4275
        ),
4276
        $result
4277
    );
4278
4279
    return $result['page'];
4280
}
4281
4282
/**
4283
 * Permits to return value if set
4284
 *
4285
 * @param string|int $value
4286
 * @param string|int|null $retFalse
4287
 * @param string|int $retTrue
4288
 * @return mixed
4289
 */
4290
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4291
{
4292
4293
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4294
}
4295