Passed
Branch wip_sessions (830972)
by Nils
07:55
created

installHandleFoldersCategories()   C

Complexity

Conditions 12
Paths 27

Size

Total Lines 101
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 63
nc 27
nop 2
dl 0
loc 101
rs 6.3806
c 1
b 0
f 0

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
 * Teampass - a collaborative passwords manager.
4
 * ---
5
 * This library is distributed in the hope that it will be useful,
6
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
7
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8
 * ---
9
 * @project   Teampass
10
 * @file      upgrade_operations.php
11
 * ---
12
 * @author    Nils Laumaillé ([email protected])
13
 * @copyright 2009-2023 Teampass.net
14
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
15
 * ---
16
 * @see       https://www.teampass.net
17
 */
18
19
use EZimuel\PHPSecureSession;
20
use TeampassClasses\SuperGlobal\SuperGlobal;
21
use TeampassClasses\Language\Language;
22
use PasswordLib\PasswordLib;
23
24
// Load functions
25
require_once __DIR__.'/../sources/main.functions.php';
26
27
// init
28
loadClasses('DB');
29
$superGlobal = new SuperGlobal();
30
$lang = new Language(); 
31
session_name('teampass_session');
32
session_start();
33
error_reporting(E_ERROR | E_PARSE);
34
set_time_limit(600);
35
$_SESSION['CPM'] = 1;
36
37
//include librairies
38
require_once __DIR__.'/../includes/language/english.php';
39
require_once __DIR__.'/../includes/config/include.php';
40
require_once __DIR__.'/../includes/config/settings.php';
41
require_once __DIR__.'/tp.functions.php';
42
require_once __DIR__.'/libs/aesctr.php';
43
require_once __DIR__.'/../includes/config/tp.config.php';
44
45
// Prepare POST variables
46
$post_nb = filter_input(INPUT_POST, 'nb', FILTER_SANITIZE_NUMBER_INT);
47
$post_start = filter_input(INPUT_POST, 'start', FILTER_SANITIZE_NUMBER_INT);
48
49
// Load libraries
50
$superGlobal = new SuperGlobal();
51
$lang = new Language(); 
52
53
// Some init
54
$_SESSION['settings']['loaded'] = '';
55
$finish = true;
56
57
// Get the encrypted password
58
define('DB_PASSWD_CLEAR', defuse_return_decrypted(DB_PASSWD));
59
60
// DataBase
61
// Test DB connexion
62
$pass = DB_PASSWD_CLEAR;
63
$server = DB_HOST;
64
$pre = DB_PREFIX;
65
$database = DB_NAME;
66
$port = DB_PORT;
67
$user = DB_USER;
68
69
if (mysqli_connect(
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with socket. ( Ignorable by Annotation )

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

69
if (/** @scrutinizer ignore-call */ mysqli_connect(

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
70
    $server,
71
    $user,
72
    $pass,
73
    $database,
74
    $port
0 ignored issues
show
Bug introduced by
$port of type string is incompatible with the type integer|null expected by parameter $port of mysqli_connect(). ( Ignorable by Annotation )

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

74
    /** @scrutinizer ignore-type */ $port
Loading history...
75
)) {
76
    $db_link = mysqli_connect(
77
        $server,
78
        $user,
79
        $pass,
80
        $database,
81
        $port
82
    );
83
} else {
84
    $res = 'Impossible to get connected to server. Error is: ' . addslashes(mysqli_connect_error());
85
    echo '[{"finish":"1", "msg":"", "error":"Impossible to get connected to server. Error is: ' . addslashes(mysqli_connect_error()) . '!"}]';
86
    mysqli_close($db_link);
87
    exit();
88
}
89
90
// Get POST with operation to perform
91
$post_operation = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
92
93
if (isset($post_operation) === true && empty($post_operation) === false && strpos($post_operation, 'step') === false) {
94
    if ($post_operation === '20230604_1') {
95
        // ---->
96
        // OPERATION - 20230604_1 - generate key for item_key
97
98
        // Get items to treat
99
        $rows = mysqli_query(
100
            $db_link,
101
            "SELECT id FROM ".$pre."items
102
            WHERE item_key = '-1'
103
            ORDER BY id
104
            LIMIT ".$post_nb.";"
105
        );
106
        // Handle error on query
107
        if (!$rows) {
108
            echo '[{"finish":"1" , "error":"'.mysqli_error($db_link).'"}]';
109
            exit();
110
        }
111
112
        // Get total of items to treat
113
        $total = mysqli_num_rows($rows);
0 ignored issues
show
Bug introduced by
It seems like $rows can also be of type true; however, parameter $result of mysqli_num_rows() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

113
        $total = mysqli_num_rows(/** @scrutinizer ignore-type */ $rows);
Loading history...
114
115
        // Loop on items and update for requested ones
116
        if ((int) $total > 0) {
117
            while ($row = mysqli_fetch_array($rows, MYSQLI_ASSOC)) {
0 ignored issues
show
Bug introduced by
It seems like $rows can also be of type true; however, parameter $result of mysqli_fetch_array() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

117
            while ($row = mysqli_fetch_array(/** @scrutinizer ignore-type */ $rows, MYSQLI_ASSOC)) {
Loading history...
118
                // Gererate a key and update
119
                mysqli_query(
120
                    $db_link,
121
                    "UPDATE `".$pre."items`
122
                    SET `item_key` = '".uniqidReal(50)."'
123
                    WHERE `id` = ".$row['id'].";"
124
                );
125
                if (mysqli_error($db_link)) {
126
                    echo '[{"finish":"1", "next":"", "error":"MySQL Error! '.addslashes(mysqli_error($db_link)).'"}]';
127
                    exit();
128
                }
129
            }
130
        }
131
132
        // Manage end of operation
133
        if ($total === 0) {
134
            $finish = 1;
135
        } else {
136
            $finish = 0;
137
        }
138
        // ----<
139
    } elseif ($post_operation === 'populateItemsTable_CreatedAt') {
140
        $finish = populateItemsTable_CreatedAt($pre, $post_nb);
141
    } elseif ($post_operation === 'populateItemsTable_UpdatedAt') {
142
        $finish = populateItemsTable_UpdatedAt($pre);
143
    } elseif ($post_operation === 'populateItemsTable_DeletedAt') {
144
        $finish = populateItemsTable_DeletedAt($pre);
145
    } elseif ($post_operation === '20231017_1') {
146
            // ---->
147
            // OPERATION - 20231017_1 - remove all existing keys
148
            // if item is personal and user is not owner
149
150
            installPurgeUnnecessaryKeys(true, 0,$pre);
151
    }
152
    // Return back
153
    echo '[{"finish":"'.$finish.'" , "next":"", "error":"", "total":"'.$total.'"}]';
0 ignored issues
show
Bug introduced by
Are you sure $finish of type integer|true can be used in concatenation? ( Ignorable by Annotation )

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

153
    echo '[{"finish":"'./** @scrutinizer ignore-type */ $finish.'" , "next":"", "error":"", "total":"'.$total.'"}]';
Loading history...
154
}
155
156
157
function populateItemsTable_CreatedAt($pre, $post_nb)
158
{
159
    global $db_link;
160
    // loop on items - created_at
161
    $items = mysqli_query(
162
        $db_link,
163
        "select i.id as id, ls.date as datetime
164
        from `" . $pre . "items` as i
165
        inner join `" . $pre . "log_items` as ls on ls.id_item = i.id
166
        WHERE ls.action = 'at_creation' AND i.created_at IS NULL
167
        LIMIT " . $post_nb.";"
168
    );
169
    while ($item = mysqli_fetch_assoc($items)) {
0 ignored issues
show
Bug introduced by
It seems like $items can also be of type true; however, parameter $result of mysqli_fetch_assoc() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

169
    while ($item = mysqli_fetch_assoc(/** @scrutinizer ignore-type */ $items)) {
Loading history...
170
        if (empty((string) $item['datetime']) === false && is_null($item['datetime']) === false) {
171
            // update created_at field
172
            mysqli_query(
173
                $db_link,
174
                "UPDATE `" . $pre . "items` SET created_at = '".$item['datetime']."' WHERE id = ".$item['id']
175
            );
176
        }
177
    }
178
179
    // Is it finished?
180
    $remainingItems = mysqli_num_rows(
181
        mysqli_query(
0 ignored issues
show
Bug introduced by
It seems like mysqli_query($db_link, '...RE created_at IS NULL') can also be of type true; however, parameter $result of mysqli_num_rows() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

181
        /** @scrutinizer ignore-type */ mysqli_query(
Loading history...
182
            $db_link,
183
            "SELECT * FROM `" . $pre . "items` WHERE created_at IS NULL"
184
        )
185
    );
186
    return $remainingItems > 0 ? 0 : 1;
187
}
188
189
function populateItemsTable_UpdatedAt($pre)
190
{
191
    global $db_link;
192
    // loop on items - updated_at
193
    $items = mysqli_query(
194
        $db_link,
195
        "select i.id as id, (select date from " . $pre . "log_items where action = 'at_modification' and id_item=id order by date DESC limit 1) as datetime 
196
        from `" . $pre . "items` as i;"
197
    );
198
    while ($item = mysqli_fetch_assoc($items)) {
0 ignored issues
show
Bug introduced by
It seems like $items can also be of type true; however, parameter $result of mysqli_fetch_assoc() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

198
    while ($item = mysqli_fetch_assoc(/** @scrutinizer ignore-type */ $items)) {
Loading history...
199
        if (is_null($item['datetime']) === false) {
200
            // update updated_at field
201
            mysqli_query(
202
                $db_link,
203
                "UPDATE `" . $pre . "items` SET updated_at = '".$item['datetime']."' WHERE id = ".$item['id']
204
            );
205
        }
206
    }
207
208
    return 1;
209
}
210
211
function populateItemsTable_DeletedAt($pre)
212
{
213
    global $db_link;
214
    // loop on items - deleted_at
215
    $items = mysqli_query(
216
        $db_link,
217
        "select i.id as id, (select date from " . $pre . "log_items where action = 'at_deleted' and id_item=id order by date DESC limit 1) as datetime
218
        from `" . $pre . "items` as i;"
219
    );
220
    while ($item = mysqli_fetch_assoc($items)) {
0 ignored issues
show
Bug introduced by
It seems like $items can also be of type true; however, parameter $result of mysqli_fetch_assoc() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

220
    while ($item = mysqli_fetch_assoc(/** @scrutinizer ignore-type */ $items)) {
Loading history...
221
        if (is_null($item['datetime']) === false) {
222
            // update updated_at field
223
            mysqli_query(
224
                $db_link,
225
                "UPDATE `" . $pre . "items` SET deleted_at = '".$item['datetime']."' WHERE id = ".$item['id']
226
            );
227
        }
228
    }
229
230
    return 1;
231
}
232
233
234
/**
235
 * Delete unnecessary keys for personal items
236
 *
237
 * @param boolean $allUsers
238
 * @param integer $user_id
239
 * @return void
240
 */
241
function installPurgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0, string $pre)
242
{
243
    global $db_link;
244
    if ($allUsers === true) {
245
        $users = mysqli_query(
246
            $db_link,
247
            'SELECT id
248
            FROM ' . $pre . 'users
249
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
250
            ORDER BY login ASC'
251
        );
252
        while ($user = mysqli_fetch_assoc($users)) {
0 ignored issues
show
Bug introduced by
It seems like $users can also be of type true; however, parameter $result of mysqli_fetch_assoc() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

252
        while ($user = mysqli_fetch_assoc(/** @scrutinizer ignore-type */ $users)) {
Loading history...
253
            installPurgeUnnecessaryKeysForUser((int) $user['id'], $pre);
254
        }
255
    } else {
256
        installPurgeUnnecessaryKeysForUser((int) $user_id, $pre);
257
    }
258
}
259
260
/**
261
 * Delete unnecessary keys for personal items
262
 *
263
 * @param integer $user_id
264
 * @return void
265
 */
266
function installPurgeUnnecessaryKeysForUser(int $user_id=0, string $pre)
267
{
268
    global $db_link;
269
    if ($user_id === 0) {
270
        return;
271
    }
272
273
    $result = mysqli_query(
274
        $db_link,
275
        'SELECT id
276
        FROM ' . $pre . 'items AS i
277
        INNER JOIN ' . $pre . 'log_items AS li ON li.id_item = i.id
278
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN ('.TP_USER_ID.', '.$user_id.')',
279
    );
280
    $rowcount = mysqli_num_rows($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $result of mysqli_num_rows() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

280
    $rowcount = mysqli_num_rows(/** @scrutinizer ignore-type */ $result);
Loading history...
281
    if ($rowcount > 0) {
282
        // Build list of items id
283
        $pfItemsList = [];
284
        while ($row = mysqli_fetch_assoc($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $result of mysqli_fetch_assoc() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

284
        while ($row = mysqli_fetch_assoc(/** @scrutinizer ignore-type */ $result)) {
Loading history...
285
            array_push($pfItemsList, $row['id']);
286
        }
287
288
        /*$personalItem = mysqli_fetch_column($result, 0);*/
289
        $pfItemsList = implode(',', $pfItemsList);
290
        // Item keys
291
        mysqli_query(
292
            $db_link,
293
            'DELETE FROM ' . $pre . 'sharekeys_items
294
            WHERE object_id IN ('.$pfItemsList.') AND user_id NOT IN ('.TP_USER_ID.', '.$user_id.')'
295
        );
296
        // Files keys
297
        mysqli_query(
298
            $db_link,
299
            'DELETE FROM ' . $pre . 'sharekeys_files
300
            WHERE object_id IN ('.$pfItemsList.') AND user_id NOT IN ('.TP_USER_ID.', '.$user_id.')'
301
        );
302
        // Fields keys
303
        mysqli_query(
304
            $db_link,
305
            'DELETE FROM ' . $pre . 'sharekeys_fields
306
            WHERE object_id IN ('.$pfItemsList.') AND user_id NOT IN ('.TP_USER_ID.', '.$user_id.')'
307
        );
308
        // Logs keys
309
        mysqli_query(
310
            $db_link,
311
            'DELETE FROM ' . $pre . 'sharekeys_logs
312
            WHERE object_id IN ('.$pfItemsList.') AND user_id NOT IN ('.TP_USER_ID.', '.$user_id.')'
313
        );
314
    }
315
}
316
317
318
/**
319
 * Permits to refresh the categories of folders
320
 *
321
 * @param array $folderIds
322
 * @return void
323
 */
324
function installHandleFoldersCategories(
325
    array $folderIds, $pre
0 ignored issues
show
Unused Code introduced by
The parameter $pre is not used and could be removed. ( Ignorable by Annotation )

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

325
    array $folderIds, /** @scrutinizer ignore-unused */ $pre

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
326
)
327
{
328
    global $db_link;
329
    $filename = '../includes/config/settings.php';
0 ignored issues
show
Unused Code introduced by
The assignment to $filename is dead and can be removed.
Loading history...
330
    include_once '../sources/main.functions.php';
331
    $pass = defuse_return_decrypted(DB_PASSWD);
332
    $server = DB_HOST;
333
    $pre = DB_PREFIX;
334
    $database = DB_NAME;
335
    $port = intval(DB_PORT);
336
    $user = DB_USER;
337
    $arr_data = array();
338
    $mysqli2 = new mysqli($server, $user, $pass, $database, $port);
339
340
    // force full list of folders
341
    if (count($folderIds) === 0) {
342
        $result = $mysqli2->query('SELECT id
343
            FROM ' . $pre . 'nested_tree
344
            WHERE personal_folder = 0',
345
        );
346
        $rowcount = $result->num_rows;
347
        if ($rowcount > 0) {
348
            while ($row = $result->fetch_assoc()) {
349
                array_push($folderIds, $row['id']);
350
            }
351
        }
352
    }
353
354
    // Get complexity
355
    defineComplexity();
356
357
    // update
358
    foreach ($folderIds as $folder) {
359
        // Do we have Categories
360
        // get list of associated Categories
361
        $arrCatList = array();
362
        $result = $mysqli2->query('SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
363
            f.id_category AS category_id
364
            FROM ' . $pre . 'categories_folders AS f
365
            INNER JOIN ' . $pre . 'categories AS c ON (f.id_category = c.parent_id)
366
            WHERE id_folder = '.$folder
367
        );
368
        $rowcount = $result->num_rows;
369
        if ($rowcount > 0) {
370
            while ($row = $result->fetch_assoc()) {
371
                $arrCatList[$row['id']] = array(
372
                    'id' => $row['id'],
373
                    'title' => $row['title'],
374
                    'level' => $row['level'],
375
                    'type' => $row['type'],
376
                    'masked' => $row['masked'],
377
                    'order' => $row['order'],
378
                    'encrypted_data' => $row['encrypted_data'],
379
                    'role_visibility' => $row['role_visibility'],
380
                    'is_mandatory' => $row['is_mandatory'],
381
                    'category_id' => $row['category_id'],
382
                );
383
            }
384
        }
385
        $arr_data['categories'] = $arrCatList;
386
387
        // Now get complexity
388
        $valTemp = '';
389
        $result = $mysqli2->query('SELECT valeur
390
            FROM ' . $pre . 'misc
391
            WHERE type = "complex" AND intitule = '.$folder
392
        );
393
        $rowcount = $result->num_rows;
394
        if ($rowcount > 0) {
395
            while ($row = $result->fetch_assoc()) {
396
                $valTemp = array(
397
                    'value' => $row['valeur'],
398
                    'text' => TP_PW_COMPLEXITY[$row['valeur']][1],
399
                );
400
            }
401
        }
402
        $arr_data['complexity'] = $valTemp;
403
404
        // Now get Roles
405
        $valTemp = '';
406
        $result = $mysqli2->query('SELECT t.title
407
            FROM ' . $pre . 'roles_values as v
408
            INNER JOIN ' . $pre . 'roles_title as t ON (v.role_id = t.id)
409
            WHERE v.folder_id = '.$folder.'
410
            GROUP BY title'
411
        );
412
        $rowcount = $result->num_rows;
413
        if ($rowcount > 0) {
414
            while ($row = $result->fetch_assoc()) {
415
                $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $row['title'];
416
            }
417
        }
418
        $arr_data['visibilityRoles'] = $valTemp;
419
420
        // now save in DB
421
        $mysqli2->query("UPDATE " . $pre . "nested_tree SET categories = '".json_encode($arr_data)."' WHERE id = ".$folder);
422
    }
423
    
424
    mysqli_close($mysqli2);
425
}
426
427
/**
428
 * Permits to handle the Teampass config file
429
 * $action accepts "rebuild" and "update"
430
 *
431
 * @param string $action   Action to perform
432
 * @param array  $SETTINGS Teampass settings
433
 * @param string $field    Field to refresh
434
 * @param string $value    Value to set
435
 *
436
 * @return string|bool
437
 */
438
function installHandleConfigFile($action, $SETTINGS, $field = null, $value = null)
439
{
440
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
441
    $filename = '../includes/config/settings.php';
0 ignored issues
show
Unused Code introduced by
The assignment to $filename is dead and can be removed.
Loading history...
442
    include_once '../sources/main.functions.php';
443
    $pass = defuse_return_decrypted(DB_PASSWD);
444
    $server = DB_HOST;
445
    $pre = DB_PREFIX;
446
    $database = DB_NAME;
447
    $port = intval(DB_PORT);
448
    $user = DB_USER;
449
    $arr_data = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $arr_data is dead and can be removed.
Loading history...
450
    $mysqli2 = new mysqli($server, $user, $pass, $database, $port);
451
452
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
453
        // perform a copy
454
        if (file_exists($tp_config_file)) {
455
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
456
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
457
            }
458
        }
459
460
        // regenerate
461
        $data = [];
462
        $data[0] = "<?php\n";
463
        $data[1] = "global \$SETTINGS;\n";
464
        $data[2] = "\$SETTINGS = array (\n";
465
466
        $result = $mysqli2->query('SELECT *
467
            FROM ' . $pre . 'misc
468
            WHERE type = "admin"'
469
        );
470
        $rowcount = $result->num_rows;
471
        if ($rowcount > 0) {
472
            while ($row = $result->fetch_assoc()) {
473
                array_push($data, "    '" . $row['intitule'] . "' => '" . htmlspecialchars_decode($row['valeur'], ENT_COMPAT) . "',\n");
474
            }
475
        }
476
        array_push($data, ");\n");
477
        $data = array_unique($data);
478
    // ---
479
    } elseif ($action === 'update' && empty($field) === false) {
480
        $data = file($tp_config_file);
481
        $inc = 0;
482
        $bFound = false;
483
        foreach ($data as $line) {
484
            if (stristr($line, ');')) {
485
                break;
486
            }
487
488
            if (stristr($line, "'" . $field . "' => '")) {
489
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT) . "',\n";
490
                $bFound = true;
491
                break;
492
            }
493
            ++$inc;
494
        }
495
        if ($bFound === false) {
496
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT). "',\n);\n";
497
        }
498
    }
499
    mysqli_close($mysqli2);
500
501
    // update file
502
    file_put_contents($tp_config_file, implode('', $data ?? []));
503
    return true;
504
}