Passed
Push — master ( 3ca7fe...e7fa53 )
by Nils
04:04
created

handleFoldersCategories()   C

Complexity

Conditions 10
Paths 52

Size

Total Lines 109
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 68
c 1
b 0
f 0
nc 52
nop 1
dl 0
loc 109
rs 6.8315

How to fix   Long Method    Complexity   

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
 *
15
 * @file      main.functions.php
16
 * ---
17
 *
18
 * @author    Nils Laumaillé ([email protected])
19
 *
20
 * @copyright 2009-2022 Teampass.net
21
 *
22
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
23
 * ---
24
 *
25
 * @see       https://www.teampass.net
26
 */
27
28
use LdapRecord\Connection;
29
use \ForceUTF8\Encoding;
0 ignored issues
show
Bug introduced by
The type \ForceUTF8\Encoding was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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