Passed
Push — master ( 378303...213640 )
by Nils
06:34
created

ItemModel::handlePostInsertTasks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 34
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
c 0
b 0
f 0
nc 2
nop 9
dl 0
loc 34
rs 9.8666

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Teampass - a collaborative passwords manager.
4
 * ---
5
 * This file is part of the TeamPass project.
6
 * 
7
 * TeamPass is free software: you can redistribute it and/or modify it
8
 * under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, version 3 of the License.
10
 * 
11
 * TeamPass is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 * 
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 * 
19
 * Certain components of this file may be under different licenses. For
20
 * details, see the `licenses` directory or individual file headers.
21
 * ---
22
 * @version    API
23
 *
24
 * @file      ItemModel.php
25
 * @author    Nils Laumaillé ([email protected])
26
 * @copyright 2009-2025 Teampass.net
27
 * @license   GPL-3.0
28
 * @see       https://www.teampass.net
29
 */
30
31
use TeampassClasses\NestedTree\NestedTree;
32
use TeampassClasses\ConfigManager\ConfigManager;
33
use ZxcvbnPhp\Zxcvbn;
34
35
class ItemModel
36
{
37
38
    /**
39
     * Get the list of items to return
40
     *
41
     * @param string $sqlExtra
42
     * @param integer $limit
43
     * @param string $userPrivateKey
44
     * @param integer $userId
45
     * 
46
     * @return array
47
     */
48
    public function getItems(string $sqlExtra, int $limit, string $userPrivateKey, int $userId): array
49
    {
50
        // Get items
51
        $rows = DB::query(
52
            'SELECT i.id, label, description, i.pw, i.url, i.id_tree, i.login, i.email, i.viewed_no, i.fa_icon, i.inactif, i.perso,
53
            t.title as folder_label, io.secret as otp_secret
54
            FROM ' . prefixTable('items') . ' AS i
55
            LEFT JOIN '.prefixTable('nested_tree').' as t ON (t.id = i.id_tree) 
56
            LEFT JOIN '.prefixTable('items_otp').' as io ON (io.item_id = i.id) '.
57
            $sqlExtra . 
58
            " ORDER BY i.id ASC" .
59
            ($limit > 0 ? " LIMIT ". $limit : '')
60
        );
61
        
62
        $ret = [];
63
        foreach ($rows as $row) {
64
            $userKey = DB::queryfirstrow(
65
                'SELECT share_key
66
                FROM ' . prefixTable('sharekeys_items') . '
67
                WHERE user_id = %i AND object_id = %i',
68
                $userId,
69
                $row['id']
70
            );
71
            if (DB::count() === 0 || empty($row['pw']) === true) {
72
                // No share key found
73
                // Exit this item
74
                continue;
75
            }
76
77
            // Get password
78
            try {
79
                $pwd = base64_decode(
80
                    (string) doDataDecryption(
81
                        $row['pw'],
82
                        decryptUserObjectKey(
83
                            $userKey['share_key'],
84
                            $userPrivateKey
85
                        )
86
                    )
87
                );
88
            } catch (Exception $e) {
89
                // Password is not encrypted
90
                // deepcode ignore ServerLeak: No important data
91
                echo "ERROR";
92
            }
93
            
94
95
            // get path to item
96
            $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
97
            $arbo = $tree->getPath($row['id_tree'], false);
98
            $path = '';
99
            foreach ($arbo as $elem) {
100
                if (empty($path) === true) {
101
                    $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
102
                } else {
103
                    $path .= '/' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
104
                }
105
            }
106
107
            // Get TOTP
108
            if (empty($row['otp_secret']) === false) {
109
                $decryptedTotp = cryption(
110
                    $row['otp_secret'],
111
                    '',
112
                    'decrypt'
113
                );
114
                $row['otp_secret'] = $decryptedTotp['string'];
115
            }
116
117
            array_push(
118
                $ret,
119
                [
120
                    'id' => (int) $row['id'],
121
                    'label' => $row['label'],
122
                    'description' => $row['description'],
123
                    'pwd' => $pwd,
124
                    'url' => $row['url'],
125
                    'login' => $row['login'],
126
                    'email' => $row['email'],
127
                    'viewed_no' => (int) $row['viewed_no'],
128
                    'fa_icon' => $row['fa_icon'],
129
                    'inactif' => (int) $row['inactif'],
130
                    'perso' => (int) $row['perso'],
131
                    'id_tree' => (int) $row['id_tree'],
132
                    'folder_label' => $row['folder_label'],
133
                    'path' => empty($path) === true ? '' : $path,
134
                    'totp' => $row['otp_secret'],
135
                ]
136
            );
137
        }
138
139
        return $ret;
140
    }
141
    //end getItems() 
142
143
    /**
144
     * Main function to add a new item to the database.
145
     * It handles data preparation, validation, password checks, folder settings,
146
     * item creation, and post-insertion tasks (like logging, sharing, and tagging).
147
     */
148
    public function addItem(
149
        int $folderId,
150
        string $label,
151
        string $password,
152
        string $description,
153
        string $login,
154
        string $email,
155
        string $url,
156
        string $tags,
157
        string $anyone_can_modify,
158
        string $icon,
159
        int $userId,
160
        string $username,
161
        string $totp
162
    ) : array
163
    {
164
        try {
165
            include_once API_ROOT_PATH . '/../sources/main.functions.php';
166
167
            // Load config
168
            $configManager = new ConfigManager();
169
            $SETTINGS = $configManager->getAllSettings();
170
171
            // Step 1: Prepare data and sanitize inputs
172
            $data = $this->prepareData($folderId, $label, $password, $description, $login, $email, $url, $tags, $anyone_can_modify, $icon, $totp);
173
            $this->validateData($data); // Step 2: Validate the data
174
175
            // Step 3: Validate password rules (length, emptiness)
176
            $this->validatePassword($password, $SETTINGS);
177
178
            // Step 4: Check folder settings for permission checks
179
            $itemInfos = $this->getFolderSettings($folderId);
180
181
            // Step 5: Ensure the password meets folder complexity requirements
182
            $this->checkPasswordComplexity($password, $itemInfos);
183
184
            // Step 6: Check for duplicates in the system
185
            $this->checkForDuplicates($label, $SETTINGS, $itemInfos);
186
187
            // Step 7: Encrypt password if provided
188
            $cryptedData = $this->encryptPassword($password);
189
            $passwordKey = $cryptedData['passwordKey'];
190
            $password = $cryptedData['encrypted'];
191
192
            // Step 8: Insert the new item into the database
193
            $newID = $this->insertNewItem($data, $password, $itemInfos);
194
195
            // Step 9: Handle post-insert tasks (logging, sharing, tagging)
196
            $this->handlePostInsertTasks($newID, $itemInfos, $folderId, $passwordKey, $userId, $username, $tags, $data, $SETTINGS);
197
198
            // Success response
199
            return [
200
                'error' => false,
201
                'message' => 'Item added successfully',
202
                'newId' => $newID,
203
            ];
204
205
        } catch (Exception $e) {
206
            // Error response
207
            return [
208
                'error' => true,
209
                'error_header' => 'HTTP/1.1 422 Unprocessable Entity',
210
                'error_message' => $e->getMessage(),
211
            ];
212
        }
213
    }
214
215
    /**
216
     * Prepares the data array for processing by combining all inputs.
217
     * @param int $folderId - Folder ID where the item is stored
218
     * @param string $label - Label or title of the item
219
     * @param string $password - Password associated with the item
220
     * @param string $description - Description of the item
221
     * @param string $login - Login associated with the item
222
     * @param string $email - Email linked to the item
223
     * @param string $url - URL for the item
224
     * @param string $tags - Tags for categorizing the item
225
     * @param string $anyone_can_modify - Permission to allow modifications by others
226
     * @param string $icon - Icon representing the item
227
     * @param string $totp - Token for OTP
228
     * @return array - Returns the prepared data
229
     */
230
    private function prepareData(
231
        int $folderId, string $label, string $password, string $description, string $login, 
232
        string $email, string $url, string $tags, string $anyone_can_modify, string $icon, string $totp
233
    ) : array {
234
        return [
235
            'folderId' => $folderId,
236
            'label' => $label,
237
            'password' => $password,
238
            'description' => $description,
239
            'login' => $login,
240
            'email' => $email,
241
            'tags' => $tags,
242
            'anyoneCanModify' => $anyone_can_modify,
243
            'url' => $url,
244
            'icon' => $icon,
245
            'totp' => $totp,
246
        ];
247
    }
248
249
    /**
250
     * Sanitizes and validates the input data according to specified filters.
251
     * If validation fails, throws an exception.
252
     * @param array $data - Data to be validated and sanitized
253
     * @throws Exception - If the data is invalid
254
     */
255
    private function validateData(array $data) : void
256
    {
257
        $filters = [
258
            'folderId' => 'cast:integer',
259
            'label' => 'trim|escape',
260
            'password' => 'trim|escape',
261
            'description' => 'trim|escape',
262
            'login' => 'trim|escape',
263
            'email' => 'trim|escape',
264
            'tags' => 'trim|escape',
265
            'anyoneCanModify' => 'trim|escape',
266
            'url' => 'trim|escape',
267
            'icon' => 'trim|escape',
268
            'totp' => 'trim|escape',
269
        ];
270
271
        $inputData = dataSanitizer($data, $filters);
272
        if (is_string($inputData)) {
273
            throw new Exception('Data is not valid');
274
        }
275
    }
276
277
    /**
278
     * Validates the password against length and empty password rules.
279
     * Throws an exception if validation fails.
280
     * @param string $password - The password to validate
281
     * @param array $SETTINGS - Global settings from configuration
282
     * @throws Exception - If the password is invalid
283
     */
284
    private function validatePassword(string $password, array $SETTINGS) : void
285
    {
286
        if ($this->isPasswordEmptyAllowed($password, $SETTINGS['create_item_without_password'])) {
287
            throw new Exception('Empty password is not allowed');
288
        }
289
290
        if (strlen($password) > $SETTINGS['pwd_maximum_length']) {
291
            throw new Exception('Password is too long (max allowed is ' . $SETTINGS['pwd_maximum_length'] . ' characters)');
292
        }
293
    }
294
295
    /**
296
     * Retrieves folder-specific settings, including permission to modify and create items.
297
     * @param int $folderId - The folder ID to fetch settings for
298
     * @return array - Returns an array with folder-specific permissions
299
     */
300
    private function getFolderSettings(int $folderId) : array
301
    {
302
        $dataFolderSettings = DB::queryFirstRow(
303
            'SELECT bloquer_creation, bloquer_modification, personal_folder
304
            FROM ' . prefixTable('nested_tree') . ' 
305
            WHERE id = %i',
306
            $folderId
307
        );
308
309
        return [
310
            'personal_folder' => $dataFolderSettings['personal_folder'],
311
            'no_complex_check_on_modification' => (int) $dataFolderSettings['personal_folder'] === 1 ? 1 : (int) $dataFolderSettings['bloquer_modification'],
312
            'no_complex_check_on_creation' => (int) $dataFolderSettings['personal_folder'] === 1 ? 1 : (int) $dataFolderSettings['bloquer_creation'],
313
        ];
314
    }
315
316
    /**
317
     * Validates that the password meets the complexity requirements of the folder.
318
     * Throws an exception if the password is too weak.
319
     * @param string $password - The password to check
320
     * @param array $itemInfos - Folder settings including password complexity requirements
321
     * @throws Exception - If the password complexity is insufficient
322
     */
323
    private function checkPasswordComplexity(string $password, array $itemInfos) : void
324
    {
325
        if ($itemInfos['folderId'] <= 0 || is_int($itemInfos['folderId']) === false || isset($itemInfos['folderId']) === false) {
326
            throw new Exception('Invalid folder ID for complexity check');
327
        }
328
        
329
        $folderComplexity = DB::queryFirstRow(
330
            'SELECT valeur
331
            FROM ' . prefixTable('misc') . '
332
            WHERE type = %s AND intitule = %i',
333
            'complex',
334
            $itemInfos['folderId']
335
        );
336
337
        $requested_folder_complexity = $folderComplexity !== null ? (int) $folderComplexity['valeur'] : 0;
338
339
        $zxcvbn = new Zxcvbn();
340
        $passwordStrength = $zxcvbn->passwordStrength($password);
341
        $passwordStrengthScore = convertPasswordStrength($passwordStrength['score']);
342
343
        if ($passwordStrengthScore < $requested_folder_complexity && (int) $itemInfos['no_complex_check_on_creation'] === 0) {
344
            throw new Exception('Password strength is too low');
345
        }
346
    }
347
348
    /**
349
     * Checks if an item with the same label already exists in the folder.
350
     * Throws an exception if duplicates are not allowed.
351
     * @param string $label - The label of the item to check for duplicates
352
     * @param array $SETTINGS - Global settings for duplicate items
353
     * @param array $itemInfos - Folder-specific settings
354
     * @throws Exception - If a duplicate item is found and not allowed
355
     */
356
    private function checkForDuplicates(string $label, array $SETTINGS, array $itemInfos) : void
357
    {
358
        DB::queryFirstRow(
359
            'SELECT * FROM ' . prefixTable('items') . '
360
            WHERE label = %s AND inactif = %i',
361
            $label,
362
            0
363
        );
364
365
        if (DB::count() > 0 && (
366
	     (isset($SETTINGS['duplicate_item']) && (int) $SETTINGS['duplicate_item'] === 0)
367
	     && (int) $itemInfos['personal_folder'] === 0)
368
        ) {
369
            throw new Exception('Similar item already exists. Duplicates are not allowed.');
370
        }
371
    }
372
373
    /**
374
     * Encrypts the password using the system's encryption function.
375
     * Returns an array containing both the encrypted password and the encryption key.
376
     * @param string $password - The password to encrypt
377
     * @return array - Returns the encrypted password and the encryption key
378
     */
379
    private function encryptPassword(string $password) : array
380
    {
381
        $cryptedStuff = doDataEncryption($password);
382
        return [
383
            'encrypted' => $cryptedStuff['encrypted'],
384
            'passwordKey' => $cryptedStuff['objectKey'],
385
        ];
386
    }
387
388
    /**
389
     * Inserts the new item into the database with all its associated data.
390
     * @param array $data - The item data to insert
391
     * @param string $password - The encrypted password
392
     * @param array $itemInfos - Folder-specific settings
393
     * @return int - Returns the ID of the newly created item
394
     */
395
    private function insertNewItem(array $data, string $password, array $itemInfos) : int
396
    {
397
        include_once API_ROOT_PATH . '/../sources/main.functions.php';
398
399
        DB::insert(
400
            prefixTable('items'),
401
            [
402
                'label' => $data['label'],
403
                'description' => $data['description'],
404
                'pw' => $password,
405
                'pw_iv' => '',
406
                'pw_len' => strlen($data['password']),
407
                'email' => $data['email'],
408
                'url' => $data['url'],
409
                'id_tree' => $data['folderId'],
410
                'login' => $data['login'],
411
                'inactif' => 0,
412
                'restricted_to' => '',
413
                'perso' => $itemInfos['personal_folder'],
414
                'anyone_can_modify' => $data['anyoneCanModify'],
415
                'complexity_level' => $this->getPasswordComplexityLevel($password),
416
                'encryption_type' => 'teampass_aes',
417
                'fa_icon' => $data['icon'],
418
                'item_key' => uniqidReal(50),
419
                'created_at' => time(),
420
            ]
421
        );
422
423
        $newItemId = DB::insertId();
424
425
        // Handle TOTP if provided
426
        if (empty($data['totp']) === true) {
427
            $encryptedSecret = cryption(
428
                $data['totp'],
429
                '',
430
                'encrypt'
431
            );
432
433
            DB::insert(
434
                prefixTable('items_otp'),
435
                array(
436
                    'item_id' => $newItemId,
437
                    'secret' => $encryptedSecret['string'],
438
                    'phone_number' => '',
439
                    'timestamp' => time(),
440
                    'enabled' => 1,
441
                )
442
            );
443
        }
444
445
        return $newItemId;
446
    }
447
448
    /**
449
     * Determines the complexity level of a password based on its strength score.
450
     * @param string $password - The encrypted password for which complexity is being evaluated
451
     * @return int - Returns the complexity level (0 for weak, higher numbers for stronger passwords)
452
     */
453
    private function getPasswordComplexityLevel(string $password) : int
454
    {
455
        $zxcvbn = new Zxcvbn();
456
        $passwordStrength = $zxcvbn->passwordStrength($password);
457
        return convertPasswordStrength($passwordStrength['score']);
458
    }
459
460
    /**
461
     * Handles tasks that need to be performed after the item is inserted:
462
     * 1. Stores sharing keys
463
     * 2. Logs the item creation
464
     * 3. Adds a task if the folder is not personal
465
     * 4. Adds tags to the item
466
     * @param int $newID - The ID of the newly created item
467
     * @param array $itemInfos - Folder-specific settings
468
     * @param int $folderId - Folder ID of the item
469
     * @param string $passwordKey - The encryption key for the item
470
     * @param int $userId - ID of the user creating the item
471
     * @param string $username - Username of the creator
472
     * @param string $tags - Tags to be associated with the item
473
     * @param array $data - The original data used to create the item (including the label)
474
     * @param array $SETTINGS - System settings for logging and task creation
475
     */
476
    private function handlePostInsertTasks(
477
        int $newID,
478
        array $itemInfos,
479
        int $folderId,
480
        string $passwordKey,
481
        int $userId,
482
        string $username,
483
        string $tags,
484
        array $data,
485
        array $SETTINGS
486
    ) : void {
487
        // Create share keys for the creator
488
        storeUsersShareKey(
489
            'sharekeys_items',
490
            (int) $itemInfos['personal_folder'],
491
            (int) $newID,
492
            $passwordKey,
493
            true,
494
            false,
495
            [],
496
            -1,
497
            $userId
498
        );
499
500
        // Log the item creation
501
        logItems($SETTINGS, $newID, $data['label'], $userId, 'at_creation', $username);
502
503
        // Create a task if the folder is not personal
504
        if ((int) $itemInfos['personal_folder'] === 0) {
505
            storeTask('new_item', $userId, 0, $folderId, $newID, $passwordKey, [], []);
506
        }
507
508
        // Add tags to the item
509
        $this->addTags($newID, $tags);
510
    }
511
512
513
    /**
514
     * Splits the tags string into individual tags and inserts them into the database.
515
     * @param int $newID - The ID of the item to associate tags with
516
     * @param string $tags - A comma-separated string of tags
517
     */
518
    private function addTags(int $newID, string $tags) : void
519
    {
520
        $tagsArray = explode(',', $tags);
521
        foreach ($tagsArray as $tag) {
522
            if (!empty($tag)) {
523
                DB::insert(
524
                    prefixTable('tags'),
525
                    ['item_id' => $newID, 'tag' => strtolower($tag)]
526
                );
527
            }
528
        }
529
    }
530
531
532
    private function isPasswordEmptyAllowed($password, $create_item_without_password)
533
    {
534
        if (
535
            empty($password) === true
536
            && null !== $create_item_without_password
537
            && (int) $create_item_without_password !== 1
538
        ) {
539
            return true;
540
        }
541
        return false;
542
    }
543
544
    /**
545
     * Main function to update an existing item in the database.
546
     * It handles data validation, password encryption, folder permission checks,
547
     * and updates the item with the provided fields.
548
     *
549
     * @param int $itemId The ID of the item to update
550
     * @param array $params Array of parameters to update
551
     * @param array $userData User data from JWT token
552
     * @param string $userPrivateKey User's private key for encryption
553
     * @return array Returns success or error response
554
     */
555
    public function updateItem(
556
        int $itemId,
557
        array $params,
558
        array $userData,
559
        string $userPrivateKey
0 ignored issues
show
Unused Code introduced by
The parameter $userPrivateKey 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

559
        /** @scrutinizer ignore-unused */ string $userPrivateKey

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...
560
    ): array
561
    {
562
        try {
563
            include_once API_ROOT_PATH . '/../sources/main.functions.php';
564
565
            // Load config
566
            $configManager = new ConfigManager();
567
            $SETTINGS = $configManager->getAllSettings();
568
569
            // Load current item data
570
            $currentItem = DB::queryFirstRow(
571
                'SELECT * FROM ' . prefixTable('items') . ' WHERE id = %i',
572
                $itemId
573
            );
574
575
            if (DB::count() === 0) {
576
                return [
577
                    'error' => true,
578
                    'error_message' => 'Item not found',
579
                    'error_header' => 'HTTP/1.1 404 Not Found',
580
                ];
581
            }
582
583
            // Prepare update data
584
            $updateData = [];
585
            $passwordKey = null;
586
            $newPassword = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $newPassword is dead and can be removed.
Loading history...
587
588
            // Handle folder_id change
589
            if (isset($params['folder_id'])) {
590
                $newFolderId = (int) $params['folder_id'];
591
592
                // Check if user has access to the new folder
593
                if (!in_array((string) $newFolderId, $userData['folders_list'], true)) {
594
                    return [
595
                        'error' => true,
596
                        'error_message' => 'Access denied to the target folder',
597
                        'error_header' => 'HTTP/1.1 403 Forbidden',
598
                    ];
599
                }
600
601
                $updateData['id_tree'] = $newFolderId;
602
            }
603
604
            // Handle label update
605
            if (isset($params['label'])) {
606
                $updateData['label'] = filter_var($params['label'], FILTER_SANITIZE_STRING);
607
            }
608
609
            // Handle description update
610
            if (isset($params['description'])) {
611
                $updateData['description'] = $params['description'];
612
            }
613
614
            // Handle login update
615
            if (isset($params['login'])) {
616
                $updateData['login'] = filter_var($params['login'], FILTER_SANITIZE_STRING);
617
            }
618
619
            // Handle email update
620
            if (isset($params['email'])) {
621
                $updateData['email'] = filter_var($params['email'], FILTER_SANITIZE_EMAIL);
622
            }
623
624
            // Handle url update
625
            if (isset($params['url'])) {
626
                $updateData['url'] = filter_var($params['url'], FILTER_SANITIZE_URL);
627
            }
628
629
            // Handle icon update
630
            if (isset($params['icon'])) {
631
                $updateData['fa_icon'] = filter_var($params['icon'], FILTER_SANITIZE_STRING);
632
            }
633
634
            // Handle anyone_can_modify update
635
            if (isset($params['anyone_can_modify'])) {
636
                $updateData['anyone_can_modify'] = (int) $params['anyone_can_modify'];
637
            }
638
639
            // Handle password update
640
            if (isset($params['password']) && !empty($params['password'])) {
641
                $newPassword = $params['password'];
642
643
                // Validate password length
644
                if (strlen($newPassword) > $SETTINGS['pwd_maximum_length']) {
645
                    return [
646
                        'error' => true,
647
                        'error_message' => 'Password is too long (max allowed is ' . $SETTINGS['pwd_maximum_length'] . ' characters)',
648
                        'error_header' => 'HTTP/1.1 400 Bad Request',
649
                    ];
650
                }
651
652
                // Get folder ID for complexity check
653
                $folderId = isset($updateData['id_tree']) ? $updateData['id_tree'] : $currentItem['id_tree'];
654
655
                // Get folder settings
656
                $itemInfos = $this->getFolderSettings((int) $folderId);
657
658
                // Check password complexity
659
                $this->checkPasswordComplexity($newPassword, array_merge($itemInfos, ['folderId' => $folderId]));
660
661
                // Encrypt password
662
                $cryptedData = $this->encryptPassword($newPassword);
663
                $passwordKey = $cryptedData['passwordKey'];
664
                $updateData['pw'] = $cryptedData['encrypted'];
665
                $updateData['pw_len'] = strlen($newPassword);
666
                $updateData['complexity_level'] = $this->getPasswordComplexityLevel($newPassword);
667
            }
668
            
669
            // Update the item
670
            if (!empty($updateData) || (isset($params['totp']) === true && empty($params['totp']) === false)) {
671
                $updateData['updated_at'] = time();
672
673
                DB::update(
674
                    prefixTable('items'),
675
                    $updateData,
676
                    'id = %i',
677
                    $itemId
678
                );
679
680
                // Handle TOTP update
681
                if (isset($params['totp']) === true && empty($params['totp']) === false) {
682
                    $encryptedSecret = cryption(
683
                        $params['totp'],
684
                        '',
685
                        'encrypt'
686
                    );
687
                    
688
                    DB::insertUpdate(
689
                        prefixTable('items_otp'),
690
                        [
691
                            'item_id' => $itemId,
692
                            'secret' => $encryptedSecret['string'],
693
                            'phone_number' => '',
694
                            'timestamp' => time(),
695
                            'enabled' => 1,
696
                        ]
697
                    );
698
                }
699
            } else if (empty($updateData)) {
700
                // Check if an entry exists for TOTP in items_otp table
701
                // IF yes delete it
702
                if (isset($params['totp']) === true && empty($params['totp']) === true) {
703
                    DB::delete(
704
                        prefixTable('items_otp'),
705
                        'item_id = %i',
706
                        $itemId
707
                    );
708
                }
709
            }
710
711
            // Handle tags update
712
            if (isset($params['tags'])) {
713
                // Delete existing tags
714
                DB::delete(
715
                    prefixTable('tags'),
716
                    'item_id = %i',
717
                    $itemId
718
                );
719
720
                // Add new tags
721
                $this->addTags($itemId, $params['tags']);
722
            }
723
724
            // If password was updated, update share keys
725
            if ($passwordKey !== null) {
726
                // Get folder ID (either new or current)
727
                $folderId = isset($updateData['id_tree']) ? $updateData['id_tree'] : $currentItem['id_tree'];
728
729
                // Get folder settings
730
                $itemInfos = $this->getFolderSettings((int) $folderId);
731
732
                // Update share keys for all users with access
733
                storeUsersShareKey(
734
                    'sharekeys_items',
735
                    (int) $itemInfos['personal_folder'],
736
                    $itemId,
737
                    $passwordKey,
738
                    false,
739
                    true,
740
                    [],
741
                    -1,
742
                    $userData['id']
743
                );
744
            }
745
746
            // Log the update
747
            $label = isset($updateData['label']) ? $updateData['label'] : $currentItem['label'];
748
            logItems($SETTINGS, $itemId, $label, $userData['id'], 'at_modification', $userData['username']);
749
750
            // Success response
751
            return [
752
                'error' => false,
753
                'message' => 'Item updated successfully',
754
                'item_id' => $itemId,
755
            ];
756
757
        } catch (Exception $e) {
758
            // Error response
759
            return [
760
                'error' => true,
761
                'error_header' => 'HTTP/1.1 500 Internal Server Error',
762
                'error_message' => $e->getMessage(),
763
            ];
764
        }
765
    }
766
767
    /**
768
     * Main function to delete an existing item in the database.
769
     *
770
     * @param int $itemId The ID of the item to delete
771
     * @param array $userData User data from JWT token
772
     * @return array Returns success or error response
773
     */
774
    public function deleteItem(
775
        int $itemId,
776
        array $userData
777
    ): array
778
    {
779
        try {
780
            include_once API_ROOT_PATH . '/../sources/main.functions.php';
781
782
            // Load config
783
            $configManager = new ConfigManager();
784
            $SETTINGS = $configManager->getAllSettings();
785
786
            // Load current item data
787
            $currentItem = DB::queryFirstRow(
788
                'SELECT * FROM ' . prefixTable('items') . ' WHERE id = %i',
789
                $itemId
790
            );
791
792
            if (DB::count() === 0) {
793
                return [
794
                    'error' => true,
795
                    'error_message' => 'Item not found',
796
                    'error_header' => 'HTTP/1.1 404 Not Found',
797
                ];
798
            }
799
800
            // delete item consists in disabling it
801
            DB::update(
802
                prefixTable('items'),
803
                array(
804
                    'inactif' => '1',
805
                    'deleted_at' => time(),
806
                ),
807
                'id = %i',
808
                $itemId
809
            );
810
811
            logItems($SETTINGS, $itemId, $currentItem['label'], $userData['id'], 'at_delete', $userData['username']);
812
813
            // Success response
814
            return [
815
                'error' => false,
816
                'message' => 'Item deleted successfully',
817
                'item_id' => $itemId,
818
            ];
819
820
        } catch (Exception $e) {
821
            // Error response
822
            return [
823
                'error' => true,
824
                'error_header' => 'HTTP/1.1 500 Internal Server Error',
825
                'error_message' => $e->getMessage(),
826
            ];
827
        }
828
    }
829
830
}
831