Passed
Push — master ( 85a2c6...18cd31 )
by Nils
07:03 queued 14s
created

createUserTasks()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 89
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 63
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 89
rs 8.8072

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\SessionManager\SessionManager;
34
use Symfony\Component\HttpFoundation\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
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 Elegant\Sanitizer\Filters\Uppercase;
43
use PHPMailer\PHPMailer\PHPMailer;
44
use PasswordLib\PasswordLib;
45
use Symfony\Component\Process\Exception\ProcessFailedException;
46
use Symfony\Component\Process\Process;
47
use Symfony\Component\Process\PhpExecutableFinder;
48
use TeampassClasses\Encryption\Encryption;
49
50
51
// Load config if $SETTINGS not defined
52
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
53
    include_once __DIR__ . '/../includes/config/tp.config.php';
54
}
55
56
header('Content-type: text/html; charset=utf-8');
57
header('Cache-Control: no-cache, must-revalidate');
58
59
loadClasses('DB');
60
$session = SessionManager::getSession();
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
        /*
1217
        // Define custom Debug output function
1218
        $mail->Debugoutput = function($str, $level) {
1219
            // Path to your log file
1220
            $logFilePath = '/var/log/phpmailer.log';
1221
            file_put_contents($logFilePath, gmdate('Y-m-d H:i:s'). "\t$level\t$str\n", FILE_APPEND | LOCK_EX);
1222
        };
1223
        */
1224
1225
        // Configure SMTP
1226
        $mail->isSMTP();
1227
        $mail->Host = $SETTINGS['email_smtp_server'];
1228
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1;
1229
        $mail->Username = $SETTINGS['email_auth_username'];
1230
        $mail->Password = $SETTINGS['email_auth_pwd'];
1231
        $mail->Port = (int) $SETTINGS['email_port'];
1232
        $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1233
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none';
1234
        $mail->SMTPOptions = [
1235
            'ssl' => [
1236
                'verify_peer' => false,
1237
                'verify_peer_name' => false,
1238
                'allow_self_signed' => true,
1239
            ],
1240
        ];
1241
1242
        // Set From and FromName
1243
        $mail->From = $SETTINGS['email_from'];
1244
        $mail->FromName = $SETTINGS['email_from_name'];
1245
1246
        // Prepare recipients
1247
        foreach (array_filter(explode(',', $email)) as $dest) {
1248
            $mail->addAddress($dest);
1249
        }
1250
        
1251
        // Prepare HTML and AltBody
1252
        $text_html = emailBody($textMail);
1253
        $mail->WordWrap = 80;
1254
        $mail->isHtml(true);
1255
        $mail->Subject = $subject;
1256
        $mail->Body = $text_html;
1257
        $mail->AltBody = is_null($textMailAlt) ? '' : $textMailAlt;
1258
        
1259
        // Send email
1260
        $mail->send();
1261
        $mail->smtpClose();
1262
        
1263
        return '';
1264
    } catch (Exception $e) {
1265
        if (!$silent || (int) $SETTINGS['email_debug_level'] !== 0) {
1266
            return json_encode([
1267
                'error' => true,
1268
                'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1269
            ]);
1270
        }
1271
        return '';
1272
    }
1273
}
1274
1275
/**
1276
 * Returns the email body.
1277
 *
1278
 * @param string $textMail Text for the email
1279
 */
1280
function emailBody(string $textMail): string
1281
{
1282
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1283
    w3.org/TR/html4/loose.dtd"><html>
1284
    <head><title>Email Template</title>
1285
    <style type="text/css">
1286
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1287
    </style></head>
1288
    <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">
1289
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1290
    <tr><td style="border-collapse: collapse;"><br>
1291
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1292
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1293
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1294
        </td></tr></table></td>
1295
    </tr>
1296
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1297
        <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;">
1298
        <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;">
1299
        <br><div style="float:right;">' .
1300
        $textMail .
1301
        '<br><br></td></tr></table>
1302
    </td></tr></table>
1303
    <br></body></html>';
1304
}
1305
1306
/**
1307
 * Generate a Key.
1308
 * 
1309
 * @return string
1310
 */
1311
function generateKey(): string
1312
{
1313
    return substr(md5(rand() . rand()), 0, 15);
1314
}
1315
1316
/**
1317
 * Convert date to timestamp.
1318
 *
1319
 * @param string $date        The date
1320
 * @param string $date_format Date format
1321
 *
1322
 * @return int
1323
 */
1324
function dateToStamp(string $date, string $date_format): int
1325
{
1326
    $date = date_parse_from_format($date_format, $date);
1327
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1328
        return mktime(
1329
            empty($date['hour']) === false ? $date['hour'] : 23,
1330
            empty($date['minute']) === false ? $date['minute'] : 59,
1331
            empty($date['second']) === false ? $date['second'] : 59,
1332
            $date['month'],
1333
            $date['day'],
1334
            $date['year']
1335
        );
1336
    }
1337
    return 0;
1338
}
1339
1340
/**
1341
 * Is this a date.
1342
 *
1343
 * @param string $date Date
1344
 *
1345
 * @return bool
1346
 */
1347
function isDate(string $date): bool
1348
{
1349
    return strtotime($date) !== false;
1350
}
1351
1352
/**
1353
 * Check if isUTF8().
1354
 *
1355
 * @param string|array $string Is the string
1356
 *
1357
 * @return int is the string in UTF8 format
1358
 */
1359
function isUTF8($string): int
1360
{
1361
    if (is_array($string) === true) {
1362
        $string = $string['string'];
1363
    }
1364
1365
    return preg_match(
1366
        '%^(?:
1367
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1368
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1369
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1370
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1371
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1372
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1373
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1374
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1375
        )*$%xs',
1376
        $string
1377
    );
1378
}
1379
1380
/**
1381
 * Prepare an array to UTF8 format before JSON_encode.
1382
 *
1383
 * @param array $array Array of values
1384
 *
1385
 * @return array
1386
 */
1387
function utf8Converter(array $array): array
1388
{
1389
    array_walk_recursive(
1390
        $array,
1391
        static function (&$item): void {
1392
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1393
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1394
            }
1395
        }
1396
    );
1397
    return $array;
1398
}
1399
1400
/**
1401
 * Permits to prepare data to be exchanged.
1402
 *
1403
 * @param array|string $data Text
1404
 * @param string       $type Parameter
1405
 * @param string       $key  Optional key
1406
 *
1407
 * @return string|array
1408
 */
1409
function prepareExchangedData($data, string $type, ?string $key = null)
1410
{
1411
    $session = SessionManager::getSession();
1412
    
1413
    // Perform
1414
    if ($type === 'encode' && is_array($data) === true) {
1415
        // Now encode
1416
        return Encryption::encrypt(
1417
            json_encode(
1418
                $data,
1419
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1420
            ),
1421
            $session->get('key')
1422
        );
1423
    }
1424
    if ($type === 'decode' && is_array($data) === false) {
1425
        // check if key exists
1426
        return json_decode(
1427
            (string) Encryption::decrypt(
1428
                (string) $data,
1429
                $session->get('key')
1430
            ),
1431
            true
1432
        );
1433
    }
1434
    return '';
1435
}
1436
1437
1438
/**
1439
 * Create a thumbnail.
1440
 *
1441
 * @param string  $src           Source
1442
 * @param string  $dest          Destination
1443
 * @param int $desired_width Size of width
1444
 * 
1445
 * @return void|string|bool
1446
 */
1447
function makeThumbnail(string $src, string $dest, int $desired_width)
1448
{
1449
    /* read the source image */
1450
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1451
        $source_image = imagecreatefrompng($src);
1452
        if ($source_image === false) {
1453
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1454
        }
1455
    } else {
1456
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1457
    }
1458
1459
    // Get height and width
1460
    $width = imagesx($source_image);
1461
    $height = imagesy($source_image);
1462
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1463
    $desired_height = (int) floor($height * $desired_width / $width);
1464
    /* create a new, "virtual" image */
1465
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1466
    if ($virtual_image === false) {
1467
        return false;
1468
    }
1469
    /* copy source image at a resized size */
1470
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1471
    /* create the physical thumbnail image to its destination */
1472
    imagejpeg($virtual_image, $dest);
1473
}
1474
1475
/**
1476
 * Check table prefix in SQL query.
1477
 *
1478
 * @param string $table Table name
1479
 * 
1480
 * @return string
1481
 */
1482
function prefixTable(string $table): string
1483
{
1484
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1485
    if (empty($safeTable) === false) {
1486
        // sanitize string
1487
        return $safeTable;
1488
    }
1489
    // stop error no table
1490
    return 'table_not_exists';
1491
}
1492
1493
/**
1494
 * GenerateCryptKey
1495
 *
1496
 * @param int     $size      Length
1497
 * @param bool $secure Secure
1498
 * @param bool $numerals Numerics
1499
 * @param bool $uppercase Uppercase letters
1500
 * @param bool $symbols Symbols
1501
 * @param bool $lowercase Lowercase
1502
 * @param array   $SETTINGS  SETTINGS
1503
 * 
1504
 * @return string
1505
 */
1506
function GenerateCryptKey(
1507
    int $size = 20,
1508
    bool $secure = false,
1509
    bool $numerals = false,
1510
    bool $uppercase = false,
1511
    bool $symbols = false,
1512
    bool $lowercase = false,
1513
    array $SETTINGS = []
1514
): string {
1515
    $generator = new ComputerPasswordGenerator();
1516
    $generator->setRandomGenerator(new Php7RandomGenerator());
1517
    
1518
    // Manage size
1519
    $generator->setLength((int) $size);
1520
    if ($secure === true) {
1521
        $generator->setSymbols(true);
1522
        $generator->setLowercase(true);
1523
        $generator->setUppercase(true);
1524
        $generator->setNumbers(true);
1525
    } else {
1526
        $generator->setLowercase($lowercase);
1527
        $generator->setUppercase($uppercase);
1528
        $generator->setNumbers($numerals);
1529
        $generator->setSymbols($symbols);
1530
    }
1531
1532
    return $generator->generatePasswords()[0];
1533
}
1534
1535
/**
1536
 * Send sysLOG message
1537
 *
1538
 * @param string    $message
1539
 * @param string    $host
1540
 * @param int       $port
1541
 * @param string    $component
1542
 * 
1543
 * @return void
1544
*/
1545
function send_syslog($message, $host, $port, $component = 'teampass'): void
1546
{
1547
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1548
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1549
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1550
    socket_close($sock);
1551
}
1552
1553
/**
1554
 * Permits to log events into DB
1555
 *
1556
 * @param array  $SETTINGS Teampass settings
1557
 * @param string $type     Type
1558
 * @param string $label    Label
1559
 * @param string $who      Who
1560
 * @param string $login    Login
1561
 * @param string|int $field_1  Field
1562
 * 
1563
 * @return void
1564
 */
1565
function logEvents(
1566
    array $SETTINGS, 
1567
    string $type, 
1568
    string $label, 
1569
    string $who, 
1570
    ?string $login = null, 
1571
    $field_1 = null
1572
): void
1573
{
1574
    if (empty($who)) {
1575
        $who = getClientIpServer();
1576
    }
1577
1578
    // Load class DB
1579
    loadClasses('DB');
1580
1581
    DB::insert(
1582
        prefixTable('log_system'),
1583
        [
1584
            'type' => $type,
1585
            'date' => time(),
1586
            'label' => $label,
1587
            'qui' => $who,
1588
            'field_1' => $field_1 === null ? '' : $field_1,
1589
        ]
1590
    );
1591
    // If SYSLOG
1592
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1593
        if ($type === 'user_mngt') {
1594
            send_syslog(
1595
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1596
                $SETTINGS['syslog_host'],
1597
                $SETTINGS['syslog_port'],
1598
                'teampass'
1599
            );
1600
        } else {
1601
            send_syslog(
1602
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1603
                $SETTINGS['syslog_host'],
1604
                $SETTINGS['syslog_port'],
1605
                'teampass'
1606
            );
1607
        }
1608
    }
1609
}
1610
1611
/**
1612
 * Log events.
1613
 *
1614
 * @param array  $SETTINGS        Teampass settings
1615
 * @param int    $item_id         Item id
1616
 * @param string $item_label      Item label
1617
 * @param int    $id_user         User id
1618
 * @param string $action          Code for reason
1619
 * @param string $login           User login
1620
 * @param string $raison          Code for reason
1621
 * @param string $encryption_type Encryption on
1622
 * @param string $time Encryption Time
1623
 * @param string $old_value       Old value
1624
 * 
1625
 * @return void
1626
 */
1627
function logItems(
1628
    array $SETTINGS,
1629
    int $item_id,
1630
    string $item_label,
1631
    int $id_user,
1632
    string $action,
1633
    ?string $login = null,
1634
    ?string $raison = null,
1635
    ?string $encryption_type = null,
1636
    ?string $time = null,
1637
    ?string $old_value = null
1638
): void {
1639
    // Load class DB
1640
    loadClasses('DB');
1641
1642
    // Insert log in DB
1643
    DB::insert(
1644
        prefixTable('log_items'),
1645
        [
1646
            'id_item' => $item_id,
1647
            'date' => is_null($time) === true ? time() : $time,
1648
            'id_user' => $id_user,
1649
            'action' => $action,
1650
            'raison' => $raison,
1651
            'old_value' => $old_value,
1652
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1653
        ]
1654
    );
1655
    // Timestamp the last change
1656
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1657
        DB::update(
1658
            prefixTable('misc'),
1659
            [
1660
                'valeur' => time(),
1661
            ],
1662
            'type = %s AND intitule = %s',
1663
            'timestamp',
1664
            'last_item_change'
1665
        );
1666
    }
1667
1668
    // SYSLOG
1669
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1670
        // Extract reason
1671
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1672
        // Get item info if not known
1673
        if (empty($item_label) === true) {
1674
            $dataItem = DB::queryfirstrow(
1675
                'SELECT id, id_tree, label
1676
                FROM ' . prefixTable('items') . '
1677
                WHERE id = %i',
1678
                $item_id
1679
            );
1680
            $item_label = $dataItem['label'];
1681
        }
1682
1683
        send_syslog(
1684
            'action=' . str_replace('at_', '', $action) .
1685
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1686
                ' itemno=' . $item_id .
1687
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1688
                ' itemname="' . addslashes($item_label) . '"',
1689
            $SETTINGS['syslog_host'],
1690
            $SETTINGS['syslog_port'],
1691
            'teampass'
1692
        );
1693
    }
1694
1695
    // send notification if enabled
1696
    //notifyOnChange($item_id, $action, $SETTINGS);
1697
}
1698
1699
/**
1700
 * Prepare notification email to subscribers.
1701
 *
1702
 * @param int    $item_id  Item id
1703
 * @param string $label    Item label
1704
 * @param array  $changes  List of changes
1705
 * @param array  $SETTINGS Teampass settings
1706
 * 
1707
 * @return void
1708
 */
1709
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1710
{
1711
    $session = SessionManager::getSession();
1712
    $lang = new Language(); 
1713
    $globalsUserId = $session->get('user-id');
1714
    $globalsLastname = $session->get('user-lastname');
1715
    $globalsName = $session->get('user-name');
1716
    // send email to user that what to be notified
1717
    $notification = DB::queryOneColumn(
1718
        'email',
1719
        'SELECT *
1720
        FROM ' . prefixTable('notification') . ' AS n
1721
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1722
        WHERE n.item_id = %i AND n.user_id != %i',
1723
        $item_id,
1724
        $globalsUserId
1725
    );
1726
    if (DB::count() > 0) {
1727
        // Prepare path
1728
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1729
        // Get list of changes
1730
        $htmlChanges = '<ul>';
1731
        foreach ($changes as $change) {
1732
            $htmlChanges .= '<li>' . $change . '</li>';
1733
        }
1734
        $htmlChanges .= '</ul>';
1735
        // send email
1736
        DB::insert(
1737
            prefixTable('emails'),
1738
            [
1739
                'timestamp' => time(),
1740
                'subject' => $lang->get('email_subject_item_updated'),
1741
                'body' => str_replace(
1742
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1743
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1744
                    $lang->get('email_body_item_updated')
1745
                ),
1746
                'receivers' => implode(',', $notification),
1747
                'status' => '',
1748
            ]
1749
        );
1750
    }
1751
}
1752
1753
/**
1754
 * Returns the Item + path.
1755
 *
1756
 * @param int    $id_tree  Node id
1757
 * @param string $label    Label
1758
 * @param array  $SETTINGS TP settings
1759
 * 
1760
 * @return string
1761
 */
1762
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1763
{
1764
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1765
    $arbo = $tree->getPath($id_tree, true);
1766
    $path = '';
1767
    foreach ($arbo as $elem) {
1768
        if (empty($path) === true) {
1769
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1770
        } else {
1771
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1772
        }
1773
    }
1774
1775
    // Build text to show user
1776
    if (empty($label) === false) {
1777
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1778
    }
1779
    return empty($path) === true ? '' : $path;
1780
}
1781
1782
/**
1783
 * Get the client ip address.
1784
 *
1785
 * @return string IP address
1786
 */
1787
function getClientIpServer(): string
1788
{
1789
    if (getenv('HTTP_CLIENT_IP')) {
1790
        $ipaddress = getenv('HTTP_CLIENT_IP');
1791
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1792
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1793
    } elseif (getenv('HTTP_X_FORWARDED')) {
1794
        $ipaddress = getenv('HTTP_X_FORWARDED');
1795
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1796
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1797
    } elseif (getenv('HTTP_FORWARDED')) {
1798
        $ipaddress = getenv('HTTP_FORWARDED');
1799
    } elseif (getenv('REMOTE_ADDR')) {
1800
        $ipaddress = getenv('REMOTE_ADDR');
1801
    } else {
1802
        $ipaddress = 'UNKNOWN';
1803
    }
1804
1805
    return $ipaddress;
1806
}
1807
1808
/**
1809
 * Escape all HTML, JavaScript, and CSS.
1810
 *
1811
 * @param string $input    The input string
1812
 * @param string $encoding Which character encoding are we using?
1813
 * 
1814
 * @return string
1815
 */
1816
function noHTML(string $input, string $encoding = 'UTF-8'): string
1817
{
1818
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1819
}
1820
1821
/**
1822
 * Permits to handle the Teampass config file
1823
 * $action accepts "rebuild" and "update"
1824
 *
1825
 * @param string $action   Action to perform
1826
 * @param array  $SETTINGS Teampass settings
1827
 * @param string $field    Field to refresh
1828
 * @param string $value    Value to set
1829
 *
1830
 * @return string|bool
1831
 */
1832
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1833
{
1834
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1835
1836
    // Load class DB
1837
    loadClasses('DB');
1838
1839
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1840
        // perform a copy
1841
        if (file_exists($tp_config_file)) {
1842
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1843
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1844
            }
1845
        }
1846
1847
        // regenerate
1848
        $data = [];
1849
        $data[0] = "<?php\n";
1850
        $data[1] = "global \$SETTINGS;\n";
1851
        $data[2] = "\$SETTINGS = array (\n";
1852
        $rows = DB::query(
1853
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1854
            'admin'
1855
        );
1856
        foreach ($rows as $record) {
1857
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
1858
        }
1859
        array_push($data, ");\n");
1860
        $data = array_unique($data);
1861
    // ---
1862
    } elseif ($action === 'update' && empty($field) === false) {
1863
        $data = file($tp_config_file);
1864
        $inc = 0;
1865
        $bFound = false;
1866
        foreach ($data as $line) {
1867
            if (stristr($line, ');')) {
1868
                break;
1869
            }
1870
1871
            if (stristr($line, "'" . $field . "' => '")) {
1872
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT) . "',\n";
1873
                $bFound = true;
1874
                break;
1875
            }
1876
            ++$inc;
1877
        }
1878
        if ($bFound === false) {
1879
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT). "',\n);\n";
1880
        }
1881
    }
1882
1883
    // update file
1884
    file_put_contents($tp_config_file, implode('', $data ?? []));
1885
    return true;
1886
}
1887
1888
/**
1889
 * Permits to replace &#92; to permit correct display
1890
 *
1891
 * @param string $input Some text
1892
 * 
1893
 * @return string
1894
 */
1895
function handleBackslash(string $input): string
1896
{
1897
    return str_replace('&amp;#92;', '&#92;', $input);
1898
}
1899
1900
/**
1901
 * Permits to load settings
1902
 * 
1903
 * @return void
1904
*/
1905
function loadSettings(): void
1906
{
1907
    global $SETTINGS;
1908
    /* LOAD CPASSMAN SETTINGS */
1909
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1910
        $SETTINGS = [];
1911
        $SETTINGS['duplicate_folder'] = 0;
1912
        //by default, this is set to 0;
1913
        $SETTINGS['duplicate_item'] = 0;
1914
        //by default, this is set to 0;
1915
        $SETTINGS['number_of_used_pw'] = 5;
1916
        //by default, this value is set to 5;
1917
        $settings = [];
1918
        $rows = DB::query(
1919
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1920
            [
1921
                'type' => 'admin',
1922
                'type2' => 'settings',
1923
            ]
1924
        );
1925
        foreach ($rows as $record) {
1926
            if ($record['type'] === 'admin') {
1927
                $SETTINGS[$record['intitule']] = $record['valeur'];
1928
            } else {
1929
                $settings[$record['intitule']] = $record['valeur'];
1930
            }
1931
        }
1932
        $SETTINGS['loaded'] = 1;
1933
        $SETTINGS['default_session_expiration_time'] = 5;
1934
    }
1935
}
1936
1937
/**
1938
 * check if folder has custom fields.
1939
 * Ensure that target one also has same custom fields
1940
 * 
1941
 * @param int $source_id
1942
 * @param int $target_id 
1943
 * 
1944
 * @return bool
1945
*/
1946
function checkCFconsistency(int $source_id, int $target_id): bool
1947
{
1948
    $source_cf = [];
1949
    $rows = DB::QUERY(
1950
        'SELECT id_category
1951
            FROM ' . prefixTable('categories_folders') . '
1952
            WHERE id_folder = %i',
1953
        $source_id
1954
    );
1955
    foreach ($rows as $record) {
1956
        array_push($source_cf, $record['id_category']);
1957
    }
1958
1959
    $target_cf = [];
1960
    $rows = DB::QUERY(
1961
        'SELECT id_category
1962
            FROM ' . prefixTable('categories_folders') . '
1963
            WHERE id_folder = %i',
1964
        $target_id
1965
    );
1966
    foreach ($rows as $record) {
1967
        array_push($target_cf, $record['id_category']);
1968
    }
1969
1970
    $cf_diff = array_diff($source_cf, $target_cf);
1971
    if (count($cf_diff) > 0) {
1972
        return false;
1973
    }
1974
1975
    return true;
1976
}
1977
1978
/**
1979
 * Will encrypte/decrypt a fil eusing Defuse.
1980
 *
1981
 * @param string $type        can be either encrypt or decrypt
1982
 * @param string $source_file path to source file
1983
 * @param string $target_file path to target file
1984
 * @param array  $SETTINGS    Settings
1985
 * @param string $password    A password
1986
 *
1987
 * @return string|bool
1988
 */
1989
function prepareFileWithDefuse(
1990
    string $type,
1991
    string $source_file,
1992
    string $target_file,
1993
    array $SETTINGS,
1994
    string $password = null
1995
) {
1996
    // Load AntiXSS
1997
    $antiXss = new AntiXSS();
1998
    // Protect against bad inputs
1999
    if (is_array($source_file) === true || is_array($target_file) === true) {
2000
        return 'error_cannot_be_array';
2001
    }
2002
2003
    // Sanitize
2004
    $source_file = $antiXss->xss_clean($source_file);
2005
    $target_file = $antiXss->xss_clean($target_file);
2006
    if (empty($password) === true || is_null($password) === true) {
2007
        // get KEY to define password
2008
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2009
        $password = Key::loadFromAsciiSafeString($ascii_key);
2010
    }
2011
2012
    $err = '';
2013
    if ($type === 'decrypt') {
2014
        // Decrypt file
2015
        $err = defuseFileDecrypt(
2016
            $source_file,
2017
            $target_file,
2018
            $SETTINGS, /** @scrutinizer ignore-type */
2019
            $password
2020
        );
2021
    } elseif ($type === 'encrypt') {
2022
        // Encrypt file
2023
        $err = defuseFileEncrypt(
2024
            $source_file,
2025
            $target_file,
2026
            $SETTINGS, /** @scrutinizer ignore-type */
2027
            $password
2028
        );
2029
    }
2030
2031
    // return error
2032
    return $err === true ? '' : $err;
2033
}
2034
2035
/**
2036
 * Encrypt a file with Defuse.
2037
 *
2038
 * @param string $source_file path to source file
2039
 * @param string $target_file path to target file
2040
 * @param array  $SETTINGS    Settings
2041
 * @param string $password    A password
2042
 *
2043
 * @return string|bool
2044
 */
2045
function defuseFileEncrypt(
2046
    string $source_file,
2047
    string $target_file,
2048
    array $SETTINGS,
2049
    string $password = null
2050
) {
2051
    try {
2052
        CryptoFile::encryptFileWithPassword(
2053
            $source_file,
2054
            $target_file,
2055
            $password
2056
        );
2057
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2058
        $err = 'wrong_key';
2059
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2060
        $err = $ex;
2061
    } catch (CryptoException\IOException $ex) {
2062
        $err = $ex;
2063
    }
2064
2065
    // return error
2066
    return empty($err) === false ? $err : true;
2067
}
2068
2069
/**
2070
 * Decrypt a file with Defuse.
2071
 *
2072
 * @param string $source_file path to source file
2073
 * @param string $target_file path to target file
2074
 * @param array  $SETTINGS    Settings
2075
 * @param string $password    A password
2076
 *
2077
 * @return string|bool
2078
 */
2079
function defuseFileDecrypt(
2080
    string $source_file,
2081
    string $target_file,
2082
    array $SETTINGS,
2083
    string $password = null
2084
) {
2085
    try {
2086
        CryptoFile::decryptFileWithPassword(
2087
            $source_file,
2088
            $target_file,
2089
            $password
2090
        );
2091
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2092
        $err = 'wrong_key';
2093
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2094
        $err = $ex;
2095
    } catch (CryptoException\IOException $ex) {
2096
        $err = $ex;
2097
    }
2098
2099
    // return error
2100
    return empty($err) === false ? $err : true;
2101
}
2102
2103
/*
2104
* NOT TO BE USED
2105
*/
2106
/**
2107
 * Undocumented function.
2108
 *
2109
 * @param string $text Text to debug
2110
 */
2111
function debugTeampass(string $text): void
2112
{
2113
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2114
    if ($debugFile !== false) {
2115
        fputs($debugFile, $text);
2116
        fclose($debugFile);
2117
    }
2118
}
2119
2120
/**
2121
 * DELETE the file with expected command depending on server type.
2122
 *
2123
 * @param string $file     Path to file
2124
 * @param array  $SETTINGS Teampass settings
2125
 *
2126
 * @return void
2127
 */
2128
function fileDelete(string $file, array $SETTINGS): void
2129
{
2130
    // Load AntiXSS
2131
    $antiXss = new AntiXSS();
2132
    $file = $antiXss->xss_clean($file);
2133
    if (is_file($file)) {
2134
        unlink($file);
2135
    }
2136
}
2137
2138
/**
2139
 * Permits to extract the file extension.
2140
 *
2141
 * @param string $file File name
2142
 *
2143
 * @return string
2144
 */
2145
function getFileExtension(string $file): string
2146
{
2147
    if (strpos($file, '.') === false) {
2148
        return $file;
2149
    }
2150
2151
    return substr($file, strrpos($file, '.') + 1);
2152
}
2153
2154
/**
2155
 * Chmods files and folders with different permissions.
2156
 *
2157
 * This is an all-PHP alternative to using: \n
2158
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2159
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2160
 *
2161
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2162
  *
2163
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2164
 * @param int    $filePerm The permissions any found files should get.
2165
 * @param int    $dirPerm  The permissions any found folder should get.
2166
 *
2167
 * @return bool Returns TRUE if the path if found and FALSE if not.
2168
 *
2169
 * @warning The permission levels has to be entered in octal format, which
2170
 * normally means adding a zero ("0") in front of the permission level. \n
2171
 * More info at: http://php.net/chmod.
2172
*/
2173
2174
function recursiveChmod(
2175
    string $path,
2176
    int $filePerm = 0644,
2177
    int  $dirPerm = 0755
2178
) {
2179
    // Check if the path exists
2180
    if (! file_exists($path)) {
2181
        return false;
2182
    }
2183
2184
    // See whether this is a file
2185
    if (is_file($path)) {
2186
        // Chmod the file with our given filepermissions
2187
        try {
2188
            chmod($path, $filePerm);
2189
        } catch (Exception $e) {
2190
            return false;
2191
        }
2192
    // If this is a directory...
2193
    } elseif (is_dir($path)) {
2194
        // Then get an array of the contents
2195
        $foldersAndFiles = scandir($path);
2196
        // Remove "." and ".." from the list
2197
        $entries = array_slice($foldersAndFiles, 2);
2198
        // Parse every result...
2199
        foreach ($entries as $entry) {
2200
            // And call this function again recursively, with the same permissions
2201
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2202
        }
2203
2204
        // When we are done with the contents of the directory, we chmod the directory itself
2205
        try {
2206
            chmod($path, $filePerm);
2207
        } catch (Exception $e) {
2208
            return false;
2209
        }
2210
    }
2211
2212
    // Everything seemed to work out well, return true
2213
    return true;
2214
}
2215
2216
/**
2217
 * Check if user can access to this item.
2218
 *
2219
 * @param int   $item_id ID of item
2220
 * @param array $SETTINGS
2221
 *
2222
 * @return bool|string
2223
 */
2224
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2225
{
2226
    
2227
    $session = SessionManager::getSession();
2228
    $session_groupes_visibles = $session->get('user-accessible_folders');
2229
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2230
    // Load item data
2231
    $data = DB::queryFirstRow(
2232
        'SELECT id_tree
2233
        FROM ' . prefixTable('items') . '
2234
        WHERE id = %i',
2235
        $item_id
2236
    );
2237
    // Check if user can access this folder
2238
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2239
        // Now check if this folder is restricted to user
2240
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2241
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2242
        ) {
2243
            return 'ERR_FOLDER_NOT_ALLOWED';
2244
        }
2245
    }
2246
2247
    return true;
2248
}
2249
2250
/**
2251
 * Creates a unique key.
2252
 *
2253
 * @param int $lenght Key lenght
2254
 *
2255
 * @return string
2256
 */
2257
function uniqidReal(int $lenght = 13): string
2258
{
2259
    if (function_exists('random_bytes')) {
2260
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2261
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2262
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2263
    } else {
2264
        throw new Exception('no cryptographically secure random function available');
2265
    }
2266
2267
    return substr(bin2hex($bytes), 0, $lenght);
2268
}
2269
2270
/**
2271
 * Obfuscate an email.
2272
 *
2273
 * @param string $email Email address
2274
 *
2275
 * @return string
2276
 */
2277
function obfuscateEmail(string $email): string
2278
{
2279
    $email = explode("@", $email);
2280
    $name = $email[0];
2281
    if (strlen($name) > 3) {
2282
        $name = substr($name, 0, 2);
2283
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2284
            $name .= "*";
2285
        }
2286
        $name .= substr($email[0], -1, 1);
2287
    }
2288
    $host = explode(".", $email[1])[0];
2289
    if (strlen($host) > 3) {
2290
        $host = substr($host, 0, 1);
2291
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2292
            $host .= "*";
2293
        }
2294
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2295
    }
2296
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2297
    return $email;
2298
}
2299
2300
/**
2301
 * Perform a Query.
2302
 *
2303
 * @param array  $SETTINGS Teamapss settings
2304
 * @param string $fields   Fields to use
2305
 * @param string $table    Table to use
2306
 *
2307
 * @return array
2308
 */
2309
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2310
{
2311
    // include librairies & connect to DB
2312
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2313
2314
    // Load class DB
2315
    loadClasses('DB');
2316
    
2317
    // Insert log in DB
2318
    return DB::query(
2319
        'SELECT ' . $fields . '
2320
        FROM ' . prefixTable($table)
2321
    );
2322
}
2323
2324
/**
2325
 * Undocumented function.
2326
 *
2327
 * @param int $bytes Size of file
2328
 *
2329
 * @return string
2330
 */
2331
function formatSizeUnits(int $bytes): string
2332
{
2333
    if ($bytes >= 1073741824) {
2334
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2335
    } elseif ($bytes >= 1048576) {
2336
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2337
    } elseif ($bytes >= 1024) {
2338
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2339
    } elseif ($bytes > 1) {
2340
        $bytes .= ' bytes';
2341
    } elseif ($bytes === 1) {
2342
        $bytes .= ' byte';
2343
    } else {
2344
        $bytes = '0 bytes';
2345
    }
2346
2347
    return $bytes;
2348
}
2349
2350
/**
2351
 * Generate user pair of keys.
2352
 *
2353
 * @param string $userPwd User password
2354
 *
2355
 * @return array
2356
 */
2357
function generateUserKeys(string $userPwd): array
2358
{
2359
    // Sanitize
2360
    $antiXss = new AntiXSS();
2361
    $userPwd = $antiXss->xss_clean($userPwd);
2362
    // Load classes
2363
    $rsa = new Crypt_RSA();
2364
    $cipher = new Crypt_AES();
2365
    // Create the private and public key
2366
    $res = $rsa->createKey(4096);
2367
    // Encrypt the privatekey
2368
    $cipher->setPassword($userPwd);
2369
    $privatekey = $cipher->encrypt($res['privatekey']);
2370
    return [
2371
        'private_key' => base64_encode($privatekey),
2372
        'public_key' => base64_encode($res['publickey']),
2373
        'private_key_clear' => base64_encode($res['privatekey']),
2374
    ];
2375
}
2376
2377
/**
2378
 * Permits to decrypt the user's privatekey.
2379
 *
2380
 * @param string $userPwd        User password
2381
 * @param string $userPrivateKey User private key
2382
 *
2383
 * @return string|object
2384
 */
2385
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2386
{
2387
    // Sanitize
2388
    $antiXss = new AntiXSS();
2389
    $userPwd = $antiXss->xss_clean($userPwd);
2390
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2391
2392
    if (empty($userPwd) === false) {
2393
        // Load classes
2394
        $cipher = new Crypt_AES();
2395
        // Encrypt the privatekey
2396
        $cipher->setPassword($userPwd);
2397
        try {
2398
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2399
        } catch (Exception $e) {
2400
            return $e;
2401
        }
2402
    }
2403
    return '';
2404
}
2405
2406
/**
2407
 * Permits to encrypt the user's privatekey.
2408
 *
2409
 * @param string $userPwd        User password
2410
 * @param string $userPrivateKey User private key
2411
 *
2412
 * @return string
2413
 */
2414
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2415
{
2416
    // Sanitize
2417
    $antiXss = new AntiXSS();
2418
    $userPwd = $antiXss->xss_clean($userPwd);
2419
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2420
2421
    if (empty($userPwd) === false) {
2422
        // Load classes
2423
        $cipher = new Crypt_AES();
2424
        // Encrypt the privatekey
2425
        $cipher->setPassword($userPwd);        
2426
        try {
2427
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2428
        } catch (Exception $e) {
2429
            return $e;
2430
        }
2431
    }
2432
    return '';
2433
}
2434
2435
/**
2436
 * Encrypts a string using AES.
2437
 *
2438
 * @param string $data String to encrypt
2439
 * @param string $key
2440
 *
2441
 * @return array
2442
 */
2443
function doDataEncryption(string $data, string $key = NULL): array
2444
{
2445
    // Sanitize
2446
    $antiXss = new AntiXSS();
2447
    $data = $antiXss->xss_clean($data);
2448
    
2449
    // Load classes
2450
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2451
    // Generate an object key
2452
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $antiXss->xss_clean($key);
2453
    // Set it as password
2454
    $cipher->setPassword($objectKey);
2455
    return [
2456
        'encrypted' => base64_encode($cipher->encrypt($data)),
2457
        'objectKey' => base64_encode($objectKey),
2458
    ];
2459
}
2460
2461
/**
2462
 * Decrypts a string using AES.
2463
 *
2464
 * @param string $data Encrypted data
2465
 * @param string $key  Key to uncrypt
2466
 *
2467
 * @return string
2468
 */
2469
function doDataDecryption(string $data, string $key): string
2470
{
2471
    // Sanitize
2472
    $antiXss = new AntiXSS();
2473
    $data = $antiXss->xss_clean($data);
2474
    $key = $antiXss->xss_clean($key);
2475
2476
    // Load classes
2477
    $cipher = new Crypt_AES();
2478
    // Set the object key
2479
    $cipher->setPassword(base64_decode($key));
2480
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2481
}
2482
2483
/**
2484
 * Encrypts using RSA a string using a public key.
2485
 *
2486
 * @param string $key       Key to be encrypted
2487
 * @param string $publicKey User public key
2488
 *
2489
 * @return string
2490
 */
2491
function encryptUserObjectKey(string $key, string $publicKey): string
2492
{
2493
    // Sanitize
2494
    $antiXss = new AntiXSS();
2495
    $publicKey = $antiXss->xss_clean($publicKey);
2496
    // Load classes
2497
    $rsa = new Crypt_RSA();
2498
    // Load the public key
2499
    $decodedPublicKey = base64_decode($publicKey, true);
2500
    if ($decodedPublicKey === false) {
2501
        throw new InvalidArgumentException("Error while decoding key.");
2502
    }
2503
    $rsa->loadKey($decodedPublicKey);
2504
    // Encrypt
2505
    $encrypted = $rsa->encrypt(base64_decode($key));
2506
    if ($encrypted === false) {
0 ignored issues
show
introduced by
The condition $encrypted === false is always false.
Loading history...
2507
        throw new RuntimeException("Error while encrypting key.");
2508
    }
2509
    // Return
2510
    return base64_encode($encrypted);
2511
}
2512
2513
/**
2514
 * Decrypts using RSA an encrypted string using a private key.
2515
 *
2516
 * @param string $key        Encrypted key
2517
 * @param string $privateKey User private key
2518
 *
2519
 * @return string
2520
 */
2521
function decryptUserObjectKey(string $key, string $privateKey): string
2522
{
2523
    // Sanitize
2524
    $antiXss = new AntiXSS();
2525
    $privateKey = $antiXss->xss_clean($privateKey);
2526
2527
    // Load classes
2528
    $rsa = new Crypt_RSA();
2529
    // Load the private key
2530
    $decodedPrivateKey = base64_decode($privateKey, true);
2531
    if ($decodedPrivateKey === false) {
2532
        throw new InvalidArgumentException("Error while decoding private key.");
2533
    }
2534
2535
    $rsa->loadKey($decodedPrivateKey);
2536
2537
    // Decrypt
2538
    try {
2539
        $decodedKey = base64_decode($key, true);
2540
        if ($decodedKey === false) {
2541
            throw new InvalidArgumentException("Error while decoding key.");
2542
        }
2543
2544
        $tmpValue = $rsa->decrypt($decodedKey);
2545
        if ($tmpValue !== false) {
0 ignored issues
show
introduced by
The condition $tmpValue !== false is always true.
Loading history...
2546
            return base64_encode($tmpValue);
2547
        } else {
2548
            return '';
2549
        }
2550
    } catch (Exception $e) {
2551
        return 'Exception: ' . $e->getMessage();
2552
    }
2553
}
2554
2555
/**
2556
 * Encrypts a file.
2557
 *
2558
 * @param string $fileInName File name
2559
 * @param string $fileInPath Path to file
2560
 *
2561
 * @return array
2562
 */
2563
function encryptFile(string $fileInName, string $fileInPath): array
2564
{
2565
    if (defined('FILE_BUFFER_SIZE') === false) {
2566
        define('FILE_BUFFER_SIZE', 128 * 1024);
2567
    }
2568
2569
    // Load classes
2570
    $cipher = new Crypt_AES();
2571
    $antiXSS = new AntiXSS();
2572
2573
    // Generate an object key
2574
    $objectKey = uniqidReal(32);
2575
    // Set it as password
2576
    $cipher->setPassword($objectKey);
2577
    // Prevent against out of memory
2578
    $cipher->enableContinuousBuffer();
2579
2580
    // Encrypt the file content
2581
    $filePath = filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL);
2582
    $fileContent = file_get_contents($filePath);
2583
    $plaintext = $antiXSS->xss_clean($fileContent);
2584
    $ciphertext = $cipher->encrypt($plaintext);
2585
2586
    // Save new file
2587
    $hash = md5($plaintext);
2588
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2589
    file_put_contents($fileOut, $ciphertext);
2590
    unlink($fileInPath . '/' . $fileInName);
2591
    return [
2592
        'fileHash' => base64_encode($hash),
2593
        'objectKey' => base64_encode($objectKey),
2594
    ];
2595
}
2596
2597
/**
2598
 * Decrypt a file.
2599
 *
2600
 * @param string $fileName File name
2601
 * @param string $filePath Path to file
2602
 * @param string $key      Key to use
2603
 *
2604
 * @return string
2605
 */
2606
function decryptFile(string $fileName, string $filePath, string $key): string
2607
{
2608
    if (! defined('FILE_BUFFER_SIZE')) {
2609
        define('FILE_BUFFER_SIZE', 128 * 1024);
2610
    }
2611
    
2612
    // Load classes
2613
    $cipher = new Crypt_AES();
2614
    $antiXSS = new AntiXSS();
2615
    
2616
    // Get file name
2617
    $safeFileName = $antiXSS->xss_clean(base64_decode($fileName));
2618
2619
    // Set the object key
2620
    $cipher->setPassword(base64_decode($key));
2621
    // Prevent against out of memory
2622
    $cipher->enableContinuousBuffer();
2623
    $cipher->disablePadding();
2624
    // Get file content
2625
    $safeFilePath = $filePath . '/' . TP_FILE_PREFIX . $safeFileName;
2626
    $ciphertext = file_get_contents(filter_var($safeFilePath, FILTER_SANITIZE_URL));
2627
2628
    // Decrypt file content and return
2629
    return base64_encode($cipher->decrypt($ciphertext));
2630
}
2631
2632
/**
2633
 * Generate a simple password
2634
 *
2635
 * @param int $length Length of string
2636
 * @param bool $symbolsincluded Allow symbols
2637
 *
2638
 * @return string
2639
 */
2640
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2641
{
2642
    // Generate new user password
2643
    $small_letters = range('a', 'z');
2644
    $big_letters = range('A', 'Z');
2645
    $digits = range(0, 9);
2646
    $symbols = $symbolsincluded === true ?
2647
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2648
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2649
    $count = count($res);
2650
    // first variant
2651
2652
    $random_string = '';
2653
    for ($i = 0; $i < $length; ++$i) {
2654
        $random_string .= $res[random_int(0, $count - 1)];
2655
    }
2656
2657
    return $random_string;
2658
}
2659
2660
/**
2661
 * Permit to store the sharekey of an object for users.
2662
 *
2663
 * @param string $object_name             Type for table selection
2664
 * @param int    $post_folder_is_personal Personal
2665
 * @param int    $post_folder_id          Folder
2666
 * @param int    $post_object_id          Object
2667
 * @param string $objectKey               Object key
2668
 * @param array  $SETTINGS                Teampass settings
2669
 * @param int    $user_id                 User ID if needed
2670
 * @param bool   $onlyForUser                 User ID if needed
2671
 * @param bool   $deleteAll                 User ID if needed
2672
 * @param array  $objectKeyArray                 User ID if needed
2673
 *
2674
 * @return void
2675
 */
2676
function storeUsersShareKey(
2677
    string $object_name,
2678
    int $post_folder_is_personal,
2679
    int $post_folder_id,
2680
    int $post_object_id,
2681
    string $objectKey,
2682
    array $SETTINGS,
2683
    bool $onlyForUser = false,
2684
    bool $deleteAll = true,
2685
    array $objectKeyArray = []
2686
): void {
2687
    
2688
    $session = SessionManager::getSession();
2689
    loadClasses('DB');
2690
2691
    // Delete existing entries for this object
2692
    if ($deleteAll === true) {
2693
        DB::delete(
2694
            $object_name,
2695
            'object_id = %i',
2696
            $post_object_id
2697
        );
2698
    }
2699
    
2700
    if (
2701
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2702
    ) {
2703
        // Only create the sharekey for a user
2704
        $user = DB::queryFirstRow(
2705
            'SELECT public_key
2706
            FROM ' . prefixTable('users') . '
2707
            WHERE id = ' . (int) $session->get('user-id') . '
2708
            AND public_key != ""'
2709
        );
2710
2711
        if (empty($objectKey) === false) {
2712
            DB::insert(
2713
                $object_name,
2714
                [
2715
                    'object_id' => (int) $post_object_id,
2716
                    'user_id' => (int) $session->get('user-id'),
2717
                    'share_key' => encryptUserObjectKey(
2718
                        $objectKey,
2719
                        $user['public_key']
2720
                    ),
2721
                ]
2722
            );
2723
        } else if (count($objectKeyArray) > 0) {
2724
            foreach ($objectKeyArray as $object) {
2725
                DB::insert(
2726
                    $object_name,
2727
                    [
2728
                        'object_id' => (int) $object['objectId'],
2729
                        'user_id' => (int) $session->get('user-id'),
2730
                        'share_key' => encryptUserObjectKey(
2731
                            $object['objectKey'],
2732
                            $user['public_key']
2733
                        ),
2734
                    ]
2735
                );
2736
            }
2737
        }
2738
    } else {
2739
        // Create sharekey for each user
2740
        //DB::debugmode(true);
2741
        $users = DB::query(
2742
            'SELECT id, public_key
2743
            FROM ' . prefixTable('users') . '
2744
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2745
                'id IN ("' . TP_USER_ID . '","' . $session->get('user-id') . '") ' : 
2746
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '") ') . '
2747
            AND public_key != ""'
2748
        );
2749
        //DB::debugmode(false);
2750
        foreach ($users as $user) {
2751
            // Insert in DB the new object key for this item by user
2752
            if (count($objectKeyArray) === 0) {
2753
                DB::insert(
2754
                    $object_name,
2755
                    [
2756
                        'object_id' => $post_object_id,
2757
                        'user_id' => (int) $user['id'],
2758
                        'share_key' => encryptUserObjectKey(
2759
                            $objectKey,
2760
                            $user['public_key']
2761
                        ),
2762
                    ]
2763
                );
2764
            } else {
2765
                foreach ($objectKeyArray as $object) {
2766
                    DB::insert(
2767
                        $object_name,
2768
                        [
2769
                            'object_id' => (int) $object['objectId'],
2770
                            'user_id' => (int) $user['id'],
2771
                            'share_key' => encryptUserObjectKey(
2772
                                $object['objectKey'],
2773
                                $user['public_key']
2774
                            ),
2775
                        ]
2776
                    );
2777
                }
2778
            }
2779
        }
2780
    }
2781
}
2782
2783
/**
2784
 * Is this string base64 encoded?
2785
 *
2786
 * @param string $str Encoded string?
2787
 *
2788
 * @return bool
2789
 */
2790
function isBase64(string $str): bool
2791
{
2792
    $str = (string) trim($str);
2793
    if (! isset($str[0])) {
2794
        return false;
2795
    }
2796
2797
    $base64String = (string) base64_decode($str, true);
2798
    if ($base64String && base64_encode($base64String) === $str) {
2799
        return true;
2800
    }
2801
2802
    return false;
2803
}
2804
2805
/**
2806
 * Undocumented function
2807
 *
2808
 * @param string $field Parameter
2809
 *
2810
 * @return array|bool|resource|string
2811
 */
2812
function filterString(string $field)
2813
{
2814
    // Sanitize string
2815
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2816
    if (empty($field) === false) {
2817
        // Load AntiXSS
2818
        $antiXss = new AntiXSS();
2819
        // Return
2820
        return $antiXss->xss_clean($field);
2821
    }
2822
2823
    return false;
2824
}
2825
2826
/**
2827
 * CHeck if provided credentials are allowed on server
2828
 *
2829
 * @param string $login    User Login
2830
 * @param string $password User Pwd
2831
 * @param array  $SETTINGS Teampass settings
2832
 *
2833
 * @return bool
2834
 */
2835
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2836
{
2837
    // Build ldap configuration array
2838
    $config = [
2839
        // Mandatory Configuration Options
2840
        'hosts' => [$SETTINGS['ldap_hosts']],
2841
        'base_dn' => $SETTINGS['ldap_bdn'],
2842
        'username' => $SETTINGS['ldap_username'],
2843
        'password' => $SETTINGS['ldap_password'],
2844
2845
        // Optional Configuration Options
2846
        'port' => $SETTINGS['ldap_port'],
2847
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2848
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2849
        'version' => 3,
2850
        'timeout' => 5,
2851
        'follow_referrals' => false,
2852
2853
        // Custom LDAP Options
2854
        'options' => [
2855
            // See: http://php.net/ldap_set_option
2856
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2857
        ],
2858
    ];
2859
    
2860
    $connection = new Connection($config);
2861
    // Connect to LDAP
2862
    try {
2863
        $connection->connect();
2864
    } catch (\LdapRecord\Auth\BindException $e) {
2865
        $error = $e->getDetailedError();
2866
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2867
        return false;
2868
    }
2869
2870
    // Authenticate user
2871
    try {
2872
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2873
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2874
        } else {
2875
            $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);
2876
        }
2877
    } catch (\LdapRecord\Auth\BindException $e) {
2878
        $error = $e->getDetailedError();
2879
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2880
        return false;
2881
    }
2882
2883
    return true;
2884
}
2885
2886
/**
2887
 * Removes from DB all sharekeys of this user
2888
 *
2889
 * @param int $userId User's id
2890
 * @param array   $SETTINGS Teampass settings
2891
 *
2892
 * @return bool
2893
 */
2894
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2895
{
2896
    // Load class DB
2897
    loadClasses('DB');
2898
2899
    // Remove all item sharekeys items
2900
    // expect if personal item
2901
    DB::delete(
2902
        prefixTable('sharekeys_items'),
2903
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2904
        $userId
2905
    );
2906
    // Remove all item sharekeys files
2907
    DB::delete(
2908
        prefixTable('sharekeys_files'),
2909
        'user_id = %i AND object_id NOT IN (
2910
            SELECT f.id 
2911
            FROM ' . prefixTable('items') . ' AS i 
2912
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2913
            WHERE i.perso = 1
2914
        )',
2915
        $userId
2916
    );
2917
    // Remove all item sharekeys fields
2918
    DB::delete(
2919
        prefixTable('sharekeys_fields'),
2920
        'user_id = %i AND object_id NOT IN (
2921
            SELECT c.id 
2922
            FROM ' . prefixTable('items') . ' AS i 
2923
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2924
            WHERE i.perso = 1
2925
        )',
2926
        $userId
2927
    );
2928
    // Remove all item sharekeys logs
2929
    DB::delete(
2930
        prefixTable('sharekeys_logs'),
2931
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2932
        $userId
2933
    );
2934
    // Remove all item sharekeys suggestions
2935
    DB::delete(
2936
        prefixTable('sharekeys_suggestions'),
2937
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2938
        $userId
2939
    );
2940
    return false;
2941
}
2942
2943
/**
2944
 * Manage list of timezones   $SETTINGS Teampass settings
2945
 *
2946
 * @return array
2947
 */
2948
function timezone_list()
2949
{
2950
    static $timezones = null;
2951
    if ($timezones === null) {
2952
        $timezones = [];
2953
        $offsets = [];
2954
        $now = new DateTime('now', new DateTimeZone('UTC'));
2955
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2956
            $now->setTimezone(new DateTimeZone($timezone));
2957
            $offsets[] = $offset = $now->getOffset();
2958
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2959
        }
2960
2961
        array_multisort($offsets, $timezones);
2962
    }
2963
2964
    return $timezones;
2965
}
2966
2967
/**
2968
 * Provide timezone offset
2969
 *
2970
 * @param int $offset Timezone offset
2971
 *
2972
 * @return string
2973
 */
2974
function format_GMT_offset($offset): string
2975
{
2976
    $hours = intval($offset / 3600);
2977
    $minutes = abs(intval($offset % 3600 / 60));
2978
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2979
}
2980
2981
/**
2982
 * Provides timezone name
2983
 *
2984
 * @param string $name Timezone name
2985
 *
2986
 * @return string
2987
 */
2988
function format_timezone_name($name): string
2989
{
2990
    $name = str_replace('/', ', ', $name);
2991
    $name = str_replace('_', ' ', $name);
2992
2993
    return str_replace('St ', 'St. ', $name);
2994
}
2995
2996
/**
2997
 * Provides info if user should use MFA based on roles
2998
 *
2999
 * @param string $userRolesIds  User roles ids
3000
 * @param string $mfaRoles      Roles for which MFA is requested
3001
 *
3002
 * @return bool
3003
 */
3004
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3005
{
3006
    if (empty($mfaRoles) === true) {
3007
        return true;
3008
    }
3009
3010
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3011
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3012
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3013
        return true;
3014
    }
3015
3016
    return false;
3017
}
3018
3019
/**
3020
 * Permits to clean a string for export purpose
3021
 *
3022
 * @param string $text
3023
 * @param bool $emptyCheckOnly
3024
 * 
3025
 * @return string
3026
 */
3027
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3028
{
3029
    if (is_null($text) === true || empty($text) === true) {
3030
        return '';
3031
    }
3032
    // only expected to check if $text was empty
3033
    elseif ($emptyCheckOnly === true) {
3034
        return $text;
3035
    }
3036
3037
    return strip_tags(
3038
        cleanString(
3039
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3040
            true)
3041
        );
3042
}
3043
3044
/**
3045
 * Permits to check if user ID is valid
3046
 *
3047
 * @param integer $post_user_id
3048
 * @return bool
3049
 */
3050
function isUserIdValid($userId): bool
3051
{
3052
    if (is_null($userId) === false
3053
        && isset($userId) === true
3054
        && empty($userId) === false
3055
    ) {
3056
        return true;
3057
    }
3058
    return false;
3059
}
3060
3061
/**
3062
 * Check if a key exists and if its value equal the one expected
3063
 *
3064
 * @param string $key
3065
 * @param integer|string $value
3066
 * @param array $array
3067
 * 
3068
 * @return boolean
3069
 */
3070
function isKeyExistingAndEqual(
3071
    string $key,
3072
    /*PHP8 - integer|string*/$value,
3073
    array $array
3074
): bool
3075
{
3076
    if (isset($array[$key]) === true
3077
        && (is_int($value) === true ?
3078
            (int) $array[$key] === $value :
3079
            (string) $array[$key] === $value)
3080
    ) {
3081
        return true;
3082
    }
3083
    return false;
3084
}
3085
3086
/**
3087
 * Check if a variable is not set or equal to a value
3088
 *
3089
 * @param string|null $var
3090
 * @param integer|string $value
3091
 * 
3092
 * @return boolean
3093
 */
3094
function isKeyNotSetOrEqual(
3095
    /*PHP8 - string|null*/$var,
3096
    /*PHP8 - integer|string*/$value
3097
): bool
3098
{
3099
    if (isset($var) === false
3100
        || (is_int($value) === true ?
3101
            (int) $var === $value :
3102
            (string) $var === $value)
3103
    ) {
3104
        return true;
3105
    }
3106
    return false;
3107
}
3108
3109
/**
3110
 * Check if a key exists and if its value < to the one expected
3111
 *
3112
 * @param string $key
3113
 * @param integer $value
3114
 * @param array $array
3115
 * 
3116
 * @return boolean
3117
 */
3118
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3119
{
3120
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3121
        return true;
3122
    }
3123
    return false;
3124
}
3125
3126
/**
3127
 * Check if a key exists and if its value > to the one expected
3128
 *
3129
 * @param string $key
3130
 * @param integer $value
3131
 * @param array $array
3132
 * 
3133
 * @return boolean
3134
 */
3135
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3136
{
3137
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3138
        return true;
3139
    }
3140
    return false;
3141
}
3142
3143
/**
3144
 * Check if values in array are set
3145
 * Return true if all set
3146
 * Return false if one of them is not set
3147
 *
3148
 * @param array $arrayOfValues
3149
 * @return boolean
3150
 */
3151
function isSetArrayOfValues(array $arrayOfValues): bool
3152
{
3153
    foreach($arrayOfValues as $value) {
3154
        if (isset($value) === false) {
3155
            return false;
3156
        }
3157
    }
3158
    return true;
3159
}
3160
3161
/**
3162
 * Check if values in array are set
3163
 * Return true if all set
3164
 * Return false if one of them is not set
3165
 *
3166
 * @param array $arrayOfValues
3167
 * @param integer|string $value
3168
 * @return boolean
3169
 */
3170
function isArrayOfVarsEqualToValue(
3171
    array $arrayOfVars,
3172
    /*PHP8 - integer|string*/$value
3173
) : bool
3174
{
3175
    foreach($arrayOfVars as $variable) {
3176
        if ($variable !== $value) {
3177
            return false;
3178
        }
3179
    }
3180
    return true;
3181
}
3182
3183
/**
3184
 * Checks if at least one variable in array is equal to value
3185
 *
3186
 * @param array $arrayOfValues
3187
 * @param integer|string $value
3188
 * @return boolean
3189
 */
3190
function isOneVarOfArrayEqualToValue(
3191
    array $arrayOfVars,
3192
    /*PHP8 - integer|string*/$value
3193
) : bool
3194
{
3195
    foreach($arrayOfVars as $variable) {
3196
        if ($variable === $value) {
3197
            return true;
3198
        }
3199
    }
3200
    return false;
3201
}
3202
3203
/**
3204
 * Checks is value is null, not set OR empty
3205
 *
3206
 * @param string|int|null $value
3207
 * @return boolean
3208
 */
3209
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3210
{
3211
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3212
        return true;
3213
    }
3214
    return false;
3215
}
3216
3217
/**
3218
 * Checks if value is set and if empty is equal to passed boolean
3219
 *
3220
 * @param string|int $value
3221
 * @param boolean $boolean
3222
 * @return boolean
3223
 */
3224
function isValueSetEmpty($value, $boolean = true) : bool
3225
{
3226
    if (isset($value) === true && empty($value) === $boolean) {
3227
        return true;
3228
    }
3229
    return false;
3230
}
3231
3232
/**
3233
 * Ensure Complexity is translated
3234
 *
3235
 * @return void
3236
 */
3237
function defineComplexity() : void
3238
{
3239
    // Load user's language
3240
    $lang = new Language(); 
3241
    
3242
    if (defined('TP_PW_COMPLEXITY') === false) {
3243
        define(
3244
            'TP_PW_COMPLEXITY',
3245
            [
3246
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3247
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3248
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3249
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3250
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3251
            ]
3252
        );
3253
    }
3254
}
3255
3256
/**
3257
 * Uses Sanitizer to perform data sanitization
3258
 *
3259
 * @param array     $data
3260
 * @param array     $filters
3261
 * @return array|string
3262
 */
3263
function dataSanitizer(array $data, array $filters): array|string
3264
{
3265
    // Load Sanitizer library
3266
    $sanitizer = new Sanitizer($data, $filters);
3267
3268
    // Load AntiXSS
3269
    $antiXss = new AntiXSS();
3270
3271
    // Sanitize post and get variables
3272
    return $antiXss->xss_clean($sanitizer->sanitize());
3273
}
3274
3275
/**
3276
 * Permits to manage the cache tree for a user
3277
 *
3278
 * @param integer $user_id
3279
 * @param string $data
3280
 * @param array $SETTINGS
3281
 * @param string $field_update
3282
 * @return void
3283
 */
3284
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3285
{
3286
    // Load class DB
3287
    loadClasses('DB');
3288
3289
    // Exists ?
3290
    $userCacheId = DB::queryfirstrow(
3291
        'SELECT increment_id
3292
        FROM ' . prefixTable('cache_tree') . '
3293
        WHERE user_id = %i',
3294
        $user_id
3295
    );
3296
    
3297
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3298
        // insert in table
3299
        DB::insert(
3300
            prefixTable('cache_tree'),
3301
            array(
3302
                'data' => $data,
3303
                'timestamp' => time(),
3304
                'user_id' => $user_id,
3305
                'visible_folders' => '',
3306
            )
3307
        );
3308
    } else {
3309
        if (empty($field_update) === true) {
3310
            DB::update(
3311
                prefixTable('cache_tree'),
3312
                [
3313
                    'timestamp' => time(),
3314
                    'data' => $data,
3315
                ],
3316
                'increment_id = %i',
3317
                $userCacheId['increment_id']
3318
            );
3319
        /* USELESS
3320
        } else {
3321
            DB::update(
3322
                prefixTable('cache_tree'),
3323
                [
3324
                    $field_update => $data,
3325
                ],
3326
                'increment_id = %i',
3327
                $userCacheId['increment_id']
3328
            );*/
3329
        }
3330
    }
3331
}
3332
3333
/**
3334
 * Permits to calculate a %
3335
 *
3336
 * @param float $nombre
3337
 * @param float $total
3338
 * @param float $pourcentage
3339
 * @return float
3340
 */
3341
function pourcentage(float $nombre, float $total, float $pourcentage): float
3342
{ 
3343
    $resultat = ($nombre/$total) * $pourcentage;
3344
    return round($resultat);
3345
}
3346
3347
/**
3348
 * Load the folders list from the cache
3349
 *
3350
 * @param string $fieldName
3351
 * @param string $sessionName
3352
 * @param boolean $forceRefresh
3353
 * @return array
3354
 */
3355
function loadFoldersListByCache(
3356
    string $fieldName,
3357
    string $sessionName,
3358
    bool $forceRefresh = false
3359
): array
3360
{
3361
    // Case when refresh is EXPECTED / MANDATORY
3362
    if ($forceRefresh === true) {
3363
        return [
3364
            'state' => false,
3365
            'data' => [],
3366
        ];
3367
    }
3368
    
3369
    $session = SessionManager::getSession();
3370
3371
    // Get last folder update
3372
    $lastFolderChange = DB::queryfirstrow(
3373
        'SELECT valeur FROM ' . prefixTable('misc') . '
3374
        WHERE type = %s AND intitule = %s',
3375
        'timestamp',
3376
        'last_folder_change'
3377
    );
3378
    if (DB::count() === 0) {
3379
        $lastFolderChange['valeur'] = 0;
3380
    }
3381
3382
    // Case when an update in the tree has been done
3383
    // Refresh is then mandatory
3384
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3385
        return [
3386
            'state' => false,
3387
            'data' => [],
3388
        ];
3389
    }
3390
3391
    // Does this user has the tree structure in session?
3392
    // If yes then use it
3393
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3394
        return [
3395
            'state' => true,
3396
            'data' => json_encode($session->get('user-folders_list')),
3397
        ];
3398
    }
3399
3400
    // Does this user has a tree cache
3401
    $userCacheTree = DB::queryfirstrow(
3402
        'SELECT '.$fieldName.'
3403
        FROM ' . prefixTable('cache_tree') . '
3404
        WHERE user_id = %i',
3405
        $session->get('user-id')
3406
    );
3407
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3408
        SessionManager::addRemoveFromSessionAssociativeArray(
3409
            'user-folders_list',
3410
            [$userCacheTree[$fieldName]],
3411
            'add'
3412
        );
3413
        return [
3414
            'state' => true,
3415
            'data' => $userCacheTree[$fieldName],
3416
        ];
3417
    }
3418
3419
    return [
3420
        'state' => false,
3421
        'data' => [],
3422
    ];
3423
}
3424
3425
3426
/**
3427
 * Permits to refresh the categories of folders
3428
 *
3429
 * @param array $folderIds
3430
 * @return void
3431
 */
3432
function handleFoldersCategories(
3433
    array $folderIds
3434
)
3435
{
3436
    // Load class DB
3437
    loadClasses('DB');
3438
3439
    $arr_data = array();
3440
3441
    // force full list of folders
3442
    if (count($folderIds) === 0) {
3443
        $folderIds = DB::queryFirstColumn(
3444
            'SELECT id
3445
            FROM ' . prefixTable('nested_tree') . '
3446
            WHERE personal_folder=%i',
3447
            0
3448
        );
3449
    }
3450
3451
    // Get complexity
3452
    defineComplexity();
3453
3454
    // update
3455
    foreach ($folderIds as $folder) {
3456
        // Do we have Categories
3457
        // get list of associated Categories
3458
        $arrCatList = array();
3459
        $rows_tmp = DB::query(
3460
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3461
            f.id_category AS category_id
3462
            FROM ' . prefixTable('categories_folders') . ' AS f
3463
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3464
            WHERE id_folder=%i',
3465
            $folder
3466
        );
3467
        if (DB::count() > 0) {
3468
            foreach ($rows_tmp as $row) {
3469
                $arrCatList[$row['id']] = array(
3470
                    'id' => $row['id'],
3471
                    'title' => $row['title'],
3472
                    'level' => $row['level'],
3473
                    'type' => $row['type'],
3474
                    'masked' => $row['masked'],
3475
                    'order' => $row['order'],
3476
                    'encrypted_data' => $row['encrypted_data'],
3477
                    'role_visibility' => $row['role_visibility'],
3478
                    'is_mandatory' => $row['is_mandatory'],
3479
                    'category_id' => $row['category_id'],
3480
                );
3481
            }
3482
        }
3483
        $arr_data['categories'] = $arrCatList;
3484
3485
        // Now get complexity
3486
        $valTemp = '';
3487
        $data = DB::queryFirstRow(
3488
            'SELECT valeur
3489
            FROM ' . prefixTable('misc') . '
3490
            WHERE type = %s AND intitule=%i',
3491
            'complex',
3492
            $folder
3493
        );
3494
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3495
            $valTemp = array(
3496
                'value' => $data['valeur'],
3497
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3498
            );
3499
        }
3500
        $arr_data['complexity'] = $valTemp;
3501
3502
        // Now get Roles
3503
        $valTemp = '';
3504
        $rows_tmp = DB::query(
3505
            'SELECT t.title
3506
            FROM ' . prefixTable('roles_values') . ' as v
3507
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3508
            WHERE v.folder_id = %i
3509
            GROUP BY title',
3510
            $folder
3511
        );
3512
        foreach ($rows_tmp as $record) {
3513
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3514
        }
3515
        $arr_data['visibilityRoles'] = $valTemp;
3516
3517
        // now save in DB
3518
        DB::update(
3519
            prefixTable('nested_tree'),
3520
            array(
3521
                'categories' => json_encode($arr_data),
3522
            ),
3523
            'id = %i',
3524
            $folder
3525
        );
3526
    }
3527
}
3528
3529
/**
3530
 * List all users that have specific roles
3531
 *
3532
 * @param array $roles
3533
 * @return array
3534
 */
3535
function getUsersWithRoles(
3536
    array $roles
3537
): array
3538
{
3539
    $session = SessionManager::getSession();
3540
    $arrUsers = array();
3541
3542
    foreach ($roles as $role) {
3543
        // loop on users and check if user has this role
3544
        $rows = DB::query(
3545
            'SELECT id, fonction_id
3546
            FROM ' . prefixTable('users') . '
3547
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3548
            $session->get('user-id')
3549
        );
3550
        foreach ($rows as $user) {
3551
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3552
            if (in_array($role, $userRoles, true) === true) {
3553
                array_push($arrUsers, $user['id']);
3554
            }
3555
        }
3556
    }
3557
3558
    return $arrUsers;
3559
}
3560
3561
3562
/**
3563
 * Get all users informations
3564
 *
3565
 * @param integer $userId
3566
 * @return array
3567
 */
3568
function getFullUserInfos(
3569
    int $userId
3570
): array
3571
{
3572
    if (empty($userId) === true) {
3573
        return array();
3574
    }
3575
3576
    $val = DB::queryfirstrow(
3577
        'SELECT *
3578
        FROM ' . prefixTable('users') . '
3579
        WHERE id = %i',
3580
        $userId
3581
    );
3582
3583
    return $val;
3584
}
3585
3586
/**
3587
 * Is required an upgrade
3588
 *
3589
 * @return boolean
3590
 */
3591
function upgradeRequired(): bool
3592
{
3593
    // Get settings.php
3594
    include_once __DIR__. '/../includes/config/settings.php';
3595
3596
    // Get timestamp in DB
3597
    $val = DB::queryfirstrow(
3598
        'SELECT valeur
3599
        FROM ' . prefixTable('misc') . '
3600
        WHERE type = %s AND intitule = %s',
3601
        'admin',
3602
        'upgrade_timestamp'
3603
    );
3604
    
3605
    // if not exists then error
3606
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3607
3608
    // if empty or too old then error
3609
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3610
        return true;
3611
    }
3612
3613
    return false;
3614
}
3615
3616
/**
3617
 * Permits to change the user keys on his demand
3618
 *
3619
 * @param integer $userId
3620
 * @param string $passwordClear
3621
 * @param integer $nbItemsToTreat
3622
 * @param string $encryptionKey
3623
 * @param boolean $deleteExistingKeys
3624
 * @param boolean $sendEmailToUser
3625
 * @param boolean $encryptWithUserPassword
3626
 * @param boolean $generate_user_new_password
3627
 * @param string $emailBody
3628
 * @param boolean $user_self_change
3629
 * @param string $recovery_public_key
3630
 * @param string $recovery_private_key
3631
 * @return string
3632
 */
3633
function handleUserKeys(
3634
    int $userId,
3635
    string $passwordClear,
3636
    int $nbItemsToTreat,
3637
    string $encryptionKey = '',
3638
    bool $deleteExistingKeys = false,
3639
    bool $sendEmailToUser = true,
3640
    bool $encryptWithUserPassword = false,
3641
    bool $generate_user_new_password = false,
3642
    string $emailBody = '',
3643
    bool $user_self_change = false,
3644
    string $recovery_public_key = '',
3645
    string $recovery_private_key = ''
3646
): string
3647
{
3648
    $session = SessionManager::getSession();
3649
    $lang = new Language(); 
3650
3651
    // prepapre background tasks for item keys generation        
3652
    $userTP = DB::queryFirstRow(
3653
        'SELECT pw, public_key, private_key
3654
        FROM ' . prefixTable('users') . '
3655
        WHERE id = %i',
3656
        TP_USER_ID
3657
    );
3658
    if (DB::count() === 0) {
3659
        return prepareExchangedData(
3660
            array(
3661
                'error' => true,
3662
                'message' => 'User not exists',
3663
            ),
3664
            'encode'
3665
        );
3666
    }
3667
3668
    // Do we need to generate new user password
3669
    if ($generate_user_new_password === true) {
3670
        // Generate a new password
3671
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3672
    }
3673
3674
    // Hash the password
3675
    $pwdlib = new PasswordLib();
3676
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3677
    if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3678
        return prepareExchangedData(
3679
            array(
3680
                'error' => true,
3681
                'message' => $lang->get('pw_hash_not_correct'),
3682
            ),
3683
            'encode'
3684
        );
3685
    }
3686
3687
    // Generate new keys
3688
    if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3689
        $userKeys = [
3690
            'public_key' => $recovery_public_key,
3691
            'private_key_clear' => $recovery_private_key,
3692
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3693
        ];
3694
    } else {
3695
        $userKeys = generateUserKeys($passwordClear);
3696
    }
3697
3698
    // Save in DB
3699
    DB::update(
3700
        prefixTable('users'),
3701
        array(
3702
            'pw' => $hashedPassword,
3703
            'public_key' => $userKeys['public_key'],
3704
            'private_key' => $userKeys['private_key'],
3705
        ),
3706
        'id=%i',
3707
        $userId
3708
    );
3709
3710
    // update session too
3711
    if ($userId === $session->get('user-id')) {
3712
        $session->set('user-private_key', $userKeys['private_key_clear']);
3713
        $session->set('user-public_key', $userKeys['public_key']);
3714
    }
3715
3716
    // Manage empty encryption key
3717
    // Let's take the user's password if asked and if no encryption key provided
3718
    $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3719
3720
    // Create process
3721
    DB::insert(
3722
        prefixTable('processes'),
3723
        array(
3724
            'created_at' => time(),
3725
            'process_type' => 'create_user_keys',
3726
            'arguments' => json_encode([
3727
                'new_user_id' => (int) $userId,
3728
                'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3729
                'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3730
                'owner_id' => (int) TP_USER_ID,
3731
                'creator_pwd' => $userTP['pw'],
3732
                'send_email' => $sendEmailToUser === true ? 1 : 0,
3733
                'otp_provided_new_value' => 1,
3734
                'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3735
                'user_self_change' => $user_self_change === true ? 1 : 0,
3736
            ]),
3737
            'updated_at' => '',
3738
            'finished_at' => '',
3739
            'output' => '',
3740
        )
3741
    );
3742
    $processId = DB::insertId();
3743
3744
    // Delete existing keys
3745
    if ($deleteExistingKeys === true) {
3746
        deleteUserObjetsKeys(
3747
            (int) $userId,
3748
        );
3749
    }
3750
3751
    // Create tasks
3752
    createUserTasks($processId, $nbItemsToTreat);
3753
3754
    // update user's new status
3755
    DB::update(
3756
        prefixTable('users'),
3757
        [
3758
            'is_ready_for_usage' => 0,
3759
            'otp_provided' => 1,
3760
            'ongoing_process_id' => $processId,
3761
            'special' => 'generate-keys',
3762
        ],
3763
        'id=%i',
3764
        $userId
3765
    );
3766
3767
    return prepareExchangedData(
3768
        array(
3769
            'error' => false,
3770
            'message' => '',
3771
        ),
3772
        'encode'
3773
    );
3774
}
3775
3776
/**
3777
 * Permits to generate a new password for a user
3778
 *
3779
 * @param integer $processId
3780
 * @param integer $nbItemsToTreat
3781
 * @return void
3782
 
3783
 */
3784
function createUserTasks($processId, $nbItemsToTreat): void
3785
{
3786
    DB::insert(
3787
        prefixTable('processes_tasks'),
3788
        array(
3789
            'process_id' => $processId,
3790
            'created_at' => time(),
3791
            'task' => json_encode([
3792
                'step' => 'step0',
3793
                'index' => 0,
3794
                'nb' => $nbItemsToTreat,
3795
            ]),
3796
        )
3797
    );
3798
3799
    DB::insert(
3800
        prefixTable('processes_tasks'),
3801
        array(
3802
            'process_id' => $processId,
3803
            'created_at' => time(),
3804
            'task' => json_encode([
3805
                'step' => 'step10',
3806
                'index' => 0,
3807
                'nb' => $nbItemsToTreat,
3808
            ]),
3809
        )
3810
    );
3811
3812
    DB::insert(
3813
        prefixTable('processes_tasks'),
3814
        array(
3815
            'process_id' => $processId,
3816
            'created_at' => time(),
3817
            'task' => json_encode([
3818
                'step' => 'step20',
3819
                'index' => 0,
3820
                'nb' => $nbItemsToTreat,
3821
            ]),
3822
        )
3823
    );
3824
3825
    DB::insert(
3826
        prefixTable('processes_tasks'),
3827
        array(
3828
            'process_id' => $processId,
3829
            'created_at' => time(),
3830
            'task' => json_encode([
3831
                'step' => 'step30',
3832
                'index' => 0,
3833
                'nb' => $nbItemsToTreat,
3834
            ]),
3835
        )
3836
    );
3837
3838
    DB::insert(
3839
        prefixTable('processes_tasks'),
3840
        array(
3841
            'process_id' => $processId,
3842
            'created_at' => time(),
3843
            'task' => json_encode([
3844
                'step' => 'step40',
3845
                'index' => 0,
3846
                'nb' => $nbItemsToTreat,
3847
            ]),
3848
        )
3849
    );
3850
3851
    DB::insert(
3852
        prefixTable('processes_tasks'),
3853
        array(
3854
            'process_id' => $processId,
3855
            'created_at' => time(),
3856
            'task' => json_encode([
3857
                'step' => 'step50',
3858
                'index' => 0,
3859
                'nb' => $nbItemsToTreat,
3860
            ]),
3861
        )
3862
    );
3863
3864
    DB::insert(
3865
        prefixTable('processes_tasks'),
3866
        array(
3867
            'process_id' => $processId,
3868
            'created_at' => time(),
3869
            'task' => json_encode([
3870
                'step' => 'step60',
3871
                'index' => 0,
3872
                'nb' => $nbItemsToTreat,
3873
            ]),
3874
        )
3875
    );
3876
}
3877
3878
/**
3879
 * Permeits to check the consistency of date versus columns definition
3880
 *
3881
 * @param string $table
3882
 * @param array $dataFields
3883
 * @return array
3884
 */
3885
function validateDataFields(
3886
    string $table,
3887
    array $dataFields
3888
): array
3889
{
3890
    // Get table structure
3891
    $result = DB::query(
3892
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3893
        DB_NAME,
3894
        $table
3895
    );
3896
3897
    foreach ($result as $row) {
3898
        $field = $row['COLUMN_NAME'];
3899
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3900
3901
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3902
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3903
                return [
3904
                    'state' => false,
3905
                    'field' => $field,
3906
                    'maxLength' => $maxLength,
3907
                    'currentLength' => strlen((string) $dataFields[$field]),
3908
                ];
3909
            }
3910
        }
3911
    }
3912
    
3913
    return [
3914
        'state' => true,
3915
        'message' => '',
3916
    ];
3917
}
3918
3919
/**
3920
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3921
 *
3922
 * @param string $string
3923
 * @return string
3924
 */
3925
function filterVarBack(string $string): string
3926
{
3927
    $arr = [
3928
        '&#060;' => '<',
3929
        '&#062;' => '>',
3930
        '&#034;' => '"',
3931
        '&#039;' => "'",
3932
        '&#038;' => '&',
3933
    ];
3934
3935
    foreach ($arr as $key => $value) {
3936
        $string = str_replace($key, $value, $string);
3937
    }
3938
3939
    return $string;
3940
}
3941
3942
/**
3943
 * 
3944
 */
3945
function storeTask(
3946
    string $taskName,
3947
    int $user_id,
3948
    int $is_personal_folder,
3949
    int $folder_destination_id,
3950
    int $item_id,
3951
    string $object_keys
3952
)
3953
{
3954
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3955
        // Create process
3956
        DB::insert(
3957
            prefixTable('processes'),
3958
            array(
3959
                'created_at' => time(),
3960
                'process_type' => $taskName,
3961
                'arguments' => json_encode([
3962
                    'item_id' => $item_id,
3963
                    'object_key' => $object_keys,
3964
                ]),
3965
                'updated_at' => '',
3966
                'finished_at' => '',
3967
                'output' => '',
3968
                'item_id' => $item_id,
3969
            )
3970
        );
3971
        $processId = DB::insertId();
3972
3973
        // Create tasks
3974
        // 1- Create password sharekeys for users of this new ITEM
3975
        DB::insert(
3976
            prefixTable('processes_tasks'),
3977
            array(
3978
                'process_id' => $processId,
3979
                'created_at' => time(),
3980
                'task' => json_encode([
3981
                    'step' => 'create_users_pwd_key',
3982
                    'index' => 0,
3983
                ]),
3984
            )
3985
        );
3986
3987
        // 2- Create fields sharekeys for users of this new ITEM
3988
        DB::insert(
3989
            prefixTable('processes_tasks'),
3990
            array(
3991
                'process_id' => $processId,
3992
                'created_at' => time(),
3993
                'task' => json_encode([
3994
                    'step' => 'create_users_fields_key',
3995
                    'index' => 0,
3996
                ]),
3997
            )
3998
        );
3999
4000
        // 3- Create files sharekeys for users of this new ITEM
4001
        DB::insert(
4002
            prefixTable('processes_tasks'),
4003
            array(
4004
                'process_id' => $processId,
4005
                'created_at' => time(),
4006
                'task' => json_encode([
4007
                    'step' => 'create_users_files_key',
4008
                    'index' => 0,
4009
                ]),
4010
            )
4011
        );
4012
    }
4013
}
4014
4015
/**
4016
 * Return PHP binary path
4017
 *
4018
 * @return string
4019
 */
4020
function getPHPBinary(): string
4021
{
4022
    // Get PHP binary path
4023
    $phpBinaryFinder = new PhpExecutableFinder();
4024
    $phpBinaryPath = $phpBinaryFinder->find();
4025
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4026
}
4027
4028
4029
4030
/**
4031
 * Delete unnecessary keys for personal items
4032
 *
4033
 * @param boolean $allUsers
4034
 * @param integer $user_id
4035
 * @return void
4036
 */
4037
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4038
{
4039
    if ($allUsers === true) {
4040
        // Load class DB
4041
        if (class_exists('DB') === false) {
4042
            loadClasses('DB');
4043
        }
4044
4045
        $users = DB::query(
4046
            'SELECT id
4047
            FROM ' . prefixTable('users') . '
4048
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4049
            ORDER BY login ASC'
4050
        );
4051
        foreach ($users as $user) {
4052
            purgeUnnecessaryKeysForUser((int) $user['id']);
4053
        }
4054
    } else {
4055
        purgeUnnecessaryKeysForUser((int) $user_id);
4056
    }
4057
}
4058
4059
/**
4060
 * Delete unnecessary keys for personal items
4061
 *
4062
 * @param integer $user_id
4063
 * @return void
4064
 */
4065
function purgeUnnecessaryKeysForUser(int $user_id=0)
4066
{
4067
    if ($user_id === 0) {
4068
        return;
4069
    }
4070
4071
    // Load class DB
4072
    loadClasses('DB');
4073
4074
    $personalItems = DB::queryFirstColumn(
4075
        'SELECT id
4076
        FROM ' . prefixTable('items') . ' AS i
4077
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4078
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4079
        $user_id
4080
    );
4081
    if (count($personalItems) > 0) {
4082
        // Item keys
4083
        DB::delete(
4084
            prefixTable('sharekeys_items'),
4085
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4086
            $personalItems,
4087
            $user_id
4088
        );
4089
        // Files keys
4090
        DB::delete(
4091
            prefixTable('sharekeys_files'),
4092
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4093
            $personalItems,
4094
            $user_id
4095
        );
4096
        // Fields keys
4097
        DB::delete(
4098
            prefixTable('sharekeys_fields'),
4099
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4100
            $personalItems,
4101
            $user_id
4102
        );
4103
        // Logs keys
4104
        DB::delete(
4105
            prefixTable('sharekeys_logs'),
4106
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4107
            $personalItems,
4108
            $user_id
4109
        );
4110
    }
4111
}
4112
4113
/**
4114
 * Generate recovery keys file
4115
 *
4116
 * @param integer $userId
4117
 * @param array $SETTINGS
4118
 * @return string
4119
 */
4120
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4121
{
4122
    $session = SessionManager::getSession();
4123
    // Check if user exists
4124
    $userInfo = DB::queryFirstRow(
4125
        'SELECT pw, public_key, private_key, login, name
4126
        FROM ' . prefixTable('users') . '
4127
        WHERE id = %i',
4128
        $userId
4129
    );
4130
4131
    if (DB::count() > 0) {
4132
        $now = (int) time();
4133
4134
        // Prepare file content
4135
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4136
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4137
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4138
            "Public Key:\n".$userInfo['public_key']."\n\n".
4139
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4140
4141
        // Update user's keys_recovery_time
4142
        DB::update(
4143
            prefixTable('users'),
4144
            [
4145
                'keys_recovery_time' => $now,
4146
            ],
4147
            'id=%i',
4148
            $userId
4149
        );
4150
        $session->set('user-keys_recovery_time', $now);
4151
4152
        //Log into DB the user's disconnection
4153
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4154
        
4155
        // Return data
4156
        return prepareExchangedData(
4157
            array(
4158
                'error' => false,
4159
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4160
                'timestamp' => $now,
4161
                'content' => base64_encode($export_value),
4162
                'login' => $userInfo['login'],
4163
            ),
4164
            'encode'
4165
        );
4166
    }
4167
4168
    return prepareExchangedData(
4169
        array(
4170
            'error' => true,
4171
            'datetime' => '',
4172
        ),
4173
        'encode'
4174
    );
4175
}
4176
4177
/**
4178
 * Permits to load expected classes
4179
 *
4180
 * @param string $className
4181
 * @return void
4182
 */
4183
function loadClasses(string $className = ''): void
4184
{
4185
    require_once __DIR__. '/../includes/config/include.php';
4186
    require_once __DIR__. '/../includes/config/settings.php';
4187
    require_once __DIR__.'/../vendor/autoload.php';
4188
4189
    if (defined('DB_PASSWD_CLEAR') === false) {
4190
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4191
    }
4192
4193
    if (empty($className) === false) {
4194
        // Load class DB
4195
        if ((string) $className === 'DB') {
4196
            //Connect to DB
4197
            DB::$host = DB_HOST;
4198
            DB::$user = DB_USER;
4199
            DB::$password = DB_PASSWD_CLEAR;
4200
            DB::$dbName = DB_NAME;
4201
            DB::$port = DB_PORT;
4202
            DB::$encoding = DB_ENCODING;
4203
            DB::$ssl = DB_SSL;
4204
            DB::$connect_options = DB_CONNECT_OPTIONS;
4205
        }
4206
    }
4207
4208
}
4209
4210
/**
4211
 * Returns the page the user is visiting.
4212
 *
4213
 * @return string The page name
4214
 */
4215
function getCurrectPage($SETTINGS)
4216
{
4217
    
4218
    $request = Request::createFromGlobals();
4219
4220
    // Parse the url
4221
    parse_str(
4222
        substr(
4223
            (string) $request->server->get('REQUEST_URI'),
4224
            strpos((string) $request->server->get('REQUEST_URI'), '?') + 1
4225
        ),
4226
        $result
4227
    );
4228
4229
    return $result['page'];
4230
}
4231
4232
/**
4233
 * Permits to return value if set
4234
 *
4235
 * @param string|int $value
4236
 * @param string|int|null $retFalse
4237
 * @param string|int $retTrue
4238
 * @return mixed
4239
 */
4240
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4241
{
4242
4243
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4244
}
4245