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 classes like UsersController 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 UsersController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class UsersController extends AppController |
||
20 | { |
||
21 | use MailerAwareTrait; |
||
22 | |||
23 | /** |
||
24 | * Initialize handle. |
||
25 | * |
||
26 | * @return void |
||
27 | */ |
||
28 | public function initialize() |
||
42 | |||
43 | /** |
||
44 | * BeforeFilter handle. |
||
45 | * |
||
46 | * @param Event $event The beforeFilter event that was fired. |
||
47 | * |
||
48 | * @return void |
||
49 | */ |
||
50 | public function beforeFilter(Event $event) |
||
56 | |||
57 | /** |
||
58 | * Display all Users. |
||
59 | * |
||
60 | * @return void |
||
61 | */ |
||
62 | View Code Duplication | public function index() |
|
63 | { |
||
64 | $this->paginate = [ |
||
65 | 'maxLimit' => Configure::read('User.user_per_page') |
||
66 | ]; |
||
67 | $users = $this->Users |
||
68 | ->find() |
||
69 | ->contain([ |
||
70 | 'Groups' |
||
71 | ]) |
||
72 | ->order([ |
||
73 | 'Users.created' => 'desc' |
||
74 | ]); |
||
75 | |||
76 | $users = $this->paginate($users); |
||
77 | |||
78 | $this->set(compact('users')); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Login and register page. |
||
83 | * |
||
84 | * @return \Cake\Network\Response|void |
||
85 | */ |
||
86 | public function login() |
||
87 | { |
||
88 | //Handle Maintenances |
||
89 | if (Configure::read('User.Login.enabled') === false) { |
||
90 | $this->Flash->error(__("The Login action is disabled for the moment, please try again later.")); |
||
91 | } |
||
92 | |||
93 | if (Configure::read('User.Register.enabled') === false && Configure::read('Site.maintenance') === false) { |
||
94 | $this->Flash->error(__("The Register action is disabled for the moment, please try again later.")); |
||
95 | } |
||
96 | |||
97 | if (Configure::read('Site.maintenance') === true) { |
||
98 | $this->Flash->error(__("While the site is in maintenance, you can not register a new account.")); |
||
99 | } |
||
100 | |||
101 | if ($this->request->is('post')) { |
||
102 | $method = !is_null($this->request->getData('method')) ? $this->request->getData('method') : false; |
||
103 | |||
104 | switch ($method) { |
||
105 | case "login": |
||
106 | if (Configure::read('User.Login.enabled') === false) { |
||
107 | $userRegister = $userRegister = $this->Users->newEntity($this->request->getParsedBody()); |
||
108 | |||
109 | break; |
||
110 | } |
||
111 | $userLogin = $this->Auth->identify(); |
||
112 | |||
113 | if ($userLogin) { |
||
114 | if ($userLogin['is_deleted'] == true) { |
||
115 | $this->Flash->error(__("This account has been deleted.")); |
||
116 | |||
117 | $userRegister = $this->Users->newEntity($this->request->getParsedBody()); |
||
118 | |||
119 | break; |
||
120 | } |
||
121 | |||
122 | //Check the 2FA if the user has enabled it. |
||
123 | if ($userLogin['two_factor_auth_enabled'] == true && $this->TwoFactorAuth->isAuthorized($userLogin['id']) === false) { |
||
124 | //Write the cookie |
||
125 | $cookie = base64_encode(Security::encrypt($userLogin['id'], Configure::read('Security.key'))); |
||
126 | $this->Cookie->configKey('CookieTfa', [ |
||
127 | 'expires' => '+1 hour', |
||
128 | 'httpOnly' => true |
||
129 | ]); |
||
130 | $this->Cookie->write('CookieTfa', $cookie); |
||
131 | |||
132 | return $this->redirect(['action' => 'tfa']); |
||
133 | } |
||
134 | |||
135 | $this->_handleLogin($userLogin); |
||
136 | |||
137 | $this->Auth->setUser($userLogin); |
||
138 | |||
139 | $user = $this->Users->newEntity($userLogin, ['accessibleFields' => ['id' => true]]); |
||
140 | $user->isNew(false); |
||
141 | |||
142 | $user->last_login = new Time(); |
||
143 | $user->last_login_ip = $this->request->clientIp(); |
||
144 | |||
145 | $this->Users->save($user); |
||
146 | |||
147 | //Cookies. |
||
148 | $this->Cookie->configKey('CookieAuth', [ |
||
149 | 'expires' => '+1 year', |
||
150 | 'httpOnly' => true |
||
151 | ]); |
||
152 | $this->Cookie->write('CookieAuth', [ |
||
153 | 'username' => $this->request->getData('username'), |
||
154 | 'password' => $this->request->getData('password') |
||
155 | ]); |
||
156 | |||
157 | //Badge Event. |
||
158 | $this->eventManager()->attach(new Badges($this)); |
||
159 | $user = new Event('Model.Users.register', $this, [ |
||
160 | 'user' => $user |
||
161 | ]); |
||
162 | $this->eventManager()->dispatch($user); |
||
163 | |||
164 | $url = $this->Auth->redirectUrl(); |
||
165 | View Code Duplication | if (substr($this->Auth->redirectUrl(), -5) == 'login') { |
|
166 | $url = ['controller' => 'pages', 'action' => 'home']; |
||
167 | } |
||
168 | |||
169 | return $this->redirect($url); |
||
170 | } |
||
171 | |||
172 | $user = $this->Users |
||
173 | ->find() |
||
174 | ->where([ |
||
175 | 'username' => $this->request->getData('username') |
||
176 | ]) |
||
177 | ->select([ |
||
178 | 'id', |
||
179 | 'group_id', |
||
180 | 'username', |
||
181 | 'email' |
||
182 | ]) |
||
183 | ->first(); |
||
184 | |||
185 | if (!is_null($user)) { |
||
186 | //Users Event. |
||
187 | $this->eventManager()->attach(new Users()); |
||
188 | $event = new Event('Users.login.failed', $this, [ |
||
189 | 'user_id' => $user->id, |
||
190 | 'username' => $user->username, |
||
191 | 'group_id' => $user->group_id, |
||
192 | 'user_ip' => $this->request->clientIp(), |
||
193 | 'user_email' => $user->email, |
||
194 | 'user_agent' => $this->request->header('User-Agent'), |
||
195 | 'action' => 'user.connection.manual.failed' |
||
196 | ]); |
||
197 | $this->eventManager()->dispatch($event); |
||
198 | } |
||
199 | |||
200 | $this->Flash->error(__("Your username or password doesn't match.")); |
||
201 | |||
202 | $userRegister = $this->Users->newEntity($this->request->getParsedBody()); |
||
203 | |||
204 | break; |
||
205 | |||
206 | case "register": |
||
207 | $userRegister = $this->Users->newEntity($this->request->getParsedBody(), ['validate' => 'create']); |
||
208 | |||
209 | //Handle Maintenances |
||
210 | if (Configure::read('Site.maintenance') === true || Configure::read('User.Register.enabled') === false) { |
||
211 | break; |
||
212 | } |
||
213 | |||
214 | $userRegister->register_ip = $this->request->clientIp(); |
||
215 | $userRegister->last_login_ip = $this->request->clientIp(); |
||
216 | $userRegister->last_login = new Time(); |
||
217 | |||
218 | if ($this->Recaptcha->verify() || Configure::read('Recaptcha.bypass') === true) { |
||
219 | if ($this->Users->save($userRegister)) { |
||
220 | $user = $this->Auth->identify(); |
||
221 | |||
222 | if ($user) { |
||
223 | $this->Auth->setUser($user); |
||
224 | } |
||
225 | |||
226 | $user = $this->Users->get($user['id']); |
||
227 | |||
228 | //Statistics Event. |
||
229 | $this->eventManager()->attach(new Statistics()); |
||
230 | $stats = new Event('Model.Users.register', $this); |
||
231 | $this->eventManager()->dispatch($stats); |
||
232 | |||
233 | //Notification Events. |
||
234 | $this->eventManager()->attach(new Notifications()); |
||
235 | $event = new Event('Model.Notifications.new', $this, [ |
||
236 | 'user_id' => $user->id, |
||
237 | 'type' => 'bot' |
||
238 | ]); |
||
239 | $this->eventManager()->dispatch($event); |
||
240 | |||
241 | $viewVars = [ |
||
242 | 'user' => $user, |
||
243 | 'name' => $user->full_name |
||
244 | ]; |
||
245 | |||
246 | $this->getMailer('User')->send('register', [$user, $viewVars]); |
||
247 | |||
248 | $this->Flash->success(__("Your account has been created successfully !")); |
||
249 | |||
250 | $url = $this->Auth->redirectUrl(); |
||
251 | View Code Duplication | if (substr($this->Auth->redirectUrl(), -5) == 'login') { |
|
252 | $url = ['controller' => 'pages', 'action' => 'home']; |
||
253 | } |
||
254 | |||
255 | return $this->redirect($url); |
||
256 | } |
||
257 | |||
258 | $this->Flash->error(__("Please, correct your mistake.")); |
||
259 | } else { |
||
260 | $this->Flash->error(__("Please, correct your Captcha.")); |
||
261 | } |
||
262 | |||
263 | break; |
||
264 | } |
||
265 | } else { |
||
266 | //Save the referer URL before the user send the login/register request else it will delete the referer. |
||
267 | $this->request->session()->write('Auth.redirect', $this->referer()); |
||
268 | |||
269 | $userRegister = $this->Users->newEntity($this->request->getParsedBody(), ['validate' => 'create']); |
||
270 | } |
||
271 | |||
272 | if ($this->Auth->user()) { |
||
273 | return $this->redirect($this->Auth->redirectUrl()); |
||
274 | } |
||
275 | |||
276 | $this->set(compact('userRegister')); |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * Handle the login part after all verification. |
||
281 | * |
||
282 | * @param array $userLogin The user information. |
||
283 | * |
||
284 | * @return void |
||
285 | */ |
||
286 | protected function _handleLogin($userLogin) |
||
329 | |||
330 | /** |
||
331 | * Logout an user. |
||
332 | * |
||
333 | * @return \Cake\Network\Response |
||
334 | */ |
||
335 | public function logout() |
||
341 | |||
342 | /** |
||
343 | * Ask to the user the 2FA code and verify it. |
||
344 | * |
||
345 | * @return \Cake\Network\Response|void |
||
346 | */ |
||
347 | public function tfa() |
||
442 | |||
443 | /** |
||
444 | * Page to configure our account. |
||
445 | * |
||
446 | * @return void |
||
447 | */ |
||
448 | public function account() |
||
476 | |||
477 | /** |
||
478 | * Page to configure our settings. |
||
479 | * |
||
480 | * @return \Cake\Network\Response|void |
||
481 | */ |
||
482 | public function settings() |
||
483 | { |
||
484 | $user = $this->Users->get($this->Auth->user('id')); |
||
485 | |||
486 | $oldEmail = $user->email; |
||
487 | |||
488 | if ($this->request->is('put')) { |
||
489 | $method = ($this->request->getData('method')) ? $this->request->getData('method') : false; |
||
490 | |||
491 | switch ($method) { |
||
492 | case "email": |
||
493 | if (is_null($this->request->getData('email'))) { |
||
494 | $this->set(compact('user', 'oldEmail')); |
||
495 | |||
496 | return $this->redirect(['action' => 'settings']); |
||
497 | } |
||
498 | |||
499 | $this->Users->patchEntity($user, $this->request->getParsedBody(), ['validate' => 'settings']); |
||
500 | |||
501 | View Code Duplication | if ($this->Users->save($user)) { |
|
502 | $oldEmail = $this->request->getData('email'); |
||
503 | |||
504 | //Logs Event. |
||
505 | $this->eventManager()->attach(new Logs()); |
||
506 | $event = new Event('Log.User', $this, [ |
||
507 | 'user_id' => $user->id, |
||
508 | 'username' => $user->username, |
||
509 | 'user_ip' => $this->request->clientIp(), |
||
510 | 'user_agent' => $this->request->header('User-Agent'), |
||
511 | 'action' => 'user.email' |
||
512 | ]); |
||
513 | $this->eventManager()->dispatch($event); |
||
514 | |||
515 | $this->Flash->success(__("Your E-mail has been changed !")); |
||
516 | } |
||
517 | break; |
||
518 | |||
519 | case "password": |
||
520 | $data = $this->request->getParsedBody(); |
||
521 | if (!isset($data['old_password']) || !isset($data['password']) || !isset($data['password_confirm'])) { |
||
522 | $this->set(compact('user', 'oldEmail')); |
||
523 | |||
524 | return $this->Flash->error(__("Please, complete all fields !")); |
||
525 | } |
||
526 | |||
527 | View Code Duplication | if (!(new DefaultPasswordHasher)->check($data['old_password'], $user->password)) { |
|
528 | $this->set(compact('user', 'oldEmail')); |
||
529 | |||
530 | return $this->Flash->error(__("Your old password don't match !")); |
||
531 | } |
||
532 | |||
533 | $this->Users->patchEntity($user, $this->request->getParsedBody(), ['validate' => 'settings']); |
||
534 | View Code Duplication | if ($this->Users->save($user)) { |
|
535 | //Logs Event. |
||
536 | $this->eventManager()->attach(new Logs()); |
||
537 | $event = new Event('Log.User', $this, [ |
||
538 | 'user_id' => $user->id, |
||
539 | 'username' => $user->username, |
||
540 | 'user_ip' => $this->request->clientIp(), |
||
541 | 'user_agent' => $this->request->header('User-Agent'), |
||
542 | 'action' => 'user.password.change' |
||
543 | ]); |
||
544 | $this->eventManager()->dispatch($event); |
||
545 | |||
546 | $this->Flash->success(__("Your password has been changed !")); |
||
547 | } |
||
548 | break; |
||
549 | } |
||
550 | } |
||
551 | |||
552 | $this->set(compact('user', 'oldEmail')); |
||
553 | } |
||
554 | |||
555 | /** |
||
556 | * View a profile page of an user. |
||
557 | * |
||
558 | * @return \Cake\Network\Response|void |
||
559 | */ |
||
560 | public function profile() |
||
618 | |||
619 | /** |
||
620 | * Delete an user with all his comments, articles and likes. |
||
621 | * |
||
622 | * @return \Cake\Network\Response |
||
623 | */ |
||
624 | public function delete() |
||
625 | { |
||
626 | if (!$this->request->is('post')) { |
||
627 | return $this->redirect(['action' => 'settings']); |
||
628 | } |
||
629 | |||
630 | $user = $this->Users->get($this->Auth->user('id')); |
||
631 | |||
632 | View Code Duplication | if (!(new DefaultPasswordHasher)->check($this->request->getData('password'), $user->password)) { |
|
633 | $this->Flash->error(__("Your password doesn't match !")); |
||
634 | |||
635 | return $this->redirect(['action' => 'settings']); |
||
636 | } |
||
637 | |||
638 | $user->is_deleted = true; |
||
639 | |||
640 | if ($this->Users->save($user)) { |
||
641 | $this->Flash->success(__("Your account has been deleted successfully ! Thanks for your visit !")); |
||
642 | |||
643 | return $this->redirect($this->Auth->logout()); |
||
644 | } |
||
645 | |||
646 | $this->Flash->error(__("Unable to delete your account, please try again.")); |
||
647 | |||
648 | return $this->redirect(['action' => 'settings']); |
||
649 | } |
||
650 | |||
651 | /** |
||
652 | * Display all notifications related to the user. |
||
653 | * |
||
654 | * @return void |
||
655 | */ |
||
656 | View Code Duplication | public function notifications() |
|
657 | { |
||
658 | $this->loadModel('Notifications'); |
||
659 | |||
660 | $this->paginate = [ |
||
661 | 'maxLimit' => Configure::read('User.notifications_per_page') |
||
662 | ]; |
||
663 | |||
664 | $notifications = $this->Notifications |
||
665 | ->find() |
||
666 | ->where([ |
||
667 | 'user_id' => $this->Auth->user('id') |
||
668 | ]) |
||
669 | ->order([ |
||
670 | 'is_read' => 'ASC', |
||
671 | 'created' => 'DESC' |
||
672 | ]) |
||
673 | ->find('map', [ |
||
674 | 'session' => $this->request->session() |
||
675 | ]); |
||
676 | |||
677 | $notifications = $this->paginate($notifications); |
||
678 | |||
679 | $this->set(compact('notifications')); |
||
680 | } |
||
681 | |||
682 | /** |
||
683 | * Display the form to reset the password. |
||
684 | * |
||
685 | * @return \Cake\Network\Response|void |
||
686 | */ |
||
687 | public function forgotPassword() |
||
688 | { |
||
689 | if ($this->Auth->user()) { |
||
690 | return $this->redirect(['controller' => 'pages', 'action' => 'home']); |
||
691 | } |
||
692 | |||
693 | if ($this->request->is('post')) { |
||
694 | $user = $this->Users |
||
695 | ->find() |
||
696 | ->where([ |
||
697 | 'Users.email' => $this->request->getData('email') |
||
698 | ]) |
||
699 | ->first(); |
||
700 | |||
701 | if (is_null($user)) { |
||
702 | $this->Flash->error(__("This E-mail doesn't exist or the account has been deleted.")); |
||
703 | |||
704 | $this->set(compact('user')); |
||
705 | |||
706 | return; |
||
707 | } |
||
708 | |||
709 | if (!$this->Recaptcha->verify()) { |
||
710 | $this->Flash->error(__("Please, correct your Captcha.")); |
||
711 | |||
712 | $this->set(compact('user')); |
||
713 | |||
714 | return; |
||
715 | } |
||
716 | |||
717 | //Generate the unique code |
||
718 | $code = md5(rand() . uniqid() . time()); |
||
719 | |||
720 | //Update the user's information |
||
721 | $user->password_code = $code; |
||
722 | $user->password_code_expire = new Time(); |
||
723 | |||
724 | $this->Users->save($user); |
||
725 | |||
726 | $viewVars = [ |
||
727 | 'userId' => $user->id, |
||
728 | 'name' => $user->full_name, |
||
729 | 'username' => $user->username, |
||
730 | 'code' => $code |
||
731 | ]; |
||
732 | |||
733 | $this->getMailer('User')->send('forgotPassword', [$user, $viewVars]); |
||
734 | |||
735 | //Logs Event. |
||
736 | $this->eventManager()->attach(new Logs()); |
||
737 | $event = new Event('Log.User', $this, [ |
||
738 | 'user_id' => $user->id, |
||
739 | 'username' => $user->username, |
||
740 | 'user_ip' => $this->request->clientIp(), |
||
741 | 'user_agent' => $this->request->header('User-Agent'), |
||
742 | 'action' => 'user.password.reset' |
||
743 | ]); |
||
744 | $this->eventManager()->dispatch($event); |
||
745 | |||
746 | $this->Flash->success(__("An E-mail has been send to <strong>{0}</strong>. Please follow the instructions in the E-mail.", h($user->email))); |
||
747 | } |
||
748 | |||
749 | $this->set(compact('user')); |
||
750 | } |
||
751 | |||
752 | /** |
||
753 | * Display the form to reset his password. |
||
754 | * |
||
755 | * @return \Cake\Network\Response|void |
||
756 | */ |
||
757 | public function resetPassword() |
||
821 | |||
822 | /** |
||
823 | * Display the sessions and logs informations. |
||
824 | * |
||
825 | * @return void |
||
826 | */ |
||
827 | public function security() |
||
879 | } |
||
880 |
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.