Users   B
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 347
Duplicated Lines 1.44 %

Coupling/Cohesion

Components 2
Dependencies 9

Importance

Changes 0
Metric Value
dl 5
loc 347
rs 8.295
c 0
b 0
f 0
wmc 42
lcom 2
cbo 9

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getMapper() 0 4 1
A getDataMapper() 0 4 1
A getUserByUUID() 0 6 1
A getUserByEmail() 0 6 1
B getUserDetails() 0 32 5
A getProfile() 0 7 2
B saveData() 0 29 6
B login() 5 28 6
A logout() 0 12 3
B newUserTemplate() 0 29 6
B register() 0 24 3
B saveKey() 0 42 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Users often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Users, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace FFCMS\Models;
4
5
use FFMVC\Helpers;
6
use FFCMS\{Traits, Mappers, Exceptions, Enums};
7
8
9
/**
10
 * Users Model Class.
11
 *
12
 * @author Vijay Mahrra <[email protected]>
13
 * @copyright (c) Copyright 2016 Vijay Mahrra
14
 * @license GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
15
 */
16
class Users extends DB
17
{
18
    /**
19
     * @var \FFCMS\Mappers\Users user mapper
20
     */
21
    public $mapper;
22
23
    /**
24
     * @var \FFCMS\Mappers\UsersData  mapper for user's data
25
     */
26
    protected $dataMapper;
27
28
29
    /**
30
     * initialize with array of params, 'db' and 'logger' can be injected
31
     *
32
     * @param null|\Log $logger
33
     * @param null|\DB\SQL $db
34
     */
35
    public function __construct(array $params = [], \Log $logger = null, \DB\SQL $db = null)
36
    {
37
        parent::__construct($params, $logger, $db);
38
39
        $this->dataMapper = new Mappers\UsersData;
40
    }
41
42
43
    /**
44
     * Get the associated data mapper
45
     *
46
     * @return \FFCMS\Mappers\Users
47
     */
48
    public function &getMapper()
49
    {
50
        return $this->mapper;
51
    }
52
53
54
    /**
55
     * Get the associated data mapper
56
     *
57
     * @return \FFCMS\Mappers\UsersData
58
     */
59
    public function &getDataMapper()
60
    {
61
        return $this->dataMapper;
62
    }
63
64
65
    /**
66
     * Get the user mapper by UUID
67
     *
68
     * @param string $uuid User UUID
69
     * @return \FFCMS\Mappers\Users
70
     */
71
    public function &getUserByUUID(string $uuid)
72
    {
73
        $m = $this->getMapper();
74
        $m->load(['uuid = ?', $uuid]);
75
        return $m;
76
    }
77
78
79
    /**
80
     * Get the user mapper by email address
81
     *
82
     * @param string $email email address
83
     * @return \FFCMS\Mappers\Users
84
     */
85
    public function &getUserByEmail(string $email)
86
    {
87
        $m = $this->getMapper();
88
        $m->load(['email = ?', $email]);
89
        return $m;
90
    }
91
92
93
    /**
94
     * Fetch the users data, optionally only by specified keys
95
     *
96
     * @param string $uuid
97
     * @param array $keys
98
     * @return array $data
99
     */
100
    public function getUserDetails(string $uuid, array $keys = []): array
101
    {
102
        $db = \Registry::get('db');
103
104
            // initialise return value
105
        $data = [];
106
        foreach ($keys as $k) {
107
            $data[$k] = null;
108
        }
109
110
        if (!empty($keys)) {
111
112
            $keys = array_map(function($key) {
113
                return "'$key'";
114
            }, $keys);
115
116
            $query = sprintf('SELECT * FROM users_data WHERE users_uuid = :uuid AND '.$db->quotekey('key').' IN (%s)',
117
                join(',', $keys));
118
119
        } else {
120
            $query = sprintf('SELECT * FROM users_data WHERE users_uuid = :uuid');
121
        }
122
123
        if ($rows = $db->exec($query, [':uuid' => $uuid])) {
124
125
            foreach ($rows as $r) {
126
                $data[$r['key']] = Helpers\Str::deserialize($r['value']);
127
            }
128
129
        }
130
        return $data;
131
    }
132
133
134
    /**
135
     * Fetch the users profile, optionally only by specified keys
136
     *
137
     * @param string $uuid
138
     * @param array $keys
139
     * @return array $data
140
     */
141
    public function getProfile(string $uuid, array $keys = []): array
142
    {
143
        if (empty($keys)) {
144
            $keys = Enums\ProfileKeys::values();
145
        }
146
        return $this->getUserDetails($uuid, $keys);
147
    }
148
149
150
    /**
151
     * Save user's data
152
     *
153
     * @param string $uuid
154
     * @param array $keys
155
     */
156
    public function saveData(string $uuid, array $keys = []): array
157
    {
158
        if (empty($keys)) {
159
            return false;
160
        }
161
        $db = \Registry::get('db');
162
        $dataMapper = $this->getDataMapper();
163
        foreach ($keys as $k => $v) {
164
            $dataMapper->load(['users_uuid = ? AND ' . $db->quoteKey('key') . ' = ?', $uuid, $k]);
165
            if (empty($dataMapper->type)) {
166
                switch ($k) {
167
                    case 'bio':
168
                        $dataMapper->type = 'markdown';
169
                        break;
170
                    case 'nickname':
171
                        $dataMapper->type = 'text';
172
                        break;
173
                    default:
174
                        $dataMapper->type = null;
175
                    break;
176
                }
177
            }
178
            $dataMapper->users_uuid = $uuid;
179
            $dataMapper->key = $k;
180
            $dataMapper->value = $v;
181
            $dataMapper->save();
182
        }
183
        return $dataMapper->cast();
184
    }
185
186
187
    /**
188
     * Perform a successful post-login action if the user is in the group 'user'
189
     * and is with the status 'closed', 'suspended', 'cancelled'
190
     *
191
     * @param string optional $uuid logout the current mapper user or specified one
192
     * @return boolean true/false if login permitted
193
     */
194
    public function login($uuid = null): bool
195
    {
196
        $usersMapper = empty($uuid) ? $this->getMapper() : $this->getUserByUUID($uuid);
197
        if (null == $usersMapper->uuid) {
198
            $msg = "User account not found for $uuid";
199
            throw new Exceptions\Exception($msg);
200
        }
201
202
        // set user scopes
203
        $scopes = empty($usersMapper->scopes) ? [] : preg_split("/[\s,]+/", $usersMapper->scopes);
204 View Code Duplication
        if (!in_array('user', $scopes) || in_array($usersMapper->status, ['closed', 'suspended', 'cancelled'])) {
205
            $msg = sprintf(_("User %s %s denied login because account group is not in 'user' or account status is in 'closed,suspended,cancelled'."),
206
                    $usersMapper->firstname, $usersMapper->lastname);
207
            throw new Exceptions\Exception($msg);
208
        }
209
210
        $usersMapper->login_count++;
211
        $usersMapper->login_last = Helpers\Time::database();
212
        $usersMapper->save();
213
214
        Audit::instance()->write([
215
            'users_uuid' => $usersMapper->uuid,
216
            'actor' => $usersMapper->email,
217
            'event' => 'User Login',
218
        ]);
219
220
        return true;
221
    }
222
223
224
    /**
225
     * Perform a logout action on the given user uuid
226
     *
227
     * @param string optional $uuid logout the current mapper user or specified one
228
     */
229
    public function logout($uuid = null): bool
230
    {
231
        $m = empty($uuid) ? $this->getMapper() : $this->getUserByUUID($uuid);
232
        if (null !== $m->uuid) {
233
            Audit::instance()->write([
234
                'users_uuid' => $m->uuid,
235
                'event' => 'User Logout',
236
                'actor' => $m->email,
237
            ]);
238
        }
239
        return true;
240
    }
241
242
243
    /**
244
     * Create a template object for a new user
245
     *
246
     * @param \FFCMS\Mappers\Users|null $m User Mapper
247
     * @link http://fatfreeframework.com/sql-mapper
248
     */
249
    public function &newUserTemplate($m = null): \FFCMS\Mappers\Users
250
    {
251
        if (empty($m)) {
252
            $this->mapper->reset();
253
            $m = $this->mapper;
254
        }
255
256
        $m->uuid = $m->setUUID();
257
        $m->created = Helpers\Time::database();
258
        $m->login_count = 0;
259
260
        if (empty($m->login_last)) {
261
            $m->login_last = '0000-00-00 00:00:00';
262
        }
263
264
        if (!empty($m->password)) {
265
            $m->password = Helpers\Str::password($m->password);
266
        }
267
268
        if (empty($m->status)) {
269
            $m->status = 'registered';
270
        }
271
272
        if (empty($m->scopes)) {
273
            $m->scopes = 'user';
274
        }
275
276
        return $m;
277
    }
278
279
280
    /**
281
     * Register a new user from a newly populated usersMapper object
282
     *
283
     * @param Mappers\Users|null $m User Mapper
284
     * @link http://fatfreeframework.com/sql-mapper
285
     */
286
    public function register($m = null)
287
    {
288
        if (empty($m)) {
289
            $m = $this->getMapper();
290
        }
291
292
293
        // try to save the data
294
        $m = $this->newUserTemplate($m);
295
        $result = $m->save();
296
        if (true !== $result) {
297
            return $result;
298
        }
299
300
        $audit = Audit::instance();
301
        $audit->write([
302
            'users_uuid' => $m->uuid,
303
            'actor' => $m->email,
304
            'event' => 'User Registered',
305
            'new' => $m->cast()
306
        ]);
307
308
        return true;
309
    }
310
311
312
    /**
313
     * save (insert/update) a row to the users_data table
314
     * $data['value'] is automatically encoded or serialized if array/object
315
     *
316
     * @param array $data existing data to update
317
     * @return Users $data newly saved data
318
     */
319
    public function saveKey(array $data = [])
320
    {
321
        $m = $this->getDataMapper();
322
323
        $m->load(['users_uuid = ?', $data['users_uuid']]);
324
        $oldData = clone $m;
325
326
        // set value based on content
327
        if (empty($data['value']) && !is_numeric($data['value'])) {
328
                // empty value should be null if not number
329
            $data['value'] = null;
330
        } else {
331
332
            $v = $data['value'];
333
            if (is_array($v)) {
334
                    // serialize to json if array
335
                $v = json_encode($v, JSON_PRETTY_PRINT);
336
            } elseif (is_object($v)) {
337
                    // php serialize if object
338
                $v = serialize($v);
339
            }
340
            $data['value'] = $v;
341
        }
342
343
        if (empty($m->uuid)) {
344
            $m->uuid = $data['users_uuid'];
345
        }
346
347
        $m->copyfrom($data);
348
        $m->save();
349
350
        $audit = Audit::instance();
351
352
        $audit->write([
353
            'users_uuid' => $m->users_uuid,
354
            'event' => 'Users Data Updated',
355
            'old' => $oldData->cast(),
356
            'new' => $m->cast(),
357
        ]);
358
359
        return $this;
360
    }
361
362
}
363