Passed
Push — master ( bc15d0...4182e3 )
by Nils
23:52 queued 18:53
created

FolderManager::createNewFolder()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 12
c 2
b 0
f 0
nc 5
nop 1
dl 0
loc 25
rs 8.8333
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      folders.class.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use TeampassClasses\NestedTree\NestedTree;
33
34
class FolderManager
35
{
36
    private $lang;
37
    private $settings;
38
39
    /**
40
     * Constructor
41
     */
42
    public function __construct($lang)
43
    {
44
        $this->lang = $lang;
45
        $this->loadSettings();
46
    }
47
48
    /**
49
     * Load settings
50
     */
51
    private function loadSettings()
52
    {
53
        try {
54
            include_once TEAMPASS_ROOT_PATH.'/includes/config/tp.config.php';
55
            $this->settings = $GLOBALS['SETTINGS'];
56
        } catch (Exception $e) {
57
            throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
58
        }
59
    }
60
61
    /**
62
     * Create a new folder
63
     *
64
     * @param array $params
65
     * @return array
66
     */
67
    public function createNewFolder(array $params): array
68
    {
69
        // Décomposer les paramètres pour une meilleure lisibilité
70
        extract($params);
71
72
        if ($this->isTitleNumeric($title)) {
73
            return $this->errorResponse($this->lang->get('error_only_numbers_in_folder_name'));
74
        }
75
76
        if (!$this->isParentFolderAllowed($parent_id, $user_accessible_folders, $user_is_admin)) {
77
            return $this->errorResponse($this->lang->get('error_folder_not_allowed_for_this_user'));
78
        }
79
80
        if (!$this->checkDuplicateFolderAllowed($title) && $personal_folder == 0) {
81
            return $this->errorResponse($this->lang->get('error_group_exist'));
82
        }
83
84
        $parentFolderData = $this->getParentFolderData($parent_id);
85
86
        $parentComplexity = $this->checkComplexityLevel($parentFolderData, $complexity, $parent_id);
87
        if (isset($parentComplexity ['error']) && $parentComplexity['error'] === true) {
88
            return $this->errorResponse($this->lang->get('error_folder_complexity_lower_than_top_folder') . " [<b>{$this->settings['TP_PW_COMPLEXITY'][$parentComplexity['valeur']][1]}</b>]");
89
        }
90
91
        return $this->createFolder($params, array_merge($parentFolderData, $parentComplexity));
92
    }
93
94
    /**
95
     * Check if title is numeric
96
     *
97
     * @param string $title
98
     * @return boolean
99
     */
100
    private function isTitleNumeric($title)
101
    {
102
        return is_numeric($title);
103
    }
104
105
    /**
106
     * Check if parent folder is allowed
107
     *
108
     * @param integer $parent_id
109
     * @param array $user_accessible_folders
110
     * @param boolean $user_is_admin
111
     * @return boolean
112
     */
113
    private function isParentFolderAllowed($parent_id, $user_accessible_folders, $user_is_admin)
114
    {
115
        if (in_array($parent_id, $user_accessible_folders) === false
116
            && (int) $user_is_admin !== 1
117
        ) {
118
            return false;
119
        }
120
        return true;
121
    }
122
123
    /**
124
     * Check if duplicate folder is allowed
125
     *
126
     * @param string $title
127
     * @return boolean
128
     */
129
    private function checkDuplicateFolderAllowed($title)
130
    {
131
        if (
132
            isset($this->settings['duplicate_folder']) === true
133
            && (int) $this->settings['duplicate_folder'] === 0
134
        ) {
135
            DB::query(
136
                'SELECT *
137
                FROM ' . prefixTable('nested_tree') . '
138
                WHERE title = %s AND personal_folder = 0',
139
                $title
140
            );
141
            $counter = DB::count();
142
            if ($counter !== 0) {
143
                return false;
144
            }
145
            return true;
146
        }
147
        return true;
148
    }
149
150
    /**
151
     * Get parent folder data
152
     *
153
     * @param integer $parent_id
154
     * @return array
155
     */
156
    private function getParentFolderData($parent_id)
157
    {
158
        //check if parent folder is personal
159
        $data = DB::queryfirstrow(
160
            'SELECT personal_folder, bloquer_creation, bloquer_modification
161
            FROM ' . prefixTable('nested_tree') . '
162
            WHERE id = %i',
163
            $parent_id
164
        );
165
166
        // inherit from parent the specific settings it has
167
        if (DB::count() > 0) {
168
            $parentBloquerCreation = $data['bloquer_creation'];
169
            $parentBloquerModification = $data['bloquer_modification'];
170
        } else {
171
            $parentBloquerCreation = 0;
172
            $parentBloquerModification = 0;
173
        }
174
175
        return [
176
            'isPersonal' => null !== $data['personal_folder'] ? $data['personal_folder'] : 0,
177
            'parentBloquerCreation' => $parentBloquerCreation,
178
            'parentBloquerModification' => $parentBloquerModification,
179
        ];
180
    }
181
182
    /**
183
     * Check complexity level
184
     *
185
     * @param array $data
186
     * @param integer $complexity
187
     * @param integer $parent_id
188
     * @return array|boolean
189
     */
190
    private function checkComplexityLevel(
191
        $data,
192
        $complexity,
193
        $parent_id
194
    )
195
    {
196
        if (isset($data) === false || (int) $data['isPersonal'] === 0) {    
197
            // get complexity level for this folder
198
            $data = DB::queryfirstrow(
199
                'SELECT valeur
200
                FROM ' . prefixTable('misc') . '
201
                WHERE intitule = %i AND type = %s',
202
                $parent_id,
203
                'complex'
204
            );
205
            if (isset($data['valeur']) === true && intval($complexity) < intval($data['valeur'])) {
206
                return [
207
                    'error' => true,
208
                ];
209
            }
210
        }
211
212
        return [
213
            'parent_complexity' => isset($data['valeur']) === true ? $data['valeur'] : 0,
214
        ];
215
    }
216
217
    /**
218
     * Create folder
219
     *
220
     * @param array $params
221
     * @param array $parentFolderData
222
     * @return array
223
     */
224
    private function createFolder($params, $parentFolderData)
225
    {
226
        extract($params);
227
        extract($parentFolderData);
228
        
229
        if (
230
            (int) $isPersonal === 1
231
            || (int) $user_is_admin === 1
232
            || ((int) $user_is_manager === 1 || (int) $user_can_manage_all_users === 1)
233
            || (isset($SETTINGS['enable_user_can_create_folders']) === true
234
                && (int) $SETTINGS['enable_user_can_create_folders'] == 1)
235
            || ((int) $user_can_create_root_folder && null !== $user_can_create_root_folder && (int) $user_can_create_root_folder === 1)
236
        ) {
237
            //create folder
238
            DB::insert(
239
                prefixTable('nested_tree'),
240
                array(
241
                    'parent_id' => $parent_id,
242
                    'title' => $title,
243
                    'personal_folder' => null !== $isPersonal ? $isPersonal : 0,
244
                    'renewal_period' => isset($duration) === true && (int) $duration !== 0 ? $duration : 0,
245
                    'bloquer_creation' => isset($create_auth_without) === true && (int) $create_auth_without === 1 ? '1' : $parentBloquerCreation,
246
                    'bloquer_modification' => isset($edit_auth_without) === true && (int) $edit_auth_without === 1 ? '1' : $parentBloquerModification,
247
                    'fa_icon' => empty($icon) === true ? TP_DEFAULT_ICON : $icon,
248
                    'fa_icon_selected' => empty($icon_selected) === true ? TP_DEFAULT_ICON_SELECTED : $icon_selected,
249
                    'categories' => '',
250
                )
251
            );
252
            $newId = DB::insertId();
253
    
254
            //Add complexity
255
            DB::insert(
256
                prefixTable('misc'),
257
                array(
258
                    'type' => 'complex',
259
                    'intitule' => $newId,
260
                    'valeur' => $complexity,
261
                    'created_at' => time(),
262
                )
263
            );
264
    
265
            // ensure categories are set
266
            handleFoldersCategories(
267
                [$newId]
268
            );
269
    
270
            // Update timestamp
271
            DB::update(
272
                prefixTable('misc'),
273
                array(
274
                    'valeur' => time(),
275
                    'updated_at' => time(),
276
                ),
277
                'type = %s AND intitule = %s',
278
                'timestamp',
279
                'last_folder_change'
280
            );
281
    
282
            // Load tree
283
            $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
284
    
285
            // rebuild tree
286
            $tree->rebuild();
287
    
288
    
289
            // --> build json tree if not Admin
290
            if ($user_is_admin === 0) {
291
                // Get path
292
                $path = '';
293
                $tree_path = $tree->getPath(0, false);
294
                foreach ($tree_path as $fld) {
295
                    $path .= empty($path) === true ? $fld->title : '/'.$fld->title;
296
                }
297
                $new_json = [
298
                    "path" => $path,
299
                    "id" => $newId,
300
                    "level" => count($tree_path),
301
                    "title" => $title,
302
                    "disabled" => 0,
303
                    "parent_id" => $parent_id,
304
                    "perso" => $isPersonal,
305
                    "is_visible_active" => 0,
306
                ];
307
    
308
                // update cache_tree
309
                $cache_tree = DB::queryfirstrow(
310
                    'SELECT increment_id, folders, visible_folders
311
                    FROM ' . prefixTable('cache_tree').' WHERE user_id = %i',
312
                    (int) $user_id
313
                );
314
                if (empty($cache_tree) === true) {
315
                    DB::insert(
316
                        prefixTable('cache_tree'),
317
                        array(
318
                            'user_id' => $user_id,
319
                            'folders' => json_encode($newId),
320
                            'visible_folders' => json_encode($new_json),
321
                            'timestamp' => time(),
322
                            'data' => '[{}]',
323
                        )
324
                    );
325
                } else {
326
                    $a_folders = is_null($cache_tree['folders']) === true ? [] : json_decode($cache_tree['folders'], true);
327
                    array_push($a_folders, $newId);
328
                    $a_visible_folders = is_null($cache_tree['visible_folders']) === true || empty($cache_tree['visible_folders']) === true ? [] : json_decode($cache_tree['visible_folders'], true);
329
                    array_push($a_visible_folders, $new_json);
330
                    DB::update(
331
                        prefixTable('cache_tree'),
332
                        array(
333
                            'folders' => json_encode($a_folders),
334
                            'visible_folders' => json_encode($a_visible_folders),
335
                            'timestamp' => time(),
336
                        ),
337
                        'increment_id = %i',
338
                        (int) $cache_tree['increment_id']
339
                    );
340
                }
341
            }
342
            // <-- end - build json tree
343
    
344
            // Create expected groups access rights based upon option selected
345
            if (
346
                isset($SETTINGS['subfolder_rights_as_parent']) === true
347
                && (int) $SETTINGS['subfolder_rights_as_parent'] === 1
348
            ) {
349
                //If it is a subfolder, then give access to it for all roles that allows the parent folder
350
                $rows = DB::query('SELECT role_id, type FROM ' . prefixTable('roles_values') . ' WHERE folder_id = %i', $parent_id);
351
                foreach ($rows as $record) {
352
                    //add access to this subfolder
353
                    DB::insert(
354
                        prefixTable('roles_values'),
355
                        array(
356
                            'role_id' => $record['role_id'],
357
                            'folder_id' => $newId,
358
                            'type' => $record['type'],
359
                        )
360
                    );
361
                }
362
            } elseif ((int) $user_is_admin !== 1) {
363
                // If not admin and no option enabled
364
                // then provide expected rights based upon user's roles
365
                foreach (array_unique(explode(';', $user_roles)) as $role) {
366
                    if (empty($role) === false) {
367
                        DB::insert(
368
                            prefixTable('roles_values'),
369
                            array(
370
                                'role_id' => $role,
371
                                'folder_id' => $newId,
372
                                'type' => $access_rights,
373
                            )
374
                        );
375
                    }
376
                }
377
            }
378
    
379
            // if parent folder has Custom Fields Categories then add to this child one too
380
            $rows = DB::query('SELECT id_category FROM ' . prefixTable('categories_folders') . ' WHERE id_folder = %i', $parent_id);
381
            foreach ($rows as $record) {
382
                //add CF Category to this subfolder
383
                DB::insert(
384
                    prefixTable('categories_folders'),
385
                    array(
386
                        'id_category' => $record['id_category'],
387
                        'id_folder' => $newId,
388
                    )
389
                );
390
            }
391
    
392
            // clear cache cache for each user that have at least one similar role as the current user
393
            $usersWithSimilarRoles = empty($user_roles) === false  ? getUsersWithRoles(
394
                explode(";", $user_roles)
395
            ) : [];
396
            foreach ($usersWithSimilarRoles as $user) {
397
                // delete cache tree
398
                DB::delete(
399
                    prefixTable('cache_tree'),
400
                    'user_id = %i',
401
                    $user
402
                );
403
            }
404
            return array(
405
                'error' => false,
406
                'newId' => $newId,
407
            );
408
    
409
        } else {
410
            return array(
411
                'error' => true,
412
                'newId' => $newId,
413
            );
414
        }
415
    }
416
417
    private function errorResponse($message, $newIdSuffix = "")
418
    {
419
        return [
420
            'error' => true,
421
            'message' => $message,
422
            'newId' => '' . $newIdSuffix,
423
        ];
424
    }
425
}