Passed
Push — master ( b1a0ac...a9bc2c )
by Nils
04:40
created

FolderManager::createFolder()   F

Complexity

Conditions 34
Paths 241

Size

Total Lines 188
Code Lines 122

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 34
eloc 122
c 1
b 0
f 0
nc 241
nop 2
dl 0
loc 188
rs 2.2566

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
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)) {
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, $user_is_admin, $user_is_manager, $user_can_manage_all_users);
87
        if (!$parentComplexity) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parentComplexity of type array<string,integer|mixed> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$parentComplexity is a non-empty array, thus ! $parentComplexity is always false.
Loading history...
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',
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' => $data['personal_folder'],
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
     * @param boolean $user_is_admin
189
     * @param boolean $user_is_manager
190
     * @param boolean $user_can_manage_all_users
191
     * @return array|boolean
192
     */
193
    private function checkComplexityLevel(
194
        $data,
195
        $complexity,
196
        $parent_id,
197
        $user_is_admin,
0 ignored issues
show
Unused Code introduced by
The parameter $user_is_admin 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

197
        /** @scrutinizer ignore-unused */ $user_is_admin,

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...
198
        $user_is_manager,
0 ignored issues
show
Unused Code introduced by
The parameter $user_is_manager 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

198
        /** @scrutinizer ignore-unused */ $user_is_manager,

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...
199
        $user_can_manage_all_users
0 ignored issues
show
Unused Code introduced by
The parameter $user_can_manage_all_users 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

199
        /** @scrutinizer ignore-unused */ $user_can_manage_all_users

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...
200
    )
201
    {
202
        if (isset($data) === false || (int) $data['isPersonal'] === 0) {    
203
            // get complexity level for this folder
204
            $data = DB::queryfirstrow(
205
                'SELECT valeur
206
                FROM ' . prefixTable('misc') . '
207
                WHERE intitule = %i AND type = %s',
208
                $parent_id,
209
                'complex'
210
            );
211
            if (isset($data['valeur']) === true && intval($complexity) < intval($data['valeur'])) {
212
                return false;
213
            }
214
        }
215
216
        return [
217
            'parent_complexity' => isset($data['valeur']) === true ? $data['valeur'] : 0,
218
        ];
219
    }
220
221
    /**
222
     * Create folder
223
     *
224
     * @param array $params
225
     * @param array $parentFolderData
226
     * @return array
227
     */
228
    private function createFolder($params, $parentFolderData)
229
    {
230
        extract($params);
231
        extract($parentFolderData);
232
        if (
233
            (int) $isPersonal === 1
234
            || (int) $user_is_admin === 1
235
            || ((int) $user_is_manager === 1 || (int) $user_can_manage_all_users === 1)
236
            || (isset($SETTINGS['enable_user_can_create_folders']) === true
237
                && (int) $SETTINGS['enable_user_can_create_folders'] == 1)
238
            || ((int) $user_can_create_root_folder && null !== $user_can_create_root_folder && (int) $user_can_create_root_folder === 1)
239
        ) {
240
            //create folder
241
            DB::insert(
242
                prefixTable('nested_tree'),
243
                array(
244
                    'parent_id' => $parent_id,
245
                    'title' => $title,
246
                    'personal_folder' => $isPersonal,
247
                    'renewal_period' => isset($duration) === true && (int) $duration !== 0 ? $duration : 0,
248
                    'bloquer_creation' => isset($create_auth_without) === true && (int) $create_auth_without === 1 ? '1' : $parentBloquerCreation,
249
                    'bloquer_modification' => isset($edit_auth_without) === true && (int) $edit_auth_without === 1 ? '1' : $parentBloquerModification,
250
                    'fa_icon' => empty($icon) === true ? TP_DEFAULT_ICON : $icon,
251
                    'fa_icon_selected' => empty($icon_selected) === true ? TP_DEFAULT_ICON_SELECTED : $icon_selected,
252
                    'categories' => '',
253
                )
254
            );
255
            $newId = DB::insertId();
256
    
257
            //Add complexity
258
            DB::insert(
259
                prefixTable('misc'),
260
                array(
261
                    'type' => 'complex',
262
                    'intitule' => $newId,
263
                    'valeur' => $complexity,
264
                )
265
            );
266
    
267
            // ensure categories are set
268
            handleFoldersCategories(
269
                [$newId]
270
            );
271
    
272
            // Update timestamp
273
            DB::update(
274
                prefixTable('misc'),
275
                array(
276
                    'valeur' => time(),
277
                ),
278
                'type = %s AND intitule = %s',
279
                'timestamp',
280
                'last_folder_change'
281
            );
282
    
283
            // Load tree
284
            $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
285
    
286
            // rebuild tree
287
            $tree->rebuild();
288
    
289
    
290
            // --> build json tree if not Admin
291
            if ($user_is_admin === 0) {
292
                // Get path
293
                $path = '';
294
                $tree_path = $tree->getPath(0, false);
295
                foreach ($tree_path as $fld) {
296
                    $path .= empty($path) === true ? $fld->title : '/'.$fld->title;
297
                }
298
                $new_json = [
299
                    "path" => $path,
300
                    "id" => $newId,
301
                    "level" => count($tree_path),
302
                    "title" => $title,
303
                    "disabled" => 0,
304
                    "parent_id" => $parent_id,
305
                    "perso" => $isPersonal,
306
                    "is_visible_active" => 0,
307
                ];
308
    
309
                // update cache_tree
310
                $cache_tree = DB::queryfirstrow(
311
                    'SELECT increment_id, folders, visible_folders
312
                    FROM ' . prefixTable('cache_tree').' WHERE user_id = %i',
313
                    (int) $user_id
314
                );
315
                if (empty($cache_tree) === true) {
316
                    DB::insert(
317
                        prefixTable('cache_tree'),
318
                        array(
319
                            'user_id' => $user_id,
320
                            'folders' => json_encode($newId),
321
                            'visible_folders' => json_encode($new_json),
322
                            'timestamp' => time(),
323
                            'data' => '[{}]',
324
                        )
325
                    );
326
                } else {
327
                    $a_folders = is_null($cache_tree['folders']) === true ? [] : json_decode($cache_tree['folders'], true);
328
                    array_push($a_folders, $newId);
329
                    $a_visible_folders = is_null($cache_tree['visible_folders']) === true || empty($cache_tree['visible_folders']) === true ? [] : json_decode($cache_tree['visible_folders'], true);
330
                    array_push($a_visible_folders, $new_json);
331
                    DB::update(
332
                        prefixTable('cache_tree'),
333
                        array(
334
                            'folders' => json_encode($a_folders),
335
                            'visible_folders' => json_encode($a_visible_folders),
336
                            'timestamp' => time(),
337
                        ),
338
                        'increment_id = %i',
339
                        (int) $cache_tree['increment_id']
340
                    );
341
                }
342
            }
343
            // <-- end - build json tree
344
    
345
            // Create expected groups access rights based upon option selected
346
            if (
347
                isset($SETTINGS['subfolder_rights_as_parent']) === true
348
                && (int) $SETTINGS['subfolder_rights_as_parent'] === 1
349
            ) {
350
                //If it is a subfolder, then give access to it for all roles that allows the parent folder
351
                $rows = DB::query('SELECT role_id, type FROM ' . prefixTable('roles_values') . ' WHERE folder_id = %i', $parent_id);
352
                foreach ($rows as $record) {
353
                    //add access to this subfolder
354
                    DB::insert(
355
                        prefixTable('roles_values'),
356
                        array(
357
                            'role_id' => $record['role_id'],
358
                            'folder_id' => $newId,
359
                            'type' => $record['type'],
360
                        )
361
                    );
362
                }
363
            } elseif ((int) $user_is_admin !== 1) {
364
                // If not admin and no option enabled
365
                // then provide expected rights based upon user's roles
366
                foreach (explode(';', $user_roles) as $role) {
367
                    if (empty($role) === false) {
368
                        DB::insert(
369
                            prefixTable('roles_values'),
370
                            array(
371
                                'role_id' => $role,
372
                                'folder_id' => $newId,
373
                                'type' => $access_rights,
374
                            )
375
                        );
376
                    }
377
                }
378
            }
379
    
380
            // if parent folder has Custom Fields Categories then add to this child one too
381
            $rows = DB::query('SELECT id_category FROM ' . prefixTable('categories_folders') . ' WHERE id_folder = %i', $parent_id);
382
            foreach ($rows as $record) {
383
                //add CF Category to this subfolder
384
                DB::insert(
385
                    prefixTable('categories_folders'),
386
                    array(
387
                        'id_category' => $record['id_category'],
388
                        'id_folder' => $newId,
389
                    )
390
                );
391
            }
392
    
393
            // clear cache cache for each user that have at least one similar role as the current user
394
            $usersWithSimilarRoles = empty($user_roles) === false  ? getUsersWithRoles(
395
                explode(";", $user_roles)
396
            ) : [];
397
            foreach ($usersWithSimilarRoles as $user) {
398
                // delete cache tree
399
                DB::delete(
400
                    prefixTable('cache_tree'),
401
                    'user_id = %i',
402
                    $user
403
                );
404
            }
405
            return array(
406
                'error' => false,
407
                'message' => '',
408
                'newId' => $newId,
409
            );
410
    
411
        } else {
412
            return array(
413
                'error' => true,
414
                'message' => $lang->get('error_not_allowed_to'),
415
                'newId' => $newId,
416
            );
417
        }
418
    }
419
420
    private function errorResponse($message, $newIdSuffix = "")
421
    {
422
        return [
423
            'error' => true,
424
            'message' => $message,
425
            'newId' => '' . $newIdSuffix,
426
        ];
427
    }
428
}