Passed
Pull Request — master (#4738)
by Nils
05:45
created

detectFileEncoding()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      import.queries.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2025 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
33
use Goodby\CSV\Import\Standard\Lexer;
34
use Goodby\CSV\Import\Standard\Interpreter;
35
use Goodby\CSV\Import\Standard\LexerConfig;
36
use voku\helper\AntiXSS;
37
use TeampassClasses\NestedTree\NestedTree;
38
use TeampassClasses\SessionManager\SessionManager;
39
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
40
use TeampassClasses\Language\Language;
41
use TeampassClasses\PerformChecks\PerformChecks;
42
use TeampassClasses\ConfigManager\ConfigManager;
43
44
// Load functions
45
require_once 'main.functions.php';
46
47
// init
48
loadClasses('DB');
49
$session = SessionManager::getSession();
50
$request = SymfonyRequest::createFromGlobals();
51
$lang = new Language($session->get('user-language') ?? 'english');
52
$antiXss = new AntiXSS();
53
54
// Load config
55
$configManager = new ConfigManager();
56
$SETTINGS = $configManager->getAllSettings();
57
58
// Do checks
59
// Instantiate the class with posted data
60
$checkUserAccess = new PerformChecks(
61
    dataSanitizer(
62
        [
63
            'type' => htmlspecialchars($request->request->get('type', ''), ENT_QUOTES, 'UTF-8'),
64
        ],
65
        [
66
            'type' => 'trim|escape',
67
        ],
68
    ),
69
    [
70
        'user_id' => returnIfSet($session->get('user-id'), null),
71
        'user_key' => returnIfSet($session->get('key'), null),
72
    ]
73
);
74
// Handle the case
75
echo $checkUserAccess->caseHandler();
76
if (
77
    $checkUserAccess->userAccessPage('import') === false ||
78
    $checkUserAccess->checkSession() === false
79
) {
80
    // Not allowed page
81
    $session->set('system-error_code', ERR_NOT_ALLOWED);
82
    include $SETTINGS['cpassman_dir'] . '/error.php';
83
    exit;
84
}
85
86
// Define Timezone
87
date_default_timezone_set($SETTINGS['timezone'] ?? 'UTC');
88
89
// Set header properties
90
header('Content-type: text/html; charset=utf-8');
91
header('Cache-Control: no-cache, no-store, must-revalidate');
92
error_reporting(E_ERROR);
93
set_time_limit(0);
94
95
// --------------------------------- //
96
97
// Set some constants for program readability
98
define('KP_PATH', 0);
99
define('KP_GROUP', 1);
100
define('KP_TITLE', 2);
101
define('KP_PASSWORD', 3);
102
define('KP_USERNAME', 4);
103
define('KP_URL', 5);
104
define('KP_UUID', 6);
105
define('KP_NOTES', 7);
106
107
108
// Prepare POST variables
109
$data = [
110
    'type' => $request->request->filter('type', '', FILTER_SANITIZE_SPECIAL_CHARS),
111
    'data' => $request->request->filter('data', '', FILTER_SANITIZE_SPECIAL_CHARS),
112
    'key' => $request->request->filter('key', '', FILTER_SANITIZE_SPECIAL_CHARS),
113
    'file' => $request->request->filter('file', '', FILTER_SANITIZE_SPECIAL_CHARS),
114
];
115
116
$filters = [
117
    'type' => 'trim|escape',
118
    'data' => 'trim|escape',
119
    'key' => 'trim|escape',
120
    'file' => 'cast:integer',
121
];
122
123
$inputData = dataSanitizer(
124
    $data,
125
    $filters
126
);
127
128
129
$tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
130
131
// Build query
132
switch ($inputData['type']) {
133
    //Check if import CSV file format is what expected
134
    case 'import_file_format_csv':
135
        // Check KEY and rights
136
        if ($inputData['key'] !== $session->get('key')) {
137
            echo prepareExchangedData(
138
                array(
139
                    'error' => true,
140
                    'message' => $lang->get('key_is_not_correct'),
141
                ),
142
                'encode'
143
            );
144
            break;
145
        }
146
147
        //load full tree
148
        $tree->rebuild();
149
        $tree = $tree->getDescendants();
150
151
        // Init post variable
152
        $post_operation_id = $inputData['file'];
153
154
        // Get filename from database
155
        $data = DB::queryFirstRow(
156
            'SELECT valeur
157
            FROM '.prefixTable('misc').'
158
            WHERE increment_id = %i AND type = "temp_file"',
159
            $post_operation_id
160
        );
161
        
162
        // Delete operation id
163
        DB::delete(
164
            prefixTable('misc'),
165
            "increment_id = %i AND type = 'temp_file'",
166
            $post_operation_id
167
        );
168
169
        // Initialisation
170
        $file = $SETTINGS['path_to_files_folder'] . '/' . $data['valeur'];
171
        $importation_possible = true;
172
        $valuesToImport = [];
173
        $items_number = 0;
174
        $batchInsert = [];
175
        $uniqueFolders = [];
176
        $comment = '';
177
178
        // Vérifier si le fichier est accessible
179
        if (!file_exists($file) || !is_readable($file)) {
180
            echo prepareExchangedData(
181
                array('error' => true, 'message' => $lang->get('cannot_open_file')),
182
                'encode'
183
            );
184
            unlink($file);
185
            break;
186
        }
187
188
        // Ouvrir le fichier pour récupérer l'en-tête
189
        $fp = fopen($file, 'r');
190
        $header = fgetcsv($fp);
191
        fclose($fp);
192
193
        // Vérifier si l'en-tête est valide
194
        if ($header === false || empty($header)) {
195
            echo prepareExchangedData(
196
                array('error' => true, 'message' => $lang->get('import_error_no_read_possible')),
197
                'encode'
198
            );
199
            unlink($file);
200
            break;
201
        }
202
203
        // Initialisation du tableau de stockage des valeurs
204
        $valuesToImport = [];
205
206
        // Configuration du parser CSV
207
        $config = new LexerConfig();
208
        $lexer = new Lexer($config);
209
        $config->setIgnoreHeaderLine(true);
210
211
        // Fonction pour construire la liste des encodages disponibles
212
        function getAvailableEncodings() {
213
            // Liste des encodages que nous voulons supporter (par ordre de priorité)
214
            $desiredEncodings = [
215
                'UTF-8',
216
                'UTF-16',
217
                'UTF-16LE',
218
                'UTF-16BE',
219
                'ISO-8859-1',
220
                'ISO-8859-15',
221
                'Windows-1252',
222
                'Windows-1251',  // Cyrillique
223
                'CP1251',        // Cyrillique alternatif
224
                'KOI8-R',        // Cyrillique russe
225
                'Shift_JIS',     // Japonais
226
                'EUC-JP',        // Japonais
227
                'ISO-2022-JP',   // Japonais
228
                'TIS-620',       // Thaï
229
                'Windows-874',   // Thaï Windows
230
                'Big5',          // Chinois traditionnel
231
                'GB2312',        // Chinois simplifié
232
                'GBK',           // Chinois simplifié étendu
233
                'EUC-KR',        // Coréen
234
                'ISO-8859-2',    // Europe centrale
235
                'ISO-8859-5',    // Cyrillique ISO
236
                'ISO-8859-7',    // Grec
237
                'Windows-1250',  // Europe centrale
238
                'Windows-1253',  // Grec
239
                'Windows-1254',  // Turc
240
                'Windows-1255',  // Hébreu
241
                'Windows-1256',  // Arabe
242
            ];
243
            
244
            // Récupérer les encodages supportés par le système
245
            $systemEncodings = mb_list_encodings();
246
            
247
            // Filtrer pour ne garder que ceux qui sont disponibles
248
            $availableEncodings = [];
249
            foreach ($desiredEncodings as $encoding) {
250
                if (in_array($encoding, $systemEncodings)) {
251
                    $availableEncodings[] = $encoding;
252
                }
253
            }
254
            
255
            // S'assurer qu'UTF-8 est toujours présent (normalement le cas)
256
            if (!in_array('UTF-8', $availableEncodings)) {
257
                array_unshift($availableEncodings, 'UTF-8');
258
            }
259
            
260
            return $availableEncodings;
261
        }
262
263
        // Fonction pour détecter l'encodage avec les encodages disponibles
264
        /**
265
         * Detects the encoding of a file using available encodings.
266
         * @param string $filepath The path to the file to be checked.
267
         * @return string The detected encoding or 'UTF-8' if detection fails.
268
         */
269
        function detectFileEncoding($filepath): string
270
        {
271
            $content = file_get_contents($filepath);
272
            $availableEncodings = getAvailableEncodings();
273
            
274
            $detected = mb_detect_encoding($content, $availableEncodings, true);
275
            return $detected ?: 'UTF-8';
276
        }
277
278
        // Detect file encoding and set it in the config
279
        $detectedEncoding = detectFileEncoding($file);
280
        $config->setFromCharset($detectedEncoding);
281
        $config->setToCharset('UTF-8');
282
283
        // Get the data and ensure columns are correctly mapped
284
        $interpreter = new Interpreter();
285
        $interpreter->addObserver(function (array $row) use (&$valuesToImport, $header) {
286
            $rowData = array_combine($header, $row);
287
288
            if ($rowData !== false) {
289
                $valuesToImport[] = array(
290
                    'Label' => isset($rowData['label']) ? trim($rowData['label']) : '',
291
                    'Login' => isset($rowData['login']) ? trim($rowData['login']) : '',
292
                    'Password' => isset($rowData['password']) ? trim($rowData['password']) : '',
293
                    'url' => isset($rowData['url']) ? trim($rowData['url']) : '',
294
                    'Comments' => isset($rowData['description']) ? trim($rowData['description']) : '',
295
                    'Folder' => isset($rowData['folder']) ? trim($rowData['folder']) : '',
296
                );
297
            }
298
        });
299
300
        // Launch parsing with `Lexer`
301
        $lexer->parse($file, $interpreter);
302
303
        // Check if some date were imported
304
        if (empty($valuesToImport)) {
305
            echo prepareExchangedData(
306
                array('error' => true, 'message' => $lang->get('import_error_no_read_possible')),
307
                'encode'
308
            );
309
            unlink($file);
310
            break;
311
        }
312
        
313
        // Process lines
314
        $continue_on_next_line = false;
315
        $comment = "";
316
        foreach ($valuesToImport as $row) {
317
            // Check that each line has 6 columns
318
            if (count($row) !== 6) {
319
                echo prepareExchangedData(
320
                    array('error' => true, 'message' => $lang->get('import_error_invalid_structure')),
321
                    'encode'
322
                );
323
                unlink($file);
324
                break;
325
            }
326
        
327
            // CLean data
328
            $label    = cleanInput($row['Label']);
329
            $login    = cleanInput($row['Login']);
330
            $pwd      = cleanInput($row['Password']);
331
            $url      = cleanInput($row['url']);
332
            $folder   = cleanInput($row['Folder']);
333
            $comments = cleanInput($row['Comments']);
334
        
335
            // Handle multiple lignes description
336
            if (strpos($comments, '<br>') !== false || strpos($label, '<br>') !== false) {
337
                $continue_on_next_line = true;
338
                $comment .= " " . $label . " " . $comments;
339
            } else {
340
                // Insert previous line if changing line
341
                if (!empty($label)) {
342
                    $items_number++;
343
344
                    // Insert in batch
345
                    $batchInsert[] = array(
346
                        'label'        => $label,
347
                        'description'  => $comment . $comments,
348
                        'pwd'          => $pwd,
349
                        'url'          => $url,
350
                        'folder'       => ((int) $session->get('user-admin') === 1 || (int) $session->get('user-manager') === 1 || (int) $session->get('user-can_manage_all_users') === 1) ? $folder : '',
351
                        'login'        => $login,
352
                        'operation_id' => $post_operation_id,
353
                    );
354
355
                    // Store unique folders
356
                    // Each folder is the unique element of the path located inside $folder and delimited by '/' or '\'
357
                    $folders = preg_split('/[\/\\\\]/', $folder);
358
                    foreach ($folders as $folder) {
359
                        if (!empty($folder)) {
360
                            $uniqueFolders[$folder] = $folder;
361
                        }
362
                    }
363
                    $label = '';
364
                }
365
                // Update current variables
366
                $comment = '';
367
                $continue_on_next_line = false;
368
            }
369
        }
370
371
        // Insert last line
372
        if (!empty($label)) {
373
            $items_number++;
374
375
            // Insert in batch
376
            $batchInsert[] = array(
377
                'label'        => $label,
378
                'description'  => $comment . $comments,
379
                'pwd'          => $pwd,
380
                'url'          => $url,
381
                'folder'       => ((int) $session->get('user-admin') === 1 || (int) $session->get('user-manager') === 1 || (int) $session->get('user-can_manage_all_users') === 1) ? $folder : '',
382
                'login'        => $login,
383
                'operation_id' => $post_operation_id,
384
            );
385
386
            // Store unique folders
387
            // Each folder is the unique element of the path located inside $folder and delimited by '/' or '\'
388
            $folders = preg_split('/[\/\\\\]/', $folder);
389
            foreach ($folders as $folder) {
390
                if (!empty($folder)) {
391
                    $uniqueFolders[$folder] = $folder;
392
                }
393
            }
394
        }
395
        
396
        // Insert in database (with batch optimisation)
397
        if (!empty($batchInsert)) {
398
            $tableName = prefixTable('items_importations');
399
            $values = [];
400
        
401
            foreach ($batchInsert as $data) {
402
                $values[] = "('" . implode("','", array_map('addslashes', $data)) . "')";
403
            }
404
        
405
            $sql = "INSERT INTO `$tableName` (`label`, `description`, `pwd`, `url`, `folder`, `login`, `operation_id`) VALUES " . implode(',', $values);
406
            
407
            DB::query($sql);
408
        }
409
410
        // Display results
411
        echo prepareExchangedData(
412
            array('error' => false,
413
                'operation_id' => $post_operation_id,
414
                'items_number' => $items_number,
415
                'folders_number' => count($uniqueFolders),        
416
                'userCanManageFolders' => ((int) $session->get('user-admin') === 1 || (int) $session->get('user-manager') === 1 || (int) $session->get('user-can_manage_all_users') === 1) ? 1 : 0
417
            ),
418
            'encode'
419
        );
420
        
421
        // Delete file after processing	
422
        unlink($file);
423
        break;
424
425
    // Create new folders
426
    case 'import_csv_folders':
427
        // Check KEY and rights
428
        if ($inputData['key'] !== $session->get('key')) {
429
            echo prepareExchangedData(
430
                array(
431
                    'error' => true,
432
                    'message' => $lang->get('key_is_not_correct'),
433
                ),
434
                'encode'
435
            );
436
            break;
437
        }
438
439
        // Check if user is manager at least.
440
        if ((int) $session->get('user-admin') === 0 && (int) $session->get('user-manager') === 0 && (int) $session->get('user-can_manage_all_users') === 0) {
441
            echo prepareExchangedData(
442
                array(
443
                    'error' => true,
444
                    'message' => $lang->get('import_error_no_rights'),
445
                ),
446
                'encode'
447
            );
448
            break;
449
        }
450
451
        // Decrypt and retreive data in JSON format
452
        $dataReceived = prepareExchangedData(
453
            $inputData['data'],
454
            'decode'
455
        );
456
457
        // Is this a personal folder?
458
        $personalFolder = in_array($dataReceived['folderId'], $session->get('user-personal_folders')) ? 1 : 0;
459
460
        // Get all folders from objects in DB
461
        $itemsPath = DB::query(
462
            'SELECT folder, increment_id
463
            FROM '.prefixTable('items_importations').'
464
            WHERE operation_id = %i
465
            LIMIT %i, %i',
466
            $dataReceived['csvOperationId'],
467
            $dataReceived['offset'],
468
            $dataReceived['limit']
469
        );
470
471
472
        // Save matches "path -> ID" to prevent against multiple insertions
473
        $folderIdMap = $dataReceived['folderIdMap'] ?? [];
474
        
475
        require_once 'folders.class.php';
476
        $folderManager = new FolderManager($lang);
477
478
        // Get all folders from objects in DB
479
        foreach ($itemsPath as $item) {
480
            $path = $item['folder'];
481
            $importId = $item['increment_id']; // Entry ID in items_importations
482
483
            $parts = explode("\\", $path); // Décomposer le chemin en sous-dossiers
484
            $currentPath = "";
485
            $parentId = $dataReceived['folderId']; // Strating with provided folder
486
487
            foreach ($parts as $part) {
488
                $currentPath = trim($currentPath . "/" . $part, "/");
489
                $currentFolder = $part;
490
491
                // Check if this folder has already been created
492
                if (isset($folderIdMap[$currentPath]) || empty($currentFolder)) {
493
                    $parentId = $folderIdMap[$currentPath];
494
                    continue; // Jump to next iteration
495
                }
496
497
                // Vérifier si le dossier existe déjà en base
498
                $existingId = DB::queryFirstField(
499
                    'SELECT id FROM '.prefixTable('nested_tree').' WHERE title = %s AND parent_id = %i',
500
                    $currentFolder, // Searching only by name
501
                    $parentId // Ensure we search the correct parent
502
                );
503
504
                if ($existingId) {
505
                    $folderIdMap[$currentPath] = $existingId;
506
                    $parentId = $existingId;
507
                } else {
508
                    // Insert folder and get its ID
509
                    $params = [
510
                        'title' => (string) $currentFolder,
511
                        'parent_id' => (int) $parentId,
512
                        'personal_folder' => (int) $personalFolder,
513
                        'complexity' => $dataReceived['folderPasswordComplexity'] ?? 0,
514
                        'duration' => 0,
515
                        'access_rights' => $dataReceived['folderAccessRight'] ?? '',
516
                        'user_is_admin' => (int) $session->get('user-admin'),
517
                        'user_accessible_folders' => (array) $session->get('user-accessible_folders'),
518
                        'user_is_manager' => (int) $session->get('user-manager'),
519
                        'user_can_create_root_folder' => (int) $session->get('user-can_create_root_folder'),
520
                        'user_can_manage_all_users' => (int) $session->get('user-can_manage_all_users'),
521
                        'user_id' => (int) $session->get('user-id'),
522
                        'user_roles' => (string) $session->get('user-roles')
523
                    ];
524
                    $options = [
525
                        'rebuildFolderTree' => false,
526
                        'setFolderCategories' => false,
527
                        'manageFolderPermissions' => true,
528
                        'copyCustomFieldsCategories' => false,
529
                        'refreshCacheForUsersWithSimilarRoles' => true,
530
                    ];
531
                    $creationStatus = $folderManager->createNewFolder($params, $options);
532
                    $folderCreationDone = $creationStatus['error'];
533
534
                    if ((int) $folderCreationDone === 0) {
535
                        $newFolderId = $creationStatus['newId'];
536
                        // User created the folder
537
                        // Add new ID to list of visible ones
538
                        if ((int) $session->get('user-admin') === 0 && $creationStatus['error'] === false && $newFolderId !== 0) {
539
                            SessionManager::addRemoveFromSessionArray('user-accessible_folders', [$newFolderId], 'add');
540
                        }
541
542
                        // Save ID in map to avoid recreating it
543
                        $folderIdMap[$currentPath] = $newFolderId;
544
                        $parentId = $newFolderId;
545
                    } else {
546
                        // Get ID of existing folder
547
                        $ret = DB::queryFirstRow(
548
                            'SELECT *
549
                            FROM ' . prefixTable('nested_tree') . '
550
                            WHERE title = %s',
551
                            $currentFolder
552
                        );
553
                        $newFolderId = $ret['id'];
554
                        $parentId = $newFolderId;
555
                    }
556
                }
557
            }
558
559
            // Update the importation entry with the new folder ID
560
            DB::update(prefixTable('items_importations'), [
561
                'folder_id' => $parentId
562
            ], "increment_id=%i", $importId);
563
        }
564
565
        echo prepareExchangedData(
566
            array(
567
                'error' => false,
568
                'message' => '',
569
                'processedCount' => count($itemsPath),
570
                'folderIdMap' => $folderIdMap,
571
            ),
572
            'encode'
573
        );
574
        
575
        break;
576
577
578
    //Insert into DB the items the user has selected
579
    case 'import_csv_items':
580
        // Check KEY and rights
581
        if ($inputData['key'] !== $session->get('key')) {
582
            echo prepareExchangedData(
583
                array(
584
                    'error' => true,
585
                    'message' => $lang->get('key_is_not_correct'),
586
                ),
587
                'encode'
588
            );
589
            break;
590
        }
591
        
592
593
        // Decrypt and retreive data in JSON format
594
        $dataReceived = prepareExchangedData(
595
            $inputData['data'],
596
            'decode'
597
        );
598
599
        //Get some info about personal folder
600
        $personalFolder = in_array($dataReceived['folderId'], $session->get('user-personal_folders')) ? 1 : 0;
601
602
        // Prepare some variables
603
        $targetFolderId = $dataReceived['folderId']; 
604
        $targetFolderName = DB::queryFirstField(
605
            'SELECT title
606
            FROM '.prefixTable('nested_tree').'
607
            WHERE id = %i',
608
            $targetFolderId
609
        ); 
610
        $personalFolder = in_array($dataReceived['folderId'], $session->get('user-personal_folders')) ? 1 : 0;
611
612
        // Prepare some variables
613
        $targetFolderId = $dataReceived['folderId']; 
614
        $targetFolderName = DB::queryFirstField(
615
            'SELECT title
616
            FROM '.prefixTable('nested_tree').'
617
            WHERE id = %i',
618
            $targetFolderId
619
        ); 
620
621
        // Get all folders from objects in DB
622
        if ($dataReceived['foldersNumber'] > 0) {
623
            $items = DB::query(
624
                'SELECT ii.label, ii.login, ii.pwd, ii.url, ii.description, ii.folder_id, ii.increment_id, nt.title
625
                FROM '.prefixTable('items_importations').' AS ii
626
                INNER JOIN '.prefixTable('nested_tree').' AS nt ON ii.folder_id = nt.id
627
                WHERE ii.operation_id = %i
628
                LIMIT %i, %i',
629
                $dataReceived['csvOperationId'],
630
                $dataReceived['offset'],
631
                $dataReceived['limit']
632
            );
633
        } else {
634
            $items = DB::query(
635
                'SELECT ii.label, ii.login, ii.pwd, ii.url, ii.description, ii.folder_id, ii.increment_id
636
                FROM '.prefixTable('items_importations').' AS ii
637
                WHERE ii.operation_id = %i
638
                LIMIT %i, %i',
639
                $dataReceived['csvOperationId'],
640
                $dataReceived['offset'],
641
                $dataReceived['limit']
642
            );
643
        }
644
645
        // Init some variables
646
        $insertedItems = $dataReceived['insertedItems'] ?? 0;
647
        $failedItems = [];
648
649
        // Loop on items
650
        foreach ($items as $item) {         
651
            try {
652
                // Handle case where password is empty
653
                if (($session->has('user-create_item_without_password') && 
654
                    null !== $session->get('user-create_item_without_password') &&
655
                    (int) $session->get('user-create_item_without_password') !== 1) ||
656
                    !empty($item['pwd'])) {
657
                    // Encrypt password
658
                    $cryptedStuff = doDataEncryption($item['pwd']);
659
                } else {
660
                    $cryptedStuff['encrypted'] = '';
661
                    $cryptedStuff['objectKey'] = '';
662
                }
663
                $itemPassword = $cryptedStuff['encrypted'];
664
            
665
                // Insert new item in table ITEMS
666
                DB::insert(
667
                    prefixTable('items'),
668
                    array(
669
                        'label' => substr($item['label'], 0, 500),
670
                        'description' => empty($item['description']) === true ? '' : $item['description'],
671
                        'pw' => $itemPassword,
672
                        'pw_iv' => '',
673
                        'url' => empty($item['url']) === true ? '' : substr($item['url'], 0, 500),
674
                        'id_tree' => is_null($item['folder_id']) === true ? $targetFolderId : (int) $item['folder_id'],
675
                        'login' => empty($item['login']) === true ? '' : substr($item['login'], 0, 200),
676
                        'anyone_can_modify' => $dataReceived['editAll'],
677
                        'encryption_type' => 'teampass_aes',
678
                        'item_key' => uniqidReal(50),
679
                        'created_at' => time(),
680
                    )
681
                );
682
                $newId = DB::insertId();
683
684
                // Create new task for the new item
685
                // If it is not a personnal one
686
                if ((int) $personalFolder === 0) {
687
                    if ($dataReceived['keysGenerationWithTasksHandler' === 'tasksHandler']) {
688
                        // Create task for the new item
689
                        storeTask(
690
                            'new_item',
691
                            $session->get('user-id'),
692
                            0,
693
                            (int) $item['folder_id'],
694
                            (int) $newId,
695
                            $cryptedStuff['objectKey'],
696
                        );
697
                    } else {
698
                        // Create sharekeys for current user
699
                        storeUsersShareKey(
700
                            prefixTable('sharekeys_items'),
701
                            (int) $item['folder_id'],
702
                            (int) $newId,
703
                            $cryptedStuff['objectKey'],
704
                            false
705
                        );
706
                    }
707
                } else {
708
                    // Create sharekeys for current user
709
                    storeUsersShareKey(
710
                        prefixTable('sharekeys_items'),
711
                        (int) $personalFolder,
712
                        (int) $newId,
713
                        $cryptedStuff['objectKey'],
714
                        true
715
                    );
716
                }
717
718
                //if asked, anyone in role can modify
719
                if ((int) $dataReceived['editRole'] === 1) {
720
                    foreach ($session->get('system-array_roles') as $role) {
721
                        DB::insert(
722
                            prefixTable('restriction_to_roles'),
723
                            array(
724
                                'role_id' => $role['id'],
725
                                'item_id' => $newId,
726
                            )
727
                        );
728
                    }
729
                }
730
731
                // Insert new item in table LOGS_ITEMS
732
                DB::insert(
733
                    prefixTable('log_items'),
734
                    array(
735
                        'id_item' => $newId,
736
                        'date' => time(),
737
                        'id_user' => $session->get('user-id'),
738
                        'action' => 'at_creation',
739
                    )
740
                );
741
                
742
                // Add item to cache table
743
                updateCacheTable('add_value', (int) $newId);
744
745
                // Update items_importation table
746
                DB::update(
747
                    prefixTable('items_importations'),
748
                    array(
749
                        'increment_id' => $newId,
750
                        'imported_at'=> time(),
751
                    ),
752
                    "increment_id=%i",
753
                    $item['increment_id']
754
                );
755
756
                $insertedItems++;
757
758
            } catch (Exception $e) {
759
                // Log the error and store the failed item
760
                $failedItems[] = [
761
                    'increment_id' => $item['increment_id'],
762
                    'error' => $e->getMessage(),
763
                ];
764
            }
765
        }
766
767
        echo prepareExchangedData(
768
            array(
769
                'error' => false,
770
                'message' => '',
771
                'insertedItems' => $insertedItems,
772
                'failedItems' => $failedItems,
773
            ),
774
            'encode'
775
        );
776
        break;
777
778
    case 'import_csv_items_finalization':
779
        // Check KEY and rights
780
        if ($inputData['key'] !== $session->get('key')) {
781
            echo prepareExchangedData(
782
                array(
783
                    'error' => true,
784
                    'message' => $lang->get('key_is_not_correct'),
785
                ),
786
                'encode'
787
            );
788
            break;
789
        }
790
791
        // Decrypt and retreive data in JSON format
792
        $receivedParameters = prepareExchangedData(
793
            $inputData['data'],
794
            'decode'
795
        );
796
        $csvOperationId = filter_var($receivedParameters['csvOperationId'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
797
798
        // Delete operation id
799
        DB::delete(
800
            prefixTable('items_importations'),
801
            'operation_id = %i',
802
            $csvOperationId
803
        );
804
805
        echo prepareExchangedData(
806
            array(
807
                'error' => false,
808
                'message' => '',
809
            ),
810
            'encode'
811
        );
812
        break;
813
814
815
    //Check if import KEEPASS file format is what expected
816
    case 'import_file_format_keepass':
817
        // Check KEY and rights
818
        if ($inputData['key'] !== $session->get('key')) {
819
            echo prepareExchangedData(
820
                array(
821
                    'error' => true,
822
                    'message' => $lang->get('key_is_not_correct'),
823
                ),
824
                'encode'
825
            );
826
            break;
827
        }
828
829
        // Decrypt and retreive data in JSON format
830
        $receivedParameters = prepareExchangedData(
831
            $inputData['data'],
832
            'decode'
833
        );
834
        $post_operation_id = filter_var($receivedParameters['file'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
835
        $post_folder_id = filter_var($receivedParameters['folder_id'], FILTER_SANITIZE_NUMBER_INT);
836
837
        // Get filename from database
838
        $data = DB::queryFirstRow(
839
            'SELECT valeur
840
            FROM '.prefixTable('misc').'
841
            WHERE increment_id = %i AND type = "temp_file"',
842
            $post_operation_id
843
        );
844
845
        // Delete operation id
846
        DB::delete(
847
            prefixTable('misc'),
848
            'increment_id = %i AND type = "temp_file"',
849
            $post_operation_id
850
        );
851
852
        // do some initializations
853
        $file = $data['valeur'];
854
855
        //read xml file
856
        if (file_exists($SETTINGS['path_to_files_folder'].'/'.$file)) {
857
            $xml = simplexml_load_file(
858
                $SETTINGS['path_to_files_folder'].'/'.$file
859
            );
860
        }
861
862
        // Convert XML to associative array
863
        $xmlfile = file_get_contents($SETTINGS['path_to_files_folder'].'/'.$file);
864
        $new = simplexml_load_string($xmlfile);
865
        $con = json_encode($new);
866
        $newArr = json_decode($con, true);
867
868
869
        /**
870
         * Recursive function to process the Keepass XML structure.
871
         * 
872
         * @param array $array The current array to process.
873
         * @param string $previousFolder The parent folder ID.
874
         * @param array $newItemsToAdd The new items to add to the database.
875
         * @param int $level The current level of the recursion.
876
         * 
877
         * @return array The new items to add to the database.
878
         */
879
        function recursive($array, $previousFolder, $newItemsToAdd, $level) : array
880
        {
881
            // Handle entries (items)
882
            if (isset($array['Entry'])) {
883
                $newItemsToAdd = handleEntries($array['Entry'], $previousFolder, $newItemsToAdd);
884
            }
885
886
            // Handle groups (folders)
887
            if (isset($array['Group']) && is_array($array['Group'])) {
888
                $newItemsToAdd = handleGroups($array['Group'], $previousFolder, $newItemsToAdd, $level);
889
            }
890
891
            return $newItemsToAdd;
892
        }
893
894
        /**
895
         * Handle entries (items) within the structure.
896
         * It processes each entry and adds it to the new items list.
897
         * 
898
         * @param array $entries The entries to process.
899
         * @param string $previousFolder The parent folder ID.
900
         * @param array $newItemsToAdd The new items to add to the database.
901
         * 
902
         * @return array The new items to add to the database.
903
         */
904
        function handleEntries(array $entries, string $previousFolder, array $newItemsToAdd) : array
905
        {
906
            foreach ($entries as $key => $value) {
907
                // Check if the entry has a 'String' field and process it
908
                if (isset($value['String'])) {
909
                    $newItemsToAdd['items'][] = buildItemDefinition($value['String'], $previousFolder);
910
                } 
911
                // If it's a direct 'String' item, build a simple item
912
                elseif ($key === 'String') {
913
                    $newItemsToAdd['items'][] = buildSimpleItem($value, $previousFolder);
914
                }
915
            }
916
917
            return $newItemsToAdd;
918
        }
919
920
        /**
921
         * Build an item definition from the 'String' fields.
922
         * Converts the key-value pairs into a usable item format.
923
         * 
924
         * @param array $strings The 'String' fields to process.
925
         * @param string $previousFolder The parent folder ID.
926
         * 
927
         * @return array The item definition.
928
         */
929
        function buildItemDefinition(array $strings, string $previousFolder) : array
930
        {
931
            $itemDefinition = [];
932
            // Loop through each 'String' entry and map keys and values
933
            foreach ($strings as $entry) {
934
                $itemDefinition[$entry['Key']] = is_array($entry['Value']) ? '' : $entry['Value'];
935
            }
936
937
            // Set the parent folder and ensure default values for certain fields
938
            $itemDefinition['parentFolderId'] = $previousFolder;
939
            $itemDefinition['Notes'] = $itemDefinition['Notes'] ?? '';
940
            $itemDefinition['URL'] = $itemDefinition['URL'] ?? '';
941
            $itemDefinition['Password'] = $itemDefinition['Password'] ?? '';
942
943
            return $itemDefinition;
944
        }
945
946
        /**
947
         * Build a simple item with predefined fields.
948
         * This is used when there is no associated key, just ordered values.
949
         * 
950
         * @param array $value The ordered values to process.
951
         * @param string $previousFolder The parent folder ID.
952
         * 
953
         * @return array The simple item definition.
954
         */
955
        function buildSimpleItem(array $value, string $previousFolder) : array
956
        {
957
            return [
958
                'Notes' => is_array($value[0]['Value']) ? '' : $value[0]['Value'],
959
                'Title' => is_array($value[2]['Value']) ? '' : $value[2]['Value'],
960
                'Password' => is_array($value[1]['Value']) ? '' : $value[1]['Value'],
961
                'URL' => is_array($value[3]['Value']) ? '' : $value[3]['Value'],
962
                'UserName' => is_array($value[4]['Value']) ? '' : $value[4]['Value'],
963
                'parentFolderId' => $previousFolder,
964
            ];
965
        }
966
967
        /**
968
         * Handle groups (folders) within the structure.
969
         * It processes each group and recursively goes deeper into subgroups and subentries.
970
         * 
971
         * @param array $groups The groups to process.
972
         * @param string $previousFolder The parent folder ID.
973
         * @param array $newItemsToAdd The new items to add to the database.
974
         * 
975
         * @return array The new items to add to the database.
976
         */
977
        function handleGroups($groups, string $previousFolder, array $newItemsToAdd, int $level) : array
978
        {
979
            // If a single group is found, wrap it into an array
980
            if (isset($groups['UUID'])) {
981
                $groups = [$groups];
982
            }
983
984
            foreach ($groups as $group) {
985
                // Add the current group (folder) to the list
986
                $newItemsToAdd['folders'][] = [
987
                    'folderName' => $group['Name'],
988
                    'uuid' => $group['UUID'],
989
                    'parentFolderId' => $previousFolder,
990
                    'level' => $level,
991
                ];
992
993
                // Recursively process entries and subgroups inside this group
994
                $newItemsToAdd = recursive(
995
                    [
996
                        'Entry' => $group['Entry'] ?? '',
997
                        'Group' => $group['Group'] ?? '',
998
                    ],
999
                    $group['UUID'],
1000
                    $newItemsToAdd,
1001
                    $level + 1
1002
                );
1003
            }
1004
1005
            return $newItemsToAdd;
1006
        }
1007
1008
        // Start the recursive processing
1009
        $ret = recursive(
1010
            array_merge(
1011
                ['Entry' => $newArr['Root']['Group']['Entry']],
1012
                ['Group' => $newArr['Root']['Group']['Group']],
1013
            ),
1014
            $post_folder_id,
1015
            [
1016
                'folders' => [],
1017
                'items' => []
1018
            ],
1019
            1,
1020
        );
1021
1022
        
1023
        echo prepareExchangedData(
1024
            array(
1025
                'error' => false,
1026
                'message' => '',
1027
                'data' => $ret,
1028
            ),
1029
            'encode'
1030
        );
1031
1032
        break;
1033
1034
    // KEEPASS - CREATE FOLDERS
1035
    case 'keepass_create_folders':
1036
        // Check KEY and rights
1037
        if ($inputData['key'] !== $session->get('key')) {
1038
            echo prepareExchangedData(
1039
                array(
1040
                    'error' => true,
1041
                    'message' => $lang->get('key_is_not_correct'),
1042
                ),
1043
                'encode'
1044
            );
1045
            break;
1046
        }
1047
1048
        // Decrypt and retreive data in JSON format
1049
        $receivedParameters = prepareExchangedData(
1050
            $inputData['data'],
1051
            'decode'
1052
        );
1053
1054
        $post_folder_id = filter_var($receivedParameters['folder-id'], FILTER_SANITIZE_NUMBER_INT);
1055
        $$inputData['editAll'] = filter_var($receivedParameters['edit-all'], FILTER_SANITIZE_NUMBER_INT);
1056
        $$inputData['editRole'] = filter_var($receivedParameters['edit-role'], FILTER_SANITIZE_NUMBER_INT);
1057
        $post_folders = filter_var_array(
1058
            $receivedParameters['folders'],
1059
            FILTER_SANITIZE_FULL_SPECIAL_CHARS
1060
        );
1061
1062
        // get destination folder informations
1063
        $destinationFolderInfos = getFolderComplexity((int) $post_folder_id, $session->get('user-personal_folders'));
1064
        $arrFolders[$post_folder_id] = [
1065
            'id' => (int) $post_folder_id,
1066
            'level' => 1,
1067
            'isPF' => false,
1068
        ];
1069
        $startPathLevel = 1;
1070
1071
        foreach($post_folders as $folder) {
1072
            // get parent id
1073
            $parentId = $arrFolders[$folder['parentFolderId']];
1074
1075
            // create folder in DB
1076
            $folderId = createFolder(
1077
                $folder['folderName'],
1078
                $parentId['id'],
1079
                $folder['level'],
1080
                $startPathLevel,
1081
                $destinationFolderInfos['levelPwComplexity']
1082
            );
1083
1084
            // manage parent
1085
            $arrFolders[$folder['uuid']] = [
1086
                'id' => (int) $folderId,
1087
                'level' => (int) ($folder['level'] + $startPathLevel),
1088
                'isPF' => $destinationFolderInfos['importPF'],
1089
            ];
1090
        }
1091
        //rebuild full tree
1092
        $tree->rebuild();
1093
1094
1095
        echo prepareExchangedData(
1096
            array(
1097
                'error' => false,
1098
                'message' => '',
1099
                'folders' => $arrFolders,
1100
            ),
1101
            'encode'
1102
        );
1103
1104
        break;
1105
1106
    // KEEPASS - CREATE ITEMS
1107
    case 'keepass_create_items':
1108
        // Check KEY and rights
1109
        if ($inputData['key'] !== $session->get('key')) {
1110
            echo prepareExchangedData(
1111
                array(
1112
                    'error' => true,
1113
                    'message' => $lang->get('key_is_not_correct'),
1114
                ),
1115
                'encode'
1116
            );
1117
            break;
1118
        }
1119
1120
        // Decrypt and retreive data in JSON format
1121
        $receivedParameters = prepareExchangedData(
1122
            $inputData['data'],
1123
            'decode'
1124
        );
1125
1126
        $$inputData['editAll'] = filter_var($receivedParameters['edit-all'], FILTER_SANITIZE_NUMBER_INT);
1127
        $$inputData['editRole'] = filter_var($receivedParameters['edit-role'], FILTER_SANITIZE_NUMBER_INT);
1128
        $post_folders = filter_var_array(
1129
            $receivedParameters['folders'],
1130
            FILTER_SANITIZE_FULL_SPECIAL_CHARS
1131
        );
1132
        $post_items = filter_var_array(
1133
            $receivedParameters['items'],
1134
            FILTER_SANITIZE_FULL_SPECIAL_CHARS
1135
        );
1136
        $ret = '';
1137
1138
        // Start transaction for better performance
1139
        DB::startTransaction();
1140
1141
        // Import all items
1142
        foreach($post_items as $item) {
1143
            // get info about this folder
1144
            $destinationFolderMore = DB::queryFirstRow(
1145
                'SELECT title FROM '.prefixTable('nested_tree').' WHERE id = %i',
1146
                (int) $post_folders[$item['parentFolderId']]['id']
1147
            );
1148
1149
            // Handle case where pw is empty
1150
            // if not allowed then warn user
1151
            if (($session->has('user-create_item_without_password') && null !== $session->get('user-create_item_without_password')
1152
                && (int) $session->get('user-create_item_without_password') !== 1
1153
                ) ||
1154
                empty($item['Password']) === false
1155
            ) {
1156
                // NEW ENCRYPTION
1157
                $cryptedStuff = doDataEncryption($item['Password']);
1158
            } else {
1159
                $cryptedStuff['encrypted'] = '';
1160
                $cryptedStuff['objectKey'] = '';
1161
            }
1162
            $post_password = $cryptedStuff['encrypted'];
1163
1164
            //ADD item
1165
            DB::insert(
1166
                prefixTable('items'),
1167
                array(
1168
                    'label' => substr($item['Title'], 0, 500),
1169
                    'description' => $item['Notes'],
1170
                    'pw' => $cryptedStuff['encrypted'],
1171
                    'pw_iv' => '',
1172
                    'url' => substr($item['URL'], 0, 500),
1173
                    'id_tree' => $post_folders[$item['parentFolderId']]['id'],
1174
                    'login' => substr($item['UserName'], 0, 500),
1175
                    'anyone_can_modify' => $$inputData['editAll'],
1176
                    'encryption_type' => 'teampass_aes',
1177
                    'inactif' => 0,
1178
                    'restricted_to' => '',
1179
                    'perso' => $post_folders[$item['parentFolderId']]['isPF'] === true ? 1 : 0,
1180
                    'item_key' => uniqidReal(50),
1181
                    'created_at' => time(),
1182
                )
1183
            );
1184
            $newId = DB::insertId();
1185
1186
            // Create sharekeys for users
1187
            storeUsersShareKey(
1188
                prefixTable('sharekeys_items'),
1189
                $post_folders[$item['parentFolderId']]['isPF'] === true ? 1 : 0,
1190
                (int) $newId,
1191
                $cryptedStuff['objectKey'],
1192
            );
1193
1194
            //if asked, anyone in role can modify
1195
            if ($$inputData['editRole'] === 1) {
1196
                foreach ($session->get('system-array_roles') as $role) {
1197
                    DB::insert(
1198
                        prefixTable('restriction_to_roles'),
1199
                        array(
1200
                            'role_id' => $role['id'],
1201
                            'item_id' => $newId,
1202
                        )
1203
                    );
1204
                }
1205
            }
1206
1207
            //Add log
1208
            DB::insert(
1209
                prefixTable('log_items'),
1210
                array(
1211
                    'id_item' => $newId,
1212
                    'date' => time(),
1213
                    'id_user' => $session->get('user-id'),
1214
                    'action' => 'at_creation',
1215
                    'raison' => 'at_import',
1216
                )
1217
            );
1218
            
1219
            // Add item to cache table
1220
            updateCacheTable('add_value', (int) $newId);
1221
1222
            // prepare return
1223
            $ret .= "<li>".substr(stripslashes($item['Title']), 0, 500)." [".$destinationFolderMore['title']."]</li>";
1224
        }
1225
1226
        // Commit transaction.
1227
        DB::commit();
1228
1229
        echo prepareExchangedData(
1230
            array(
1231
                'error' => false,
1232
                'message' => '',
1233
                'info' => "<ul>".$ret."</ul>",
1234
            ),
1235
            'encode'
1236
        );
1237
1238
        break;
1239
    }
1240
1241
1242
1243
/**
1244
 * Create folders during importation
1245
 *
1246
 * @param string $folderTitle
1247
 * @param integer $parentId
1248
 * @param integer $folderLevel
1249
 * @param integer $startPathLevel
1250
 * @param integer $levelPwComplexity
1251
 * @return integer
1252
 */
1253
function createFolder($folderTitle, $parentId, $folderLevel, $startPathLevel, $levelPwComplexity)
1254
{
1255
    $session = SessionManager::getSession();
1256
    //create folder - if not exists at the same level
1257
    DB::query(
1258
        'SELECT * FROM '.prefixTable('nested_tree').'
1259
        WHERE nlevel = %i AND title = %s AND parent_id = %i LIMIT 1',
1260
        intval($folderLevel + $startPathLevel),
1261
        $folderTitle,
1262
        $parentId
1263
    );
1264
    if (DB::count() === 0) {
1265
        //do query
1266
        DB::insert(
1267
            prefixTable('nested_tree'),
1268
            array(
1269
                'parent_id' => $parentId,
1270
                'title' => stripslashes($folderTitle),
1271
                'nlevel' => $folderLevel,
1272
                'categories' => '',
1273
            )
1274
        );
1275
        $id = DB::insertId();
1276
        //Add complexity level => level is set to "medium" by default.
1277
        DB::insert(
1278
            prefixTable('misc'),
1279
            array(
1280
                'type' => 'complex',
1281
                'intitule' => $id,
1282
                'valeur' => $levelPwComplexity,
1283
                'created_at' => time(),
1284
            )
1285
        );
1286
1287
        // Indicate that a change has been done to force tree user reload
1288
        DB::update(
1289
            prefixTable('misc'),
1290
            array(
1291
                'valeur' => time(),
1292
                'updated_at' => time(),
1293
            ),
1294
            'type = %s AND intitule = %s',
1295
            'timestamp',
1296
            'last_folder_change'
1297
        );
1298
1299
        //For each role to which the user depends on, add the folder just created.
1300
        foreach ($session->get('system-array_roles') as $role) {
1301
            DB::insert(
1302
                prefixTable('roles_values'),
1303
                array(
1304
                    'role_id' => $role['id'],
1305
                    'folder_id' => $id,
1306
                    'type' => 'W',
1307
                )
1308
            );
1309
        }
1310
1311
        //Add this new folder to the list of visible folders for the user.
1312
        $session->set('user-accessible_folders', array_unique(array_merge($session->get('user-accessible_folders'), [$id]), SORT_NUMERIC));
1313
1314
        return $id;
1315
    }
1316
    
1317
    //get folder actual ID
1318
    $data = DB::queryFirstRow(
1319
        'SELECT id FROM '.prefixTable('nested_tree').'
1320
        WHERE nlevel = %i AND title = %s AND parent_id = %i',
1321
        intval($folderLevel + $startPathLevel),
1322
        $folderTitle,
1323
        $parentId
1324
    );
1325
    return $data['id'];
1326
}
1327
1328
/** 
1329
 * getFolderComplexity
1330
 * 
1331
 * @param int $folderId
1332
 * @param boolean $isFolderPF
1333
 * 
1334
 * @return array
1335
*/
1336
function getFolderComplexity($folderId, $isFolderPF)
1337
{
1338
    // If destination is not ROOT then get the complexity level
1339
    if ($isFolderPF === true) {
1340
        return [
1341
            'levelPwComplexity' => 50,
1342
            'startPathLevel' => 1,
1343
            'importPF' => true
1344
        ];
1345
    } elseif ($folderId > 0) {
1346
        $data = DB::queryFirstRow(
1347
            'SELECT m.valeur as value, t.nlevel as nlevel
1348
            FROM '.prefixTable('misc').' as m
1349
            INNER JOIN '.prefixTable('nested_tree').' as t ON (m.intitule = t.id)
1350
            WHERE m.type = %s AND m.intitule = %s',
1351
            'complex',
1352
            $folderId
1353
        );
1354
        return [
1355
            'levelPwComplexity' => $data['value'],
1356
            'startPathLevel' => $data['nlevel'],
1357
            'importPF' => false
1358
        ];
1359
    }
1360
    return [
1361
        'levelPwComplexity' => 50,
1362
        'startPathLevel' => 0,
1363
        'importPF' => false
1364
    ];
1365
}
1366
1367
spl_autoload_register(function ($class) {
1368
    $prefix = 'League\\Csv\\';
1369
    $base_dir = __DIR__.'/src/';
1370
    $len = strlen($prefix);
1371
    if (strncmp($prefix, $class, $len) !== 0) {
1372
        // no, move to the next registered autoloader
1373
        return;
1374
    }
1375
    $relative_class = substr($class, $len);
1376
    $file = $base_dir.str_replace('\\', '/', $relative_class).'.php';
1377
    if (file_exists($file)) {
1378
        require $file;
1379
    }
1380
});
1381
1382
/**
1383
 * Used to format the string ready for insertion in to the database.
1384
 *
1385
 * @param string $str             String to clean
1386
 * @param string $crLFReplacement Replacement
1387
 *
1388
 * @return string
1389
 */
1390
function sanitiseString($str, $crLFReplacement)
1391
{
1392
    $str = preg_replace('#[\r\n]#', $crLFReplacement, (string) $str);
1393
    $str = str_replace('\\', '&#92;', $str);
1394
    $str = str_replace('"', '&quot;', $str);
1395
    if (!empty($str)) {
1396
        addslashes($str);
1397
    }
1398
1399
    return $str;
1400
}
1401
1402
/**
1403
 * Clean array values.
1404
 *
1405
 * @param string $value String to clean
1406
 *
1407
 * @return string
1408
 */
1409
function cleanOutput(&$value)
1410
{
1411
    return htmlspecialchars_decode($value);
1412
}
1413
1414
/**
1415
 * Clean a string.
1416
 * 
1417
 * @param string $value The string to clean.
1418
 * 
1419
 * @return string The cleaned string.
1420
 */
1421
function cleanInput($value): string
1422
{
1423
    return stripEmojis(
1424
        cleanString(
1425
            html_entity_decode($value, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
1426
            true
1427
        )
1428
    );
1429
}
1430
1431
/**
1432
 * Strip any emoji icons from the string.
1433
 *
1434
 * @param string $string
1435
 *   The string to remove emojis from.
1436
 *
1437
 * @return string
1438
 *   The string with emojis removed.
1439
 */
1440
function stripEmojis($string): string
1441
{
1442
    // Convert question marks to a special thing so that we can remove
1443
    // question marks later without any problems.
1444
    $string = str_replace("?", "{%}", $string);
1445
    // Convert the text into UTF-8.
1446
    $string = mb_convert_encoding($string, "ISO-8859-1", "UTF-8");
1447
    // Convert the text to ASCII.
1448
    $string = mb_convert_encoding($string, "UTF-8", "ISO-8859-1");
1449
    // Replace anything that is a question mark (left over from the conversion.
1450
    $string = preg_replace('/(\s?\?\s?)/', ' ', $string);
1451
    // Put back the .
1452
    $string = str_replace("{%}", "?", $string);
1453
    // Trim and return.
1454
    return trim($string);
1455
  }
1456