Total Complexity | 57 |
Total Lines | 552 |
Duplicated Lines | 0 % |
Changes | 4 | ||
Bugs | 0 | Features | 0 |
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.
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 |
||
37 | class User extends ActiveRecord implements IdentityInterface, UserInterface |
||
38 | { |
||
39 | const STATUS_DELETED = 0; |
||
40 | const STATUS_ACTIVE = 10; |
||
41 | |||
42 | const ROLE_USER = 10; |
||
43 | |||
44 | const CONFIRMED_STRING = '_confirmed'; |
||
45 | |||
46 | public $user_behavior; |
||
47 | public $time; |
||
48 | |||
49 | private $export_order = [ |
||
50 | "date" => 0, |
||
51 | "behavior" => 1, |
||
52 | "category" => 2, |
||
53 | "question1" => 3, |
||
54 | "question2" => 4, |
||
55 | "question3" => 5, |
||
56 | ]; |
||
57 | |||
58 | public function __construct(UserBehaviorInterface $user_behavior, TimeInterface $time, $config = []) { |
||
59 | $this->time = $time; |
||
60 | parent::__construct($config); |
||
61 | } |
||
62 | |||
63 | public function afterFind() { |
||
64 | $this->time->timezone = $this->timezone; |
||
|
|||
65 | parent::afterFind(); |
||
66 | } |
||
67 | |||
68 | public function afterRefresh() { |
||
71 | } |
||
72 | |||
73 | //public function afterSave() { |
||
74 | // $this->time = new \common\components\Time($this->timezone); |
||
75 | // parent::afterSave(); |
||
76 | //} |
||
77 | |||
78 | /** |
||
79 | * @inheritdoc |
||
80 | * @codeCoverageIgnore |
||
81 | */ |
||
82 | |||
83 | public function behaviors() |
||
84 | { |
||
85 | return [ |
||
86 | 'timestamp' => [ |
||
87 | 'class' => yii\behaviors\TimestampBehavior::class, |
||
88 | 'attributes' => [ |
||
89 | ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], |
||
90 | ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], |
||
91 | ], |
||
92 | ], |
||
93 | ]; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * @inheritdoc |
||
98 | */ |
||
99 | public function rules() |
||
100 | { |
||
101 | return [ |
||
102 | ['status', 'default', 'value' => self::STATUS_ACTIVE], |
||
103 | ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]], |
||
104 | |||
105 | ['role', 'default', 'value' => self::ROLE_USER], |
||
106 | ['role', 'in', 'range' => [self::ROLE_USER]], |
||
107 | ]; |
||
108 | } |
||
109 | |||
110 | public function getPartnerEmails() { |
||
111 | return [ |
||
112 | $this->partner_email1, |
||
113 | $this->partner_email2, |
||
114 | $this->partner_email3, |
||
115 | ]; |
||
116 | } |
||
117 | |||
118 | /** |
||
119 | * @inheritdoc |
||
120 | * @codeCoverageIgnore |
||
121 | */ |
||
122 | public static function findIdentity($id) |
||
123 | { |
||
124 | return static::findOne($id); |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * @inheritdoc |
||
129 | * @codeCoverageIgnore |
||
130 | */ |
||
131 | public static function findIdentityByAccessToken($token, $type = null) |
||
132 | { |
||
133 | throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Finds user by email |
||
138 | * |
||
139 | * @param string $email |
||
140 | * @return static|null |
||
141 | */ |
||
142 | public function findByEmail($email) |
||
143 | { |
||
144 | return $this->find()->where(['email' => $email, 'status' => self::STATUS_ACTIVE])->one(); |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Finds user by password reset token |
||
149 | * |
||
150 | * @param string $token password reset token |
||
151 | * @return static|null |
||
152 | */ |
||
153 | public function findByPasswordResetToken($token) |
||
154 | { |
||
155 | if(!$this->isTokenCurrent($token)) { |
||
156 | return null; |
||
157 | } |
||
158 | |||
159 | return $this->find()->where([ |
||
160 | 'password_reset_token' => $token, |
||
161 | 'status' => self::STATUS_ACTIVE, |
||
162 | ])->one(); |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Finds user by email verification token |
||
167 | * |
||
168 | * @param string $token email verification token |
||
169 | * @return static|null |
||
170 | */ |
||
171 | public function findByVerifyEmailToken($token) |
||
172 | { |
||
173 | if($this->isTokenConfirmed($token)) return null; |
||
174 | |||
175 | $user = $this->find()->where([ |
||
176 | 'verify_email_token' => [$token, $token . self::CONFIRMED_STRING], |
||
177 | 'status' => self::STATUS_ACTIVE, |
||
178 | ])->one(); |
||
179 | |||
180 | if($user) { |
||
181 | if(!$this->isTokenConfirmed($token) && |
||
182 | !$this->isTokenCurrent($token, 'user.verifyAccountTokenExpire')) { |
||
183 | return null; |
||
184 | } |
||
185 | } |
||
186 | |||
187 | return $user; |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * Finds user by email change token |
||
192 | * |
||
193 | * @param string $token email change token |
||
194 | * @return static|null |
||
195 | */ |
||
196 | public function findByChangeEmailToken($token) |
||
197 | { |
||
198 | $user = static::find()->where([ |
||
199 | 'change_email_token' => $token, |
||
200 | 'status' => self::STATUS_ACTIVE, |
||
201 | ])->one(); |
||
202 | |||
203 | if($user) { |
||
204 | if(!$user->isTokenCurrent($token, 'user.verifyAccountTokenExpire')) { |
||
205 | return null; |
||
206 | } |
||
207 | } |
||
208 | |||
209 | return $user; |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * Finds out if a token is current or expired |
||
214 | * |
||
215 | * @param string $token verification token |
||
216 | * @param string $paramPath Yii app param path |
||
217 | * @return boolean |
||
218 | */ |
||
219 | public function isTokenCurrent($token, String $paramPath = 'user.passwordResetTokenExpire') { |
||
228 | } |
||
229 | |||
230 | /* |
||
231 | * Checks if $token ends with the $match string |
||
232 | * |
||
233 | * @param string $token verification token (the haystack) |
||
234 | * @param string $match the needle to search for |
||
235 | */ |
||
236 | public function isTokenConfirmed($token = null, String $match = self::CONFIRMED_STRING) { |
||
237 | if(is_null($token)) $token = $this->verify_email_token; |
||
238 | return substr($token, -strlen($match)) === $match; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * @inheritdoc |
||
243 | * @codeCoverageIgnore |
||
244 | */ |
||
245 | public function getId() |
||
246 | { |
||
247 | return $this->getPrimaryKey(); |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * @inheritdoc |
||
252 | * @codeCoverageIgnore |
||
253 | */ |
||
254 | public function getAuthKey() |
||
255 | { |
||
256 | return $this->auth_key; |
||
257 | } |
||
258 | |||
259 | public function getTimezone() { |
||
260 | return $this->timezone; |
||
261 | } |
||
262 | |||
263 | public function isVerified() { |
||
264 | if(is_null($this->verify_email_token)) { |
||
265 | // for old users who verified their accounts before the addition of |
||
266 | // '_confirmed' to the token |
||
267 | return true; |
||
268 | } else { |
||
269 | return !!$this->verify_email_token && $this->isTokenConfirmed($this->verify_email_token); |
||
270 | } |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * @inheritdoc |
||
275 | */ |
||
276 | public function validateAuthKey($authKey) |
||
277 | { |
||
278 | return $this->getAuthKey() === $authKey; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Validates password |
||
283 | * |
||
284 | * @param string $password password to validate |
||
285 | * @return boolean if password provided is valid for current user |
||
286 | */ |
||
287 | public function validatePassword($password) |
||
288 | { |
||
289 | return Yii::$app |
||
290 | ->getSecurity() |
||
291 | ->validatePassword($password, $this->password_hash); |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * Generates password hash from password and sets it to the model |
||
296 | * |
||
297 | * @param string $password |
||
298 | */ |
||
299 | public function setPassword($password) |
||
300 | { |
||
301 | $this->password_hash = Yii::$app |
||
302 | ->getSecurity() |
||
303 | ->generatePasswordHash($password); |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Generates email verification token |
||
308 | */ |
||
309 | public function generateVerifyEmailToken() |
||
310 | { |
||
311 | $this->verify_email_token = $this->getRandomVerifyString(); |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Confirms email verification token |
||
316 | */ |
||
317 | public function confirmVerifyEmailToken() |
||
318 | { |
||
319 | $this->verify_email_token .= self::CONFIRMED_STRING; |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Removes email verification token |
||
324 | */ |
||
325 | public function removeVerifyEmailToken() |
||
326 | { |
||
327 | $this->verify_email_token = null; |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Generates email change tokens |
||
332 | */ |
||
333 | public function generateChangeEmailToken() { |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * Removes change email token |
||
339 | */ |
||
340 | public function removeChangeEmailToken() |
||
341 | { |
||
342 | $this->change_email_token = null; |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Generates "remember me" authentication key |
||
347 | */ |
||
348 | public function generateAuthKey() |
||
349 | { |
||
350 | $this->auth_key = Yii::$app |
||
351 | ->getSecurity() |
||
352 | ->generateRandomString(); |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Generates new password reset token |
||
357 | */ |
||
358 | public function generatePasswordResetToken() |
||
359 | { |
||
360 | $this->password_reset_token = $this->getRandomVerifyString(); |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * Removes password reset token |
||
365 | */ |
||
366 | public function removePasswordResetToken() |
||
367 | { |
||
368 | $this->password_reset_token = null; |
||
369 | } |
||
370 | |||
371 | /* |
||
372 | * sendEmailReport() |
||
373 | * |
||
374 | * @param $date String a date string in YYYY-mm-dd format. The desired check-in date to send an email report of. Normally just today. |
||
375 | * @return boolean whether or not it succeeds. It will return false if the user's specified criteria are not met (or if the user did not select any behaviors for the given day) |
||
376 | * |
||
377 | * This is the function that sends email reports. It can send an email report |
||
378 | * for whichever `$date` is passed in. It checks if the user's specified |
||
379 | * criteria are met before it sends any email. It sends email to every |
||
380 | * partner email address the user has set. |
||
381 | */ |
||
382 | public function sendEmailReport($date) { |
||
383 | if(!$this->send_email) return false; // no partner emails set |
||
384 | list($start, $end) = $this->time->getUTCBookends($date); |
||
385 | |||
386 | $user_behavior = Yii::$container->get(UserBehaviorInterface::class); |
||
387 | $checkins_last_month = $user_behavior->getCheckInBreakdown(); |
||
388 | |||
389 | // we should only proceed with sending the email if the user |
||
390 | // scored above their set email threshold (User::email_category) |
||
391 | $this_checkin = $checkins_last_month[$date]; // gets the check-in |
||
392 | if(!$this_checkin) return false; // sanity check |
||
393 | $highest_cat_data = end($this_checkin); // gets the data for the highest category from the check-in |
||
394 | if(!$highest_cat_data) return false; // another sanity check |
||
395 | $highest_cat_idx = key($this_checkin); // gets the key of the highest category |
||
396 | |||
397 | // if the highest category they reached today was less than |
||
398 | // the category threshold they have set, don't send the email |
||
399 | if($highest_cat_idx < $this->email_category) return false; |
||
400 | |||
401 | $user_behaviors = $user_behavior->getByDate(Yii::$app->user->id, $date); |
||
402 | |||
403 | $question = Yii::$container->get(\common\interfaces\QuestionInterface::class); |
||
404 | $user_questions = $question->getByUser(Yii::$app->user->id, $date); |
||
405 | |||
406 | $graph = Yii::$container |
||
407 | ->get(\common\components\Graph::class) |
||
408 | ->create($checkins_last_month); |
||
409 | |||
410 | $messages = []; |
||
411 | foreach($this->getPartnerEmails() as $email) { |
||
412 | if($email) { |
||
413 | $messages[] = Yii::$app->mailer->compose('checkinReport', [ |
||
414 | 'user' => $this, |
||
415 | 'email' => $email, |
||
416 | 'date' => $date, |
||
417 | 'user_behaviors' => $user_behaviors, |
||
418 | 'questions' => $user_questions, |
||
419 | 'chart_content' => $graph, |
||
420 | 'categories' => \common\models\Category::$categories, |
||
421 | 'behaviors_list' => \common\models\Behavior::$behaviors, |
||
422 | ])->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name]) |
||
423 | ->setReplyTo($this->email) |
||
424 | ->setSubject($this->email." has completed a Faster Scale check-in") |
||
425 | ->setTo($email); |
||
426 | } |
||
427 | } |
||
428 | |||
429 | return Yii::$app->mailer->sendMultiple($messages); |
||
430 | } |
||
431 | |||
432 | public function getExportData() { |
||
433 | $query = (new Query) |
||
434 | ->select( |
||
435 | 'l.id, |
||
436 | l.date AS "date", |
||
437 | l.custom_behavior AS "custom_behavior", |
||
438 | l.behavior_id AS "behavior_id", |
||
439 | l.category_id AS "category_id", |
||
440 | (SELECT q1.answer |
||
441 | FROM question q1 |
||
442 | WHERE q1.question = 1 |
||
443 | AND q1.user_behavior_id = l.id) AS "question1", |
||
444 | (SELECT q1.answer |
||
445 | FROM question q1 |
||
446 | WHERE q1.question = 2 |
||
447 | AND q1.user_behavior_id = l.id) AS "question2", |
||
448 | (SELECT q1.answer |
||
449 | FROM question q1 |
||
450 | WHERE q1.question = 3 |
||
451 | AND q1.user_behavior_id = l.id) AS "question3"') |
||
452 | ->from('user_behavior_link l') |
||
453 | ->join("LEFT JOIN", "question q", "l.id = q.user_behavior_id") |
||
454 | ->where('l.user_id=:user_id', ["user_id" => Yii::$app->user->id]) |
||
455 | ->groupBy('l.id, |
||
456 | l.date, |
||
457 | "question1", |
||
458 | "question2", |
||
459 | "question3"') |
||
460 | ->orderBy('l.date DESC'); |
||
461 | |||
462 | return $query |
||
463 | ->createCommand() |
||
464 | ->query(); |
||
465 | |||
466 | /* Plaintext Query |
||
467 | SELECT l.id, |
||
468 | l.date AS "date", |
||
469 | l.custom_behavior AS "custom_behavior", |
||
470 | l.behavior_id AS "behavior_id", |
||
471 | (SELECT q1.answer |
||
472 | FROM question q1 |
||
473 | WHERE q1.question = 1 |
||
474 | AND q1.user_behavior_id = l.id) AS "question1", |
||
475 | (SELECT q1.answer |
||
476 | FROM question q1 |
||
477 | WHERE q1.question = 2 |
||
478 | AND q1.user_behavior_id = l.id) AS "question2", |
||
479 | (SELECT q1.answer |
||
480 | FROM question q1 |
||
481 | WHERE q1.question = 3 |
||
482 | AND q1.user_behavior_id = l.id) AS "question3" |
||
483 | FROM user_behavior_link l |
||
484 | LEFT JOIN question q |
||
485 | ON l.id = q.user_behavior_id |
||
486 | WHERE l.user_id = 1 |
||
487 | GROUP BY l.id, |
||
488 | l.date, |
||
489 | l.custom_behavior, |
||
490 | "question1", |
||
491 | "question2", |
||
492 | "question3", |
||
493 | ORDER BY l.date DESC; |
||
494 | */ |
||
495 | } |
||
496 | |||
497 | public function sendSignupNotificationEmail() { |
||
498 | return \Yii::$app->mailer->compose('signupNotification') |
||
499 | ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name]) |
||
500 | ->setTo(\Yii::$app->params['adminEmail']) |
||
501 | ->setSubject('A new user has signed up for '.\Yii::$app->name) |
||
502 | ->send(); |
||
503 | } |
||
504 | |||
505 | public function sendVerifyEmail() { |
||
506 | return \Yii::$app->mailer->compose('verifyEmail', ['user' => $this]) |
||
507 | ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name]) |
||
508 | ->setTo($this->email) |
||
509 | ->setSubject('Please verify your '.\Yii::$app->name .' account') |
||
510 | ->send(); |
||
511 | } |
||
512 | |||
513 | public function sendDeleteNotificationEmail() { |
||
514 | $messages = []; |
||
515 | foreach(array_merge([$this->email], $this->getPartnerEmails()) as $email) { |
||
516 | if($email) { |
||
517 | $messages[] = Yii::$app->mailer->compose('deleteNotification', [ |
||
518 | 'user' => $this, |
||
519 | 'email' => $email |
||
520 | ])->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name]) |
||
521 | ->setReplyTo($this->email) |
||
522 | ->setSubject($this->email." has deleted their The Faster Scale App account") |
||
523 | ->setTo($email); |
||
524 | } |
||
525 | } |
||
526 | |||
527 | return Yii::$app->mailer->sendMultiple($messages); |
||
528 | } |
||
529 | |||
530 | public function cleanExportRow($row) { |
||
531 | // change timestamp to local time (for the user) |
||
532 | $row['date'] = $this->time->convertUTCToLocal($row['date'], false); |
||
533 | |||
534 | // clean up things we don't need |
||
535 | $row['category'] = $row['category']['name']; |
||
536 | if(array_key_exists('behavior', $row)) { |
||
537 | $row['behavior'] = $row['behavior']['name']; |
||
538 | } else { |
||
539 | $row['behavior'] = $row['custom_behavior']; |
||
540 | } |
||
541 | unset($row['id']); |
||
542 | unset($row['behavior_id']); |
||
543 | unset($row['category_id']); |
||
544 | unset($row['custom_behavior']); |
||
545 | |||
546 | // sort the array into a sensible order |
||
547 | uksort($row, function($a, $b) { |
||
548 | return $this->export_order[$a] <=> $this->export_order[$b]; |
||
549 | }); |
||
550 | return $row; |
||
551 | } |
||
552 | |||
553 | /* |
||
554 | * getIdHash() |
||
555 | * |
||
556 | * @return String a user-identifying hash |
||
557 | * |
||
558 | * After generating the hash, we run it through a url-safe base64 encoding to |
||
559 | * shorten it. This generated string is currently used as an identifier in |
||
560 | * URLs, so the shorter the better. the url-safe version has been ripped from |
||
561 | * https://secure.php.net/manual/en/function.base64-encode.php#103849 |
||
562 | * |
||
563 | * It does NOT take into account the user's email address. The email address |
||
564 | * is changeable by the user. If that was used for this function, the |
||
565 | * returned hash would change when the user updates their email. That would |
||
566 | * obviously not be desirable. |
||
567 | */ |
||
568 | public function getIdHash() { |
||
569 | return rtrim( |
||
570 | strtr( |
||
571 | base64_encode( |
||
572 | hash('sha256', $this->id."::".$this->created_at, true) |
||
573 | ), |
||
574 | '+/', '-_'), |
||
575 | '='); |
||
576 | } |
||
577 | |||
578 | /* |
||
579 | * getRandomVerifyString() |
||
580 | * |
||
581 | * @return String a randomly generated string with a timestamp appended |
||
582 | * |
||
583 | * This is generally used for verification purposes: verifying an email, password change, or email address change. |
||
584 | */ |
||
585 | private function getRandomVerifyString() { |
||
589 | } |
||
590 | } |
||
591 |