Auth_Basic   F
last analyzed

Complexity

Total Complexity 101

Size/Duplication

Total Lines 847
Duplicated Lines 1.77 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 15
loc 847
rs 3.9999
c 0
b 0
f 0
wmc 101
lcom 1
cbo 11

28 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 18 4
A allow() 0 21 3
C setModel() 0 53 9
B addEncryptionHook() 0 14 5
A destroy() 0 5 1
A get() 0 11 3
A getAll() 0 4 1
A allowPage() 0 13 3
A getAllowedPages() 0 4 1
A isPageAllowed() 0 8 3
A usePasswordEncryption() 0 6 1
C encryptPassword() 0 39 12
B check() 0 36 6
A addInfo() 0 15 3
A isLoggedIn() 0 4 1
D verifyCredentials() 15 122 14
A memorizeURL() 0 9 3
A getURL() 0 15 2
A loginRedirect() 0 5 1
A loggedIn() 0 6 1
B memorizeModel() 0 22 4
A loginByID() 0 7 1
A loginBy() 0 7 1
B login() 0 25 4
A logout() 0 19 2
A createForm() 0 21 3
B showLoginForm() 0 36 6
A processLogin() 0 17 3

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

1
<?php
2
/**
3
 * A basic authentication class. Include inside your APP or
4
 * on a page. You may have multiple Auth instances. Supports
5
 * 3rd party plugins.
6
 *
7
 * Use:
8
 *
9
 * $auth=$this->add('Auth');
10
 * $auth->usePasswordEncryption();
11
 * $auth->setModel('User');
12
 * $auth->check();
13
 *
14
 * Auth accessible from anywhere through $this->app->auth;
15
 *
16
 * Auth has several extensions, enable them like this:
17
 *
18
 * $auth->add('auth/Controller_DummyPopup');        // allows you to pick user from list and bypass password
19
 * $auth->add('auth/Controller_Cookie');            // adds "remember me" checkbox
20
 *
21
 * See documentation on "auth" add-on for more information
22
 *  http://agiletoolkit.org/a/auth
23
 */
24
class Auth_Basic extends AbstractController
25
{
26
    /**
27
     * Info will contain data loaded about authenticated user.
28
     * This property can be accessed through $this->get() and should not be changed after authentication.
29
     *
30
     * @var array|bool
31
     */
32
    public $info = false;
33
34
    /**
35
     * This form is created when user is being asked about authentication.
36
     * If you are willing to change the way form looks, create it prior to calling check().
37
     * Your form must have compatible field names: "username" and "password".
38
     *
39
     * @var Form
40
     */
41
    public $form = null;
42
43
    /**
44
     * @var string|callable Which encryption to use. Few are built-in
45
     */
46
    protected $password_encryption = null;
47
48
    /**
49
     * @var array Array of allowed page names
50
     */
51
    protected $allowed_pages = array();
52
53
    /**
54
     * @var string Login field name in model
55
     */
56
    public $login_field = 'email';
57
58
    /**
59
     * @var string Password field name in model
60
     */
61
    public $password_field = 'password';
62
63
    /**
64
     * @var int Encyption algorithm
65
     */
66
    public $hash_algo = PASSWORD_DEFAULT;
67
68
    /**
69
     * @var array Encryption algorithm options
70
     */
71
    public $hash_options = array();
72
73
    /**
74
     * @var string Layout class
75
     */
76
    public $login_layout_class = 'Layout_Centered';
77
78
    /** @var App_Frontend */
79
    public $app;
80
81
82
83
    public function init()
84
    {
85
        parent::init();
86
87
        // Register as auth handler, if it's not set yet
88
        if (!isset($this->app->auth)) {
89
            $this->app->auth = $this;
90
        }
91
92
        if (!$this->app->hasMethod('initializeSession') && !session_id()) {
93
            // No session support
94
            return;
95
        }
96
97
        // Try to get information from the session. If user is authenticated, information will
98
        // be available there
99
        $this->info = (array) $this->recall('info', false);
100
    }
101
102
    /**
103
     * Configure this Auth controller with a generic Model based on static
104
     * collection of user/password combinations. Use this method if you
105
     * only want one or few accounts to access the system.
106
     *
107
     * @param string|array $user Either string username or associative array with data
108
     * @param string $pass Password if username is string
109
     *
110
     * @return $this
111
     */
112
    public function allow($user, $pass = null)
113
    {
114
        // creates fictional model to allow specified user and password
115
        // TODO: test this
116
        if ($this->model) {
117
            $this->model->table[] = array($this->login_field => $user, $this->password_field => $pass);
118
119
            return $this;
120
        }
121
        /** @type Model $m */
122
        $m = $this->add('Model');
123
        $m->setSource('Array', array(
124
                is_array($user)
125
                ? $user
126
                : array($this->login_field => $user, $this->password_field => $pass)
127
            ));
128
        $m->id_field = $this->login_field;
129
        $this->setModel($m);
130
131
        return $this;
132
    }
133
    /**
134
     * Associate model with authentication class. Username / password
135
     * check will be performed against the model in the following steps:
136
     * Model will attempt to load record where login_field matches
137
     * specified. Password is then loaded and verified using configured
138
     * encryption method.
139
     *
140
     * @param string|object $model
141
     * @param string $login_field
142
     * @param string $password_field
143
     *
144
     * @return Model
145
     */
146
    public function setModel($model, $login_field = 'email', $password_field = 'password')
147
    {
148
        parent::setModel($model);
149
        $this->login_field = $login_field;
150
        $this->password_field = $password_field;
151
152
        // Load model from session
153
        if ($this->info && $this->recall('id')) {
154
            if ($this->recall('class', false) == get_class($this->model)) {
155
                $this->debug('Loading model from cache');
156
                $this->model->set($this->info);
157
                $this->model->dirty = array();
158
                $this->model->id = $this->recall('id', null);
159
            } else {
160
                // Class changed, re-fetch data from database
161
                $this->debug('Class changed, loading from database');
162
                $this->model->tryLoad($this->recall('id'));
163
                if (!$this->model->loaded()) {
164
                    $this->logout();
165
                }
166
167
                $this->memorizeModel();
168
            }
169
        }
170
171
        $id = $this->hook('tryLogin', array($model, $login_field, $password_field));
172
173
        if ($id && is_numeric($id)) {
174
            $this->model->tryLoad($id);
175
            $this->memorizeModel();
176
        }
177
178
        $t = $this;
179
180
        // If model is saved, update our cache too, but don't store password
181
        $this->model->addHook('afterSave', function ($m) use ($t) {
182
            // after this model is saved, re-cache the info
183
            $tmp = $m->get();
184
            unset($tmp[$t->password_field]);
185
            if ($t->app instanceof App_Web) {
186
                $t->memorize('info', $tmp);
187
            }
188
        });
189
190
        $this->addEncryptionHook($this->model);
191
192
        if (strtolower($this->app->page) == 'logout') {
193
            $this->logout();
194
            $this->app->redirect('/');
195
        }
196
197
        return $this->model;
198
    }
199
    /**
200
     * Adds a hook to specified model which will encrypt password before save.
201
     * This method will be applied on $this->model, so you should not call
202
     * it manually. You can call it on a fresh model, however.
203
     *
204
     * @param Model $model
205
     */
206
    public function addEncryptionHook($model)
207
    {
208
        // If model is saved, encrypt password
209
        $t = $this;
210
        if (isset($model->has_encryption_hook) && $model->has_encryption_hook) {
0 ignored issues
show
Bug introduced by
The property has_encryption_hook does not seem to exist in Model.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
211
            return;
212
        }
213
        $model->has_encryption_hook = true;
214
        $model->addHook('beforeSave', function ($m) use ($t) {
215
            if ($m->isDirty($t->password_field) && $m[$t->password_field]) {
216
                $m[$t->password_field] = $t->encryptPassword($m[$t->password_field], $m[$t->login_field]);
217
            }
218
        });
219
    }
220
221
    /**
222
     * Destroy object
223
     */
224
    public function destroy()
225
    {
226
        unset($this->app->auth);
227
        parent::destroy();
228
    }
229
230
    /**
231
     * Auth memorizes data about a logged-in user in session. You can either use this function to access
232
     * that data or $auth->model (preferred) $auth->get('username') will always point to the login field
233
     * value ofthe user regardless of how your field is named.
234
     *
235
     * @param string $property
236
     * @param mixed  $default
237
     *
238
     * @return mixed
239
     */
240
    public function get($property = null, $default = null)
241
    {
242
        if (is_null($property)) {
243
            return $this->info;
244
        }
245
        if (!isset($this->info[$property])) {
246
            return $default;
247
        }
248
249
        return $this->info[$property];
250
    }
251
252
    /**
253
     * Return array of all authenticated session info
254
     *
255
     * @return array
256
     */
257
    public function getAll()
258
    {
259
        return $this->info;
260
    }
261
262
    /**
263
     * Specify page or array of pages which will exclude authentication. Add your registration page here
264
     * or page containing terms and conditions.
265
     *
266
     * @param string|array $page
267
     *
268
     * @return $this
269
     */
270
    public function allowPage($page)
271
    {
272
        if (is_array($page)) {
273
            foreach ($page as $p) {
274
                $this->allowPage($p);
275
            }
276
277
            return $this;
278
        }
279
        $this->allowed_pages[] = $page;
280
281
        return $this;
282
    }
283
284
    /**
285
     * Return array of all allowed page names
286
     *
287
     * @return array
288
     */
289
    public function getAllowedPages()
290
    {
291
        return $this->allowed_pages;
292
    }
293
294
    /**
295
     * Verifies if the specified page is allowed to be accessed without
296
     * authentication.
297
     *
298
     * @param string $page
299
     *
300
     * @return bool
301
     */
302
    public function isPageAllowed($page)
303
    {
304
        if ($this->hook('isPageAllowed', array($page)) === true) {
305
            return true;
306
        }
307
308
        return in_array($page, $this->allowed_pages) || in_array(str_replace('_', '/', $page), $this->allowed_pages);
309
    }
310
311
    /**
312
     * Specifies how password will be encrypted when stored. It's recommended
313
     * that you do not specify encryption method, in which case a built-in
314
     * password_hash() will be used, which is defined by PHP.
315
     *
316
     * Some other values are "sha256/salt", "md5", "rot13". Note that if your
317
     * application is already using 'md5' or 'sha1', you can remove the
318
     * argument entirely and your user passwords will keep working and will
319
     * automatically be "upgraded" to password_hash when used.
320
     *
321
     * If you are having trouble with authentication, use auth->debug()
322
     *
323
     * @param string|callable $method
324
     *
325
     * @return $this
326
     */
327
    public function usePasswordEncryption($method = 'php')
328
    {
329
        $this->password_encryption = $method;
330
331
        return $this;
332
    }
333
334
    /**
335
     * Manually encrypt password
336
     *
337
     * @param string $password
338
     * @param string $salt
339
     *
340
     * @return string|bool Returns false on failure, encrypted string otherwise
341
     */
342
    public function encryptPassword($password, $salt = null)
343
    {
344
        if (!is_string($this->password_encryption) && is_callable($this->password_encryption)) {
345
            $e = $this->password_encryption;
346
347
            return $e($password, $salt);
348
        }
349
        if ($this->password_encryption) {
350
            $this->debug("Encrypting password: '$password' with ".$this->password_encryption.' salt='.$salt);
351
        }
352
        switch ($this->password_encryption) {
353
            case null:
354
                return $password;
355
            case 'php':
356
                // returns false on failure
357
                return password_hash($password, $this->hash_algo, $this->hash_options);
358
            case 'sha256/salt':
359
                if ($salt === null) {
360
                    throw $this->exception(
361
                        'sha256 requires salt (2nd argument to encryptPassword and is normaly an email)'
362
                    );
363
                }
364
                $key = $this->app->getConfig('auth/key', $this->app->name);
365
                if ($this->password_encryption) {
366
                    $this->debug('Using key '.$key);
367
                }
368
369
                return hash_hmac('sha256', $password.$salt, $key);
370
            case 'sha1':
371
                return sha1($password);
372
            case 'md5':
373
                return md5($password);
374
            case 'rot13':
375
                return str_rot13($password);
376
            default:
377
                throw $this->exception('No such encryption method')
378
                    ->addMoreInfo('encryption', $this->password_encryption);
379
        }
380
    }
381
382
    /**
383
     * Call this function to perform a check for logged in user. This will also display a login-form
384
     * and will verify user's credential. If you want to handle log-in form on your own, use
385
     * auth->isLoggedIn() to check and redirect user to a login page.
386
     *
387
     * check() returns true if user have just logged in and will return "null" for requests when user
388
     * continues to use his session. Use that to perform some calculation on log-in
389
     *
390
     * @return bool
391
     */
392
    public function check()
393
    {
394
        if ($this->isPageAllowed($this->app->page)) {
395
            return;
396
        }      // no authentication is required
397
398
        // Check if user's session contains autentication information
399
        if (!$this->isLoggedIn()) {
400
            $this->memorizeURL();
401
402
            // Brings up additional verification methods, such as cookie
403
            // authentication, token or OpenID. In case of successful login,
404
            // breakHook($user_id) must be used
405
            $user_id = $this->hook('check');
406
            if (!is_array($user_id) && !is_bool($user_id) && $user_id) {
407
                $this->model->load($user_id);
408
                //$this->loggedIn();
409
                $this->memorizeModel();
410
411
                return true;
412
            }
413
414
            /*
415
            $this->debug('User is not authenticated yet');
416
417
            // No information is present. Let's see if cookie is set
418
                }
419
            }else $this->debug("No permanent cookie");
420
             */
421
            $this->processLogin();
422
423
            return true;
424
        } else {
425
            $this->debug('User is already authenticated');
426
        }
427
    }
428
429
    /**
430
     * Add additional info to be stored in user session.
431
     *
432
     * @param string|array $key
433
     * @param mixed $val
434
     *
435
     * @return $this
436
     */
437
    public function addInfo($key, $val = null)
438
    {
439
        if (is_array($key)) {
440
            foreach ($key as $a => $b) {
441
                $this->addInfo($a, $b);
442
            }
443
444
            return $this;
445
        }
446
447
        $this->debug("Gathered info: $key=$val");
448
        $this->info[$key] = $val;
449
450
        return $this;
451
    }
452
453
    /**
454
     * This function determines - if user is already logged in or not. It does it by
455
     * looking at $this->info, which was loaded during init() from session.
456
     *
457
     * @return bool
458
     */
459
    public function isLoggedIn()
460
    {
461
        return $this->model->loaded();
462
    }
463
464
    /**
465
     * This function verifies credibility of supplied authenication data.
466
     * It will search based on user and verify the password. It's also
467
     * possible that the function will re-hash user password with
468
     * updated hash.
469
     *
470
     * if default authentication method is used, the function will
471
     * automatically determine hash used for password generation and will
472
     * upgrade to a new php5.5-compatible syntax.
473
     *
474
     * This function return false OR the id of the record matching user.
475
     *
476
     * @param string $user
477
     * @param string $password
478
     *
479
     * @return mixed
480
     */
481
    public function verifyCredentials($user, $password)
482
    {
483
        $this->debug('verifying credentials for '.$user.' '.$password);
484
485
        // First, perhaps model has a method for verifying credentials.
486
        if ($this->model->hasMethod('verifyCredentials')) {
487
            return $this->model->verifyCredentials($user, $password);
0 ignored issues
show
Documentation Bug introduced by
The method verifyCredentials does not exist on object<Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
488
        }
489
490
        // If password field is not defined in the model for security
491
        // reasons, let's add it for authentication purpose.
492
        $password_existed = true;
493
        if (!$this->model->hasElement($this->password_field)) {
494
            $this->model->addField($this->password_field)->type('password');
495
            $password_existed = false;
496
        }
497
498
        // Attempt to load user data by username. If not found, return false
499
        /** @type Model $data User model */
500
        $data = $this->model->newInstance();
501
502
        $data->tryLoadBy($this->login_field, $user);
503 View Code Duplication
        if (!$data->loaded()) {
504
            $this->debug('user with login '.$user.' could not be loaded');
505
            if (!$password_existed) {
506
                $data->getElement($this->password_field)->destroy();
507
            }
508
509
            return false;
510
        }
511
512
        $hash = $data[$this->password_field];
513
514
        $this->debug('loaded user entry with hash: '.$hash);
515
516
        // Verify password first
517
        $result = false;
518
        $rehash = false;
519
520
        if ($this->password_encryption == 'php') {
521
522
            // Get information about the hash
523
            $info = password_get_info($hash);
524
525
            // Backwards-compatibility with older ATK encryption methods
526
            if ($info['algo'] == 0) {
527
528
                // Determine type of hash by length
529
                if (strlen($hash) == 64) {
530
                    $this->password_encryption = 'sha256/salt';
531
                } elseif (strlen($hash) == 32) {
532
                    $this->password_encryption = 'md5';
533
                } elseif (strlen($hash) == 40) {
534
                    $this->password_encryption = 'sha1';
535
                } else {
536
                    $this->password_encryption = null;
537
                    $this->debug('Unable to identify password hash type, using plain-text matching');
538
                    /*
539
                    $this->password_encryption='php';
540
                    $data->unload();
541
                    if (!$password_existed) {
542
                        $data->getElement($this->password_field)->destroy();
543
                    }
544
                    return false;
545
                     */
546
                }
547
548
                // Convert password hash
549
                $this->debug('Old password found with algo='.$this->password_encryption);
550
                $ep = $this->encryptPassword($password, $user);
551
                $this->password_encryption = 'php';
552
553
                $rehash = true;
554
                $result = $hash == $ep;
555
            } else {
556
                $result = password_verify($password, $ep = $data[$this->password_field]);
557
                $this->debug('Native password hash with info: '.json_encode($info));
558
                $rehash = password_needs_rehash(
559
                    $hash,
560
                    $this->hash_algo,
561
                    $this->hash_options
562
                );
563
            }
564
565
            if ($result) {
566
                $this->debug('Verify is a SUCCESS');
567
568
                if ($rehash) {
569
                    $hash = $data[$this->password_field] = $password;
570
                    $data->setDirty($this->password_field);
571
                    $data->save();
572
                    $this->debug('Rehashed into '.$data[$this->password_field]);
573
                }
574
            }
575
        } else {
576
            $ep = $this->encryptPassword($password, $user);
577
            $result = $hash == $ep;
578
            $this->debug('Attempting algo='.$this->password_encryption.' hash='.$hash.' newhash='.$ep);
579
        }
580
581 View Code Duplication
        if (!$result) {
582
            $this->debug('Verify is a FAIL');
583
            $data->unload();
584
            if (!$password_existed) {
585
                $data->getElement($this->password_field)->destroy();
586
            }
587
588
            return false;
589
        }
590
591
        // Leave record loaded, but hide password
592
        $data[$this->password_field] = '';
593
        $data->dirty[$this->password_field] = false;
594
595
        return $data[$this->model->id_field];
596
597
        /*
598
        if (!$password_existed) {
599
            $data->getElement($this->password_field)->destroy();
600
        }
601
        */
602
    }
603
604
    /**
605
     * Memorize current URL. Called when the first unsuccessful check is executed.
606
     */
607
    public function memorizeURL()
608
    {
609
        if ($this->app->page !== 'index' && !$this->recall('page', false)) {
610
            $this->memorize('page', $this->app->page);
611
            $g = $_GET;
612
            unset($g['page']);
613
            $this->memorize('args', $g);
614
        }
615
    }
616
617
    /**
618
     * Return originalally requested URL.
619
     *
620
     * @return string
621
     */
622
    public function getURL()
623
    {
624
        $p = $this->recall('page');
625
626
        // If there is a login page, no need to return to it
627
        if ($p == 'login') {
628
            return $this->app->url('/');
629
        }
630
631
        $url = $this->app->url($p, $this->recall('args', null));
632
        $this->forget('page');
633
        $this->forget('args');
634
635
        return $url;
636
    }
637
638
    /**
639
     * Rederect to page user tried to access before authentication was requested.
640
     */
641
    public function loginRedirect()
642
    {
643
        $this->debug('to Index');
644
        $this->app->redirect($this->getURL());
645
    }
646
647
    /**
648
     * This function is always executed after successfull login through a normal means (login form or plugin).
649
     *
650
     * It will create cache model data.
651
     *
652
     * @param string $user
653
     * @param string $pass
654
     */
655
    public function loggedIn($user = null, $pass = null)
656
    {
657
        //$username,$password,$memorize=false){
658
        $this->hook('loggedIn', array($user, $pass));
659
        $this->app->redirect($this->getURL());
660
    }
661
662
    /**
663
     * Store model in session data so that it can be retrieved faster.
664
     */
665
    public function memorizeModel()
666
    {
667
        if (!$this->model->loaded()) {
668
            throw $this->exception('Authentication failure', 'AccessDenied');
669
        }
670
671
        // Don't store password in model / memory / session
672
        $this->model['password'] = null;
673
        unset($this->model->dirty['password']);
674
675
        // Cache memory. Should we use controller?
676
        $this->info = $this->model->get();
677
        $this->info['username'] = $this->info[$this->login_field];
678
679
        if ($this->app->hasMethod('initializeSession') || session_id()) {
680
            $this->memorize('info', $this->info);
681
            $this->memorize('class', get_class($this->model));
682
            $this->memorize('id', $this->model->id);
683
        }
684
685
        $this->hook('login');
686
    }
687
688
    /**
689
     * Manually Log in as specified users. Will not perform password check or redirect.
690
     *
691
     * @param mixed $id
692
     *
693
     * @return $this
694
     */
695
    public function loginByID($id)
696
    {
697
        $this->model->load($id);
698
        $this->memorizeModel();
699
700
        return $this;
701
    }
702
703
    /**
704
     * Manually Log in with specified condition.
705
     *
706
     * @param string $field
707
     * @param mixed  $value
708
     *
709
     * @return $this
710
     */
711
    public function loginBy($field, $value)
712
    {
713
        $this->model->tryLoadBy($field, $value);
714
        $this->memorizeModel();
715
716
        return $this;
717
    }
718
719
    /**
720
     * Manually Log in as specified users by using login name.
721
     *
722
     * @param string $user
723
     *
724
     * @return $this
725
     */
726
    public function login($user)
727
    {
728
        if (is_object($user)) {
729
            if (!$this->model) {
730
                throw $this->exception('Auth Model should be set');
731
            }
732
            $c = get_class($this->model);
733
734
            if (!$user instanceof $c) {
735
                throw $this->exception('Specified model with incompatible class')
736
                ->addMoreInfo('required', $c)
737
                ->addMoreInfo('supplied', get_class($user));
738
            }
739
740
            $this->model = $user;
741
            $this->memorizeModel();
742
743
            return $this;
744
        }
745
746
        $this->model->tryLoadBy($this->login_field, $user);
747
        $this->memorizeModel();
748
749
        return $this;
750
    }
751
752
    /**
753
     * Manually log out user.
754
     *
755
     * @return $this
756
     */
757
    public function logout()
758
    {
759
        $this->hook('logout');
760
761
        $this->model->unload();
762
763
        // maybe can use $this->app->destroySession() here instead?
764
        $this->forget('info');
765
        $this->forget('id');
766
767
        setcookie(session_name(), '', time() - 42000, '/');
768
        if (session_status() === PHP_SESSION_ACTIVE) {
769
            session_destroy();
770
        }
771
772
        $this->info = false;
773
774
        return $this;
775
    }
776
777
    /**
778
     * Creates log-in form.
779
     * Override if you want to use your own form. If you need to change template used by a log-in form,
780
     * add template/default/page/login.html.
781
     *
782
     * @param Page $page
783
     *
784
     * @return Form
785
     */
786
    public function createForm($page)
787
    {
788
        /** @type Form $form */
789
        $form = $page->add('Form', null, null, array('form/minimal'));
790
791
        /** @type Field $email */
792
        $email = $this->model->hasField($this->login_field);
793
        $email = $email ? $email->caption() : 'E-mail';
794
795
        /** @type Field $password */
796
        $password = $this->model->hasField($this->password_field);
797
        $password = $password ? $password->caption() : 'Password';
798
799
        $form->addField('Line', 'username', $email);
800
        $form->addField('Password', 'password', $password);
801
        $form->addSubmit('Login')->addClass('atk-jackscrew')->addClass('atk-swatch-green');
802
803
        //$form->add('View',null,'button_row_left')->addClass('atk-jackscrew');
804
805
        return $form;
806
    }
807
808
    /**
809
     * Do not override this function.
810
     *
811
     * @return Page
812
     */
813
    public function showLoginForm()
814
    {
815
        $this->app->template->trySet('page_title', 'Login');
816
        if ($this->app->layout && $this->login_layout_class) {
817
            $this->app->layout->destroy();
818
            $this->app->add($this->login_layout_class);
819
            /** @type Page $p */
820
            $p = $this->app->layout->add('Page', null, null, array('page/login'));
821
        } else {
822
            /** @type Page $p */
823
            $p = $this->app->add('Page', null, null, array('page/login'));
824
        }
825
        $this->app->page_object = $p;
826
827
        // hook: createForm use this to build basic login form
828
        $this->form = $this->hook('createForm', array($p));
829
830
        // If no hook, build standard form
831
        if (!is_object($this->form)) {
832
            $this->form = $this->createForm($p);
833
        }
834
835
        $this->hook('updateForm');
836
        $f = $this->form;
837
        if ($f->isSubmitted()) {
838
            $id = $this->verifyCredentials($f->get('username'), $f->get('password'));
839
            if ($id) {
840
                $this->loginByID($id);
841
                $this->loggedIn($f->get('username'), $f->get('password'));
842
                exit;
843
            }
844
            $f->getElement('password')->displayFieldError('Incorrect login information');
845
        }
846
847
        return $p;
848
    }
849
850
    /**
851
     * Do not override this function.
852
     */
853
    public function processLogin()
854
    {
855
        $this->memorizeURL();
856
        $this->app->template->tryDel('Menu');
857
        $this->showLoginForm();
858
859
        $this->app->hook('post-init');
860
        $this->app->hook('pre-exec');
861
862
        if (isset($_GET['submit']) && $_POST) {
863
            $this->app->hook('submitted');
864
        }
865
866
        $this->app->hook('post-submit');
867
        $this->app->execute();
868
        exit;
869
    }
870
}
871