Completed
Push — dev-master ( 1bf013...29e920 )
by Vijay
03:09
created

User   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 584
Duplicated Lines 14.9 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
dl 87
loc 584
rs 5.6363
c 0
b 0
f 0
wmc 60
lcom 1
cbo 14

11 Methods

Rating   Name   Duplication   Size   Complexity  
A addScripts() 0 5 1
A index() 0 7 1
A account() 0 13 1
A register() 8 8 1
B loginPost() 0 56 9
F accountPost() 40 143 19
C registerPost() 6 90 11
B confirmEmail() 10 42 4
B sendConfirmationEmail() 6 40 4
A profile() 17 17 1
C profilePost() 0 73 8

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 User 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 User, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace FFCMS\Controllers\User;
4
5
use FFMVC\Helpers;
6
use FFCMS\{Controllers, Models, Mappers, Traits, Enums};
7
8
/**
9
 * User Website Controller Class.
10
 *
11
 * @author Vijay Mahrra <[email protected]>
12
 * @copyright 2016 Vijay Mahrra
13
 * @license GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
14
 */
15
class User extends Base
16
{
17
18
    /**
19
     * Add default scripts for displaying templates
20
     *
21
     * @return void
22
     * @see app/config/default.ini
23
     */
24
    protected function addScripts()
25
    {
26
        // no scripts to add, override me and set css and js
27
        $this->setScripts([], ['showdown']);
28
    }
29
30
31
    /**
32
     * user homepage
33
     *
34
     * @param \Base $f3
35
     * @return void
36
     */
37
    public function index(\Base $f3)
38
    {
39
        $this->redirectLoggedOutUser();
40
41
        $f3->set('form', $f3->get('REQUEST'));
42
        echo \View::instance()->render('user/index.phtml');
43
    }
44
45
46
    /**
47
     * login form submitted
48
     *
49
     * @param \Base $f3
50
     * @return void
51
     */
52
    public function loginPost(\Base $f3)
53
    {
54
        $this->redirectLoggedInUser();
55
        $this->csrf('@user');
56
57
        // url if login failed
58
        $view = 'user/login.phtml';
59
60
        // filter input vars of request
61
        $usersModel = Models\Users::instance();
62
        $usersMapper = $usersModel->getMapper();
63
        $usersMapper->copyfrom($f3->get('REQUEST'));
64
        $data = $usersMapper->filter();
65
        $request = $f3->get('REQUEST');
66
        foreach ($data as $k => $v) {
67
            if (array_key_exists($k, $request)) {
68
                $f3->set('REQUEST.' . $k, $v);
69
            }
70
        }
71
72
        // find user by email address
73
        $usersMapper = $usersModel->getUserByEmail($f3->get('REQUEST.email'));
74
        if (null == $usersMapper->id) {
75
            $this->notify(_('No user found with that email!'), 'error');
76
            $f3->set('form', $f3->get('REQUEST'));
77
            echo \View::instance()->render($view);
78
            return;
79
        }
80
81
        // check the password is set
82
        $password = $f3->get('REQUEST.password');
83
        if (empty($password)) {
84
            $this->notify(_('You must enter a password!'), 'warning');
85
            $f3->set('form', $f3->get('REQUEST'));
86
            echo \View::instance()->render($view);
87
            return;
88
        }
89
90
        // verify password
91
        if (!Helpers\Str::passwordVerify($usersMapper->password, $password)) {
92
            $this->notify(_('Incorrect password!'), 'warning');
93
            $f3->set('form', $f3->get('REQUEST'));
94
            echo \View::instance()->render($view);
95
            return;
96
        }
97
98
        if (!$usersModel->login()) {
99
            $this->notify(_('Unable to login!'), 'warning');
100
        } else {
101
            $f3->set('SESSION.uuid', $usersMapper->uuid);
102
            $f3->set('uuid', $usersMapper->uuid);
103
            $this->notify(_('You are now logged in!'), 'success');
104
            $uri = $f3->get('REQUEST.redirect_uri');
105
            return $f3->reroute(empty($uri) || !is_string($uri) ? '@user' : urldecode($uri));
106
        }
107
    }
108
109
110
    /**
111
     * my account
112
     *
113
     * @param \Base $f3
114
     * @return void
115
     */
116
    public function account(\Base $f3)
117
    {
118
        $this->redirectLoggedOutUser();
119
        $this->csrf();
120
121
        $f3->set('breadcrumbs', [
122
            _('My Account') => 'user',
123
            _('My Details') => 'account',
124
        ]);
125
126
        $f3->set('form', $f3->get('user'));
127
        echo \View::instance()->render('user/account.phtml');
128
    }
129
130
131
    /**
132
     * my account posted
133
     *
134
     * @param \Base $f3
135
     * @return void
136
     */
137
    public function accountPost(\Base $f3)
138
    {
139
        $this->redirectLoggedOutUser();
140
        $this->csrf();
141
142
        $view = 'user/account.phtml';
143
        $f3->set('breadcrumbs', [
144
            _('My Account') => 'user',
145
            _('My Details') => 'account',
146
        ]);
147
148
        // get current user details
149
        $usersModel = Models\Users::instance();
150
        $usersMapper = $usersModel->getUserByUUID($f3->get('uuid'));
151
        if (null == $usersMapper->id) {
152
            $this->notify(_('Your account no longer exists!'), 'error');
153
            $f3->set('form', $f3->get('REQUEST'));
154
            echo \View::instance()->render('user/account.phtml');
155
            return;
156
        }
157
158
        // check password is correct
159
        $str = Helpers\Str::instance();
160
        $old_password = $f3->get('REQUEST.old_password');
161 View Code Duplication
        if (empty($old_password) || !$str->passwordVerify($usersMapper->password, $old_password)) {
162
            $this->notify(_('You entered your current password incorrectly!'), 'warning');
163
            $f3->set('form', $f3->get('REQUEST'));
164
            echo \View::instance()->render($view);
165
            return;
166
        }
167
168
        // only allow updating of these fields
169
        $data = $f3->get('REQUEST');
170
        $fields = [
171
            'email',
172
            'password',
173
            'firstname',
174
            'lastname',
175
            'password_question',
176
            'password_answer',
177
        ];
178
        // check input data has values set for the above fields
179
        foreach ($fields as $k => $field) {
180
            if (!array_key_exists($field, $data)) {
181
                $data[$field] = null;
182
            }
183
        }
184
        // then remove any input data fields that aren't in the above fields
185
        foreach ($data as $field => $v) {
186
            if (!in_array($field, $fields)) {
187
                unset($data[$field]);
188
            }
189
        }
190
191
        // is this a password change?  if so, check they match
192
        $password = $f3->get('REQUEST.password');
193
        $confirm_password = $f3->get('REQUEST.confirm_password');
194 View Code Duplication
        if (!empty($password) || !empty($confirm_password)) {
195
            if ($password !== $confirm_password) {
196
                $this->notify(_('That password and confirm password must match!'), 'warning');
197
                $f3->set('form', $f3->get('REQUEST'));
198
                echo \View::instance()->render($view);
199
                return;
200
            } elseif ($str->passwordVerify($usersMapper->password, $password)) {
201
                $this->notify(_('The new password and old password are the same!'), 'warning');
202
                $f3->set('form', $f3->get('REQUEST'));
203
                echo \View::instance()->render($view);
204
                return;
205
            } else {
206
                // set new hashed password
207
                $data['password'] = $str->password($password);
208
            }
209
        } else {
210
            // same password
211
            $data['password'] = $usersMapper->password;
212
        }
213
214
        // check if email address change that email isn't taken
215
        $email = $f3->get('REQUEST.email');
216 View Code Duplication
        if ($usersMapper->email !== $email) {
217
            $usersMapper->load(['email = ?', $email]);
218
            if ($usersMapper->email == $email) {
219
                $this->notify(sprintf(_('The email address %s is already in use!'), $email), 'warning');
220
                $f3->set('form', $f3->get('REQUEST'));
221
                echo \View::instance()->render($view);
222
                return;
223
            } else {
224
                // new email
225
                $data['email'] = $email;
226
            }
227
        } else {
228
            // no change
229
            unset($data['email']);
230
        }
231
232
        // update required fields to check from ones which changed
233
        // validate the entered data
234
        $data['uuid'] = $f3->get('uuid');
235
        $usersMapper->copyfrom($data);
236
        $usersMapper->validationRequired($fields);
237
        $errors = $usersMapper->validate(false);
238
        if (is_array($errors)) {
239
            $this->notify(['warning' => $usersMapper->validationErrors($errors)]);
240
            $f3->set('form', $f3->get('REQUEST'));
241
            echo \View::instance()->render($view);
242
            return;
243
        }
244
245
        // no change, do nothing
246
        if (!$usersMapper->changed()) {
247
            $this->notify(_('There was nothing to change!'), 'info');
248
            $f3->set('form', $f3->get('REQUEST'));
249
            echo \View::instance()->render($view);
250
            return;
251
        }
252
253
        // reset usermapper and copy in valid data
254
        $usersMapper->load(['uuid = ?', $data['uuid']]);
255
        $usersMapper->copyfrom($data);
256
        if ($usersMapper->save()) {
257
            $this->notify(_('Your account was updated!'), 'success');
258
        } else {
259
            $this->notify(_('Unable to update your account!'), 'error');
260
            $f3->set('form', $f3->get('REQUEST'));
261
            echo \View::instance()->render($view);
262
            return;
263
        }
264
265
        // send verification email if email change - non-fatal
266
        if ($usersMapper->changed()) {
267
            // if email address changed, send confirmation enail
268
            if (!$usersModel->saveKey([
269
                'users_uuid' => $usersMapper->uuid,
270
                'key'       => 'email_confirmed',
271
                'value'     => 0
272
            ])) {
273
                $this->notify(_('Setting confirmation email failed.'), 'warning');
274
            }
275
            $this->sendConfirmationEmail();
276
        }
277
278
        $f3->reroute('@user');
279
    }
280
281
282
    /**
283
     * registration page
284
     *
285
     * @param \Base $f3
286
     * @return void
287
     */
288 View Code Duplication
    public function register(\Base $f3)
289
    {
290
        $this->redirectLoggedInUser();
291
        $this->csrf('@user');
292
293
        $f3->set('form', $f3->get('REQUEST'));
294
        echo \View::instance()->render('user/register.phtml');
295
    }
296
297
298
    /**
299
     * registration posted
300
     *
301
     * @param \Base $f3
302
     * @return void
303
     */
304
    public function registerPost(\Base $f3)
305
    {
306
        $this->redirectLoggedInUser();
307
        $this->csrf('@register');
308
309
        // filter input vars of request
310
        $usersModel = Models\Users::instance();
311
        $usersMapper = $usersModel->getMapper();
312
        $usersMapper->copyfrom($f3->get('REQUEST'));
313
        $data = $usersMapper->filter();
314
        $request = $f3->get('REQUEST');
315
        foreach ($data as $k => $v) {
316
            if (array_key_exists($k, $request)) {
317
                $f3->set('REQUEST.' . $k, $v);
318
            }
319
        }
320
321
        $view = 'user/register.phtml';
322
323
        $email = $f3->get('REQUEST.email');
324
        if (empty($email)) {
325
            $this->notify(_('You need to enter an email address!'), 'warning');
326
            $f3->set('form', $f3->get('REQUEST'));
327
            echo \View::instance()->render($view);
328
            return;
329
        }
330
331
        // find user by email address
332
        $usersModel = Models\Users::instance();
333
        $usersMapper = $usersModel->getUserByEmail($email);
334
        if (null !== $usersMapper->id) {
335
            $this->notify(_('That user already exists!'), 'error');
336
            $f3->set('form', $f3->get('REQUEST'));
337
            echo \View::instance()->render($view);
338
            return;
339
        }
340
341
        // bad password
342
        $password = $f3->get('REQUEST.password');
343
        $confirm_password = $f3->get('REQUEST.confirm_password');
344 View Code Duplication
        if (empty($password) || empty($confirm_password) || ($password !== $confirm_password)) {
345
            $this->notify(_('That password and confirm password must match!'), 'warning');
346
            $f3->set('form', $f3->get('REQUEST'));
347
            echo \View::instance()->render($view);
348
            return;
349
        }
350
351
        // use the model to validate the data input
352
        $usersMapper->copyfrom($f3->get('REQUEST'));
353
        $usersMapper->validationRequired([
354
            'email',
355
            'password',
356
            'firstname',
357
            'lastname',
358
            'password_question',
359
            'password_answer',
360
        ]);
361
362
        // set defaults
363
        $usersMapper->setUUID();
364
        $usersMapper->scopes = 'user';
365
        $usersMapper->status = 'registered';
366
        $usersMapper->created = Helpers\Time::database();
367
368
        $errors = $usersMapper->validate(false);
369
370
        if (is_array($errors)) {
371
            $this->notify(['info' => $usersMapper->validationErrors($errors)]);
372
            $f3->set('form', $f3->get('REQUEST'));
373
            echo \View::instance()->render($view);
374
            return;
375
        }
376
377
        if (!$usersModel->register()) {
378
            $this->notify(_('Registration failed!'), 'error');
379
            $f3->set('form', $f3->get('REQUEST'));
380
            echo \View::instance()->render($view);
381
            return;
382
        }
383
        $usersModel->login();
384
        $f3->set('SESSION.uuid', $usersMapper->uuid);
385
        $f3->set('uuid', $usersMapper->uuid);
386
        $this->notify(_('You successfully registered!'), 'success');
387
        $uri = $f3->get('REQUEST.redirect_uri');
388
389
        // send confirmation email
390
        $this->sendConfirmationEmail();
391
392
        return $f3->reroute(empty($uri) ? '@user' : urldecode($uri));
393
    }
394
395
396
    /**
397
     * Confirm email address link handler from email
398
     *
399
     * @param \Base $f3
400
     * @return void
401
     */
402
    public function confirmEmail(\Base $f3)
403
    {
404
        $usersModel = Models\Users::instance();
405
        $usersMapper = $usersModel->getMapper();
406
        $usersDataMapper = $usersModel->getDataMapper();
407
408
        // load in the forgot password reset code row
409
        $usersDataMapper->load([$usersDataMapper->quotekey('value')." = ? AND ".$usersDataMapper->quotekey('key')." = 'confirm_email_code'", $f3->get('REQUEST.code')]);
410 View Code Duplication
        if (null == $usersDataMapper->uuid) {
411
            $this->notify(_('Unknown password reset code!'), 'error');
412
            $f3->reroute('@index');
413
            return;
414
        }
415
416
        // check that the user exists for the reset code
417
        $usersMapper->load(['uuid = ?', $usersDataMapper->users_uuid]);
418 View Code Duplication
        if (null == $usersDataMapper->uuid) {
419
            $this->notify(_('Unknown user for confirmation code!'), 'error');
420
            $f3->reroute('@index');
421
            return;
422
        }
423
424
        // update account status to 'confirmed'
425
        $usersMapper->status = 'confirmed';
426
        if (!$usersMapper->save()) {
427
            $this->notify(_('Unable to update account status!'), 'error');
428
            $f3->reroute('@index');
429
            return;
430
        }
431
432
            //delete confirm_email_code and add email_confirmed
433
        $usersDataMapper->erase();
434
        $usersModel->saveKey([
435
            'users_uuid' => $usersMapper->uuid,
436
            'key'       => 'email_confirmed',
437
            'value'     => 1
438
        ]);
439
440
        $this->notify(_('Your email was successfully confirmed!'), 'success');
441
442
        $f3->reroute('@index');
443
    }
444
445
446
    /**
447
     * Send an email confirmation to the given user
448
     *
449
     * @param null|string $email the email address of the user
450
     * @param boolean true/false
451
     */
452
    private function sendConfirmationEmail(string $email = null)
453
    {
454
        $f3          = \Base::instance();
455
        $usersModel  = Models\Users::instance();
456
        $usersMapper = empty($email) ? $usersModel->getMapper() : $usersModel->getUserByEmail($email);
457
        $usersDataMapper = $usersModel->getDataMapper();
458
        if (empty($usersMapper->email)) {
459
            $this->notify(_('Could not send confirmation email.'), 'error');
460
            return;
461
        }
462
463
            // generate a random code to email to the user for confirming the email
464
        $usersModel->saveKey([
465
            'users_uuid' => $usersMapper->uuid,
466
            'key' => 'confirm_email_code',
467
            'value' => Helpers\Str::random(6)
468
        ]);
469
470
            // set the email template variables
471
        $f3->set('templateData',
472
            array_merge($usersMapper->cast(), [
473
            'code' => $usersDataMapper->value,
474
            'url' => Helpers\Url::internal('@confirm_email')
475
        ]));
476
477
        // setup PHPMailer
478
        $mail = Helpers\Mail::getPHPMailer([
479
            'To'      => $usersMapper->email,
480
            'Subject' => "Confirm Email Address",
481
            'Body'    => \Markdown::instance()->convert(\View::instance()->render('email/confirm_email.md')),
482
            'AltBody' => \View::instance()->render('email/forgot_password.md', 'text/plain')
483
        ]);
484
485 View Code Duplication
        if ($mail->send()) {
486
            $this->notify(_("A notification has been sent to confirm your email address."), "success");
487
        } else {
488
            $this->notify(_("There was a problem sending you a registration email, please check your email and/or try again later."), "warning");
489
            $this->notify($mail->ErrorInfo, "error");
490
        }
491
    }
492
493
494
    /**
495
     * my profile
496
     *
497
     * @param \Base $f3
498
     * @return void
499
     */
500 View Code Duplication
    public function profile(\Base $f3)
501
    {
502
        $this->redirectLoggedOutUser();
503
        $this->csrf();
504
505
        $f3->set('breadcrumbs', [
506
            _('My Account') => 'user',
507
            _('My Profile') => 'profile',
508
        ]);
509
510
        // fetch profile
511
        $usersModel = Models\Users::instance();
512
        $profileData = $usersModel->getProfile($f3->get('uuid'));
513
        $f3->set('form', $profileData);
514
515
        echo \View::instance()->render('user/profile.phtml');
516
    }
517
518
519
    /**
520
     * my profile posted
521
     *
522
     * @param \Base $f3
523
     * @return void
524
     */
525
    public function profilePost(\Base $f3)
526
    {
527
        $this->redirectLoggedOutUser();
528
        $this->csrf();
529
530
        $view = 'user/profile.phtml';
531
        $f3->set('breadcrumbs', [
532
            _('My Account') => 'user',
533
            _('My Profile') => 'profile',
534
        ]);
535
536
        // handle file upload
537
        // wrong upload form field name or
538
        // upload mime type is not image/* or
539
        // size > 4 MB upload limit
540
        $files = \Web::instance()->receive(function($metadata, $fieldname){
541
            return !(
542
                'profile' !== $fieldname ||
543
                'image/' !== substr($metadata['type'], 0, 6) ||
544
                $metadata['size'] > Enums\Bytes::MEGABYTE() * 4
545
            );
546
        }, true, true);
547
548
        // create new profile image
549
        if (!empty($files)) {
550
            foreach ($files as $file => $valid) {
0 ignored issues
show
Bug introduced by
The expression $files of type boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
551
                if (false === $valid) {
552
                    $this->notify(_("The file uploaded was not valid!"), 'error');
553
                } else {
554
                    $user = $f3->get('usersMapper');
555
                    if ($user->profileImageCreate($file)) {
556
                        $this->notify(_("Your profile picture was updated!"), 'success');
557
                    }
558
                }
559
                unlink($file);
560
            }
561
        }
562
563
        // get existing profile and merge with input
564
        $usersModel = Models\Users::instance();
565
        $profileEnum = new Enums\ProfileKeys;
566
567
        // merge profile keys and filter input
568
        $profileData = $this->filter(
569
            array_intersect_key(
570
                array_merge(
571
                    $usersModel->getProfile($f3->get('uuid')),
572
                    $f3->get('REQUEST')
573
                ),
574
                $profileEnum->values()
575
            ), [
576
            'nickname' => 'trim|sanitize_string',
577
            'bio' => 'trim|sanitize_string'
578
        ]);
579
580
        $errors = $this->validate(false, $profileData, [
581
            'nickname' => 'valid_name',
582
        ]);
583
        if (is_array($errors)) {
584
            $this->notify(['warning' => $this->validationErrors($errors)]);
585
            $f3->set('form', $f3->get('REQUEST'));
586
            echo \View::instance()->render($view);
587
            return;
588
        }
589
590
        // save profile
591
        $usersModel->saveData($f3->get('uuid'), $profileData);
592
593
        // set form data
594
        $f3->set('form', $profileData);
595
596
        echo \View::instance()->render('user/profile.phtml');
597
    }
598
}
599