Passed
Push — master ( 2b951a...bd06e7 )
by Nils
06:45
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
        $folderComplexity = DB::queryFirstRow(
326
            'SELECT valeur
327
            FROM ' . prefixTable('misc') . '
328
            WHERE type = %s AND intitule = %i',
329
            'complex',
330
            $itemInfos['folderId']
331
        );
332
333
        $requested_folder_complexity = $folderComplexity !== null ? (int) $folderComplexity['valeur'] : 0;
334
335
        $zxcvbn = new Zxcvbn();
336
        $passwordStrength = $zxcvbn->passwordStrength($password);
337
        $passwordStrengthScore = convertPasswordStrength($passwordStrength['score']);
338
339
        if ($passwordStrengthScore < $requested_folder_complexity && (int) $itemInfos['no_complex_check_on_creation'] === 0) {
340
            throw new Exception('Password strength is too low');
341
        }
342
    }
343
344
    /**
345
     * Checks if an item with the same label already exists in the folder.
346
     * Throws an exception if duplicates are not allowed.
347
     * @param string $label - The label of the item to check for duplicates
348
     * @param array $SETTINGS - Global settings for duplicate items
349
     * @param array $itemInfos - Folder-specific settings
350
     * @throws Exception - If a duplicate item is found and not allowed
351
     */
352
    private function checkForDuplicates(string $label, array $SETTINGS, array $itemInfos) : void
353
    {
354
        DB::queryFirstRow(
355
            'SELECT * FROM ' . prefixTable('items') . '
356
            WHERE label = %s AND inactif = %i',
357
            $label,
358
            0
359
        );
360
361
        if (DB::count() > 0 && (
362
	     (isset($SETTINGS['duplicate_item']) && (int) $SETTINGS['duplicate_item'] === 0)
363
	     && (int) $itemInfos['personal_folder'] === 0)
364
        ) {
365
            throw new Exception('Similar item already exists. Duplicates are not allowed.');
366
        }
367
    }
368
369
    /**
370
     * Encrypts the password using the system's encryption function.
371
     * Returns an array containing both the encrypted password and the encryption key.
372
     * @param string $password - The password to encrypt
373
     * @return array - Returns the encrypted password and the encryption key
374
     */
375
    private function encryptPassword(string $password) : array
376
    {
377
        $cryptedStuff = doDataEncryption($password);
378
        return [
379
            'encrypted' => $cryptedStuff['encrypted'],
380
            'passwordKey' => $cryptedStuff['objectKey'],
381
        ];
382
    }
383
384
    /**
385
     * Inserts the new item into the database with all its associated data.
386
     * @param array $data - The item data to insert
387
     * @param string $password - The encrypted password
388
     * @param array $itemInfos - Folder-specific settings
389
     * @return int - Returns the ID of the newly created item
390
     */
391
    private function insertNewItem(array $data, string $password, array $itemInfos) : int
392
    {
393
        include_once API_ROOT_PATH . '/../sources/main.functions.php';
394
395
        DB::insert(
396
            prefixTable('items'),
397
            [
398
                'label' => $data['label'],
399
                'description' => $data['description'],
400
                'pw' => $password,
401
                'pw_iv' => '',
402
                'pw_len' => strlen($data['password']),
403
                'email' => $data['email'],
404
                'url' => $data['url'],
405
                'id_tree' => $data['folderId'],
406
                'login' => $data['login'],
407
                'inactif' => 0,
408
                'restricted_to' => '',
409
                'perso' => $itemInfos['personal_folder'],
410
                'anyone_can_modify' => $data['anyoneCanModify'],
411
                'complexity_level' => $this->getPasswordComplexityLevel($password),
412
                'encryption_type' => 'teampass_aes',
413
                'fa_icon' => $data['icon'],
414
                'item_key' => uniqidReal(50),
415
                'created_at' => time(),
416
            ]
417
        );
418
419
        $newItemId = DB::insertId();
420
421
        // Handle TOTP if provided
422
        if (empty($data['totp']) === true) {
423
            $encryptedSecret = cryption(
424
                $data['totp'],
425
                '',
426
                'encrypt'
427
            );
428
429
            DB::insert(
430
                prefixTable('items_otp'),
431
                array(
432
                    'item_id' => $newItemId,
433
                    'secret' => $encryptedSecret['string'],
434
                    'phone_number' => '',
435
                    'timestamp' => time(),
436
                    'enabled' => 1,
437
                )
438
            );
439
        }
440
441
        return $newItemId;
442
    }
443
444
    /**
445
     * Determines the complexity level of a password based on its strength score.
446
     * @param string $password - The encrypted password for which complexity is being evaluated
447
     * @return int - Returns the complexity level (0 for weak, higher numbers for stronger passwords)
448
     */
449
    private function getPasswordComplexityLevel(string $password) : int
450
    {
451
        $zxcvbn = new Zxcvbn();
452
        $passwordStrength = $zxcvbn->passwordStrength($password);
453
        return convertPasswordStrength($passwordStrength['score']);
454
    }
455
456
    /**
457
     * Handles tasks that need to be performed after the item is inserted:
458
     * 1. Stores sharing keys
459
     * 2. Logs the item creation
460
     * 3. Adds a task if the folder is not personal
461
     * 4. Adds tags to the item
462
     * @param int $newID - The ID of the newly created item
463
     * @param array $itemInfos - Folder-specific settings
464
     * @param int $folderId - Folder ID of the item
465
     * @param string $passwordKey - The encryption key for the item
466
     * @param int $userId - ID of the user creating the item
467
     * @param string $username - Username of the creator
468
     * @param string $tags - Tags to be associated with the item
469
     * @param array $data - The original data used to create the item (including the label)
470
     * @param array $SETTINGS - System settings for logging and task creation
471
     */
472
    private function handlePostInsertTasks(
473
        int $newID,
474
        array $itemInfos,
475
        int $folderId,
476
        string $passwordKey,
477
        int $userId,
478
        string $username,
479
        string $tags,
480
        array $data,
481
        array $SETTINGS
482
    ) : void {
483
        // Create share keys for the creator
484
        storeUsersShareKey(
485
            'sharekeys_items',
486
            (int) $itemInfos['personal_folder'],
487
            (int) $newID,
488
            $passwordKey,
489
            true,
490
            false,
491
            [],
492
            -1,
493
            $userId
494
        );
495
496
        // Log the item creation
497
        logItems($SETTINGS, $newID, $data['label'], $userId, 'at_creation', $username);
498
499
        // Create a task if the folder is not personal
500
        if ((int) $itemInfos['personal_folder'] === 0) {
501
            storeTask('new_item', $userId, 0, $folderId, $newID, $passwordKey, [], []);
502
        }
503
504
        // Add tags to the item
505
        $this->addTags($newID, $tags);
506
    }
507
508
509
    /**
510
     * Splits the tags string into individual tags and inserts them into the database.
511
     * @param int $newID - The ID of the item to associate tags with
512
     * @param string $tags - A comma-separated string of tags
513
     */
514
    private function addTags(int $newID, string $tags) : void
515
    {
516
        $tagsArray = explode(',', $tags);
517
        foreach ($tagsArray as $tag) {
518
            if (!empty($tag)) {
519
                DB::insert(
520
                    prefixTable('tags'),
521
                    ['item_id' => $newID, 'tag' => strtolower($tag)]
522
                );
523
            }
524
        }
525
    }
526
527
528
    private function isPasswordEmptyAllowed($password, $create_item_without_password)
529
    {
530
        if (
531
            empty($password) === true
532
            && null !== $create_item_without_password
533
            && (int) $create_item_without_password !== 1
534
        ) {
535
            return true;
536
        }
537
        return false;
538
    }
539
540
    /**
541
     * Main function to update an existing item in the database.
542
     * It handles data validation, password encryption, folder permission checks,
543
     * and updates the item with the provided fields.
544
     *
545
     * @param int $itemId The ID of the item to update
546
     * @param array $params Array of parameters to update
547
     * @param array $userData User data from JWT token
548
     * @param string $userPrivateKey User's private key for encryption
549
     * @return array Returns success or error response
550
     */
551
    public function updateItem(
552
        int $itemId,
553
        array $params,
554
        array $userData,
555
        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

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