Passed
Pull Request — master (#164)
by Corey
03:32
created

User::getBehaviorData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 8
nc 1
nop 1
1
<?php
2
namespace common\models;
3
4
use yii;
5
use yii\base\NotSupportedException;
6
use yii\db\Query;
7
use yii\web\IdentityInterface;
8
use \common\components\ActiveRecord;
9
use \common\interfaces\UserInterface;
10
use \common\interfaces\UserBehaviorInterface;
11
use \common\interfaces\QuestionInterface;
12
use \common\interfaces\TimeInterface;
13
14
/**
15
 * User model
16
 *
17
 * @property integer $id
18
 * @property string $password_hash
19
 * @property string $password_reset_token
20
 * @property string $verify_email_token
21
 * @property string $email
22
 * @property string $auth_key
23
 * @property integer $role
24
 * @property integer $status
25
 * @property integer $created_at
26
 * @property integer $updated_at
27
 * @property string $password write-only password
28
 * @property string $timezone
29
 * @property integer $email_threshold
30
 * @property string $partner_email1
31
 * @property string $partner_email2
32
 * @property string $partner_email3
33
 * @property boolean $expose_graph
34
 * @property string $desired_email
35
 * @property string $change_emaiL_token
36
 */
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 $question;
48
  public $time;
49
50
  public function __construct(UserBehaviorInterface $user_behavior, QuestionInterface $question, TimeInterface $time, $config = []) {
51
    $this->user_behavior = $user_behavior;
52
    $this->question = $question;
53
    $this->time = $time;
54
    parent::__construct($config);
55
  }
56
57
  public function afterFind() {
58
    $this->time = new \common\components\Time($this->timezone);
59
    parent::afterFind();
60
  }
61
62
  public function afterRefresh() {
63
    $this->time = new \common\components\Time($this->timezone);
64
    parent::afterRefresh();
65
  }
66
67
  //public function afterSave() {
68
  //  $this->time = new \common\components\Time($this->timezone);
69
  //  parent::afterSave();
70
  //}
71
72
  /**
73
   * @inheritdoc
74
   */
75
76
  public function behaviors()
77
  {
78
    return [
79
      'timestamp' => [
80
        'class' => 'yii\behaviors\TimestampBehavior',
81
        'attributes' => [
82
          ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
83
          ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
84
        ],
85
      ],
86
    ];
87
  }
88
89
  /**
90
   * @inheritdoc
91
   */
92
  public function rules()
93
  {
94
    return [
95
      ['status', 'default', 'value' => self::STATUS_ACTIVE],
96
      ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
97
98
      ['role', 'default', 'value' => self::ROLE_USER],
99
      ['role', 'in', 'range' => [self::ROLE_USER]],
100
    ];
101
  }
102
103
  public function getPartnerEmails() {
104
    return [
105
      $this->partner_email1,
106
      $this->partner_email2,
107
      $this->partner_email3,
108
    ];
109
  }
110
111
  /**
112
   * @inheritdoc
113
   */
114
  public static function findIdentity($id)
115
  {
116
    return static::findOne($id);
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::findOne($id) also could return the type yii\db\BaseActiveRecord which is incompatible with the return type mandated by yii\web\IdentityInterface::findIdentity() of yii\web\IdentityInterface.
Loading history...
117
  }
118
119
  /**
120
   * @inheritdoc
121
   */
122
  public static function findIdentityByAccessToken($token, $type = null)
123
  {
124
    throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
125
  }
126
127
  /**
128
   * Finds user by email
129
   *
130
   * @param  string      $email
131
   * @return static|null
132
   */
133
  public function findByEmail($email)
134
  {
135
    return $this->findOne(['email' => $email, 'status' => self::STATUS_ACTIVE]);
136
  }
137
138
  /**
139
   * Finds user by password reset token
140
   *
141
   * @param  string      $token password reset token
142
   * @return static|null
143
   */
144
  public function findByPasswordResetToken($token)
145
  {
146
    if(!$this->isTokenCurrent($token)) {
147
      return null;
148
    }
149
150
    return $this->findOne([
151
      'password_reset_token' => $token,
152
      'status' => self::STATUS_ACTIVE,
153
    ]);
154
  }
155
156
  /**
157
   * Finds user by email verification token
158
   *
159
   * @param  string      $token email verification token
160
   * @return static|null
161
   */
162
  public function findByVerifyEmailToken($token)
163
  {
164
    if($this->isTokenConfirmed($token)) return null;
165
166
    $user = $this->findOne([
167
      'verify_email_token' => [$token, $token . self::CONFIRMED_STRING],
168
      'status' => self::STATUS_ACTIVE,
169
    ]);
170
171
    if($user) {
172
      if(!$this->isTokenConfirmed($token) &&
173
         !$this->isTokenCurrent($token, 'user.verifyAccountTokenExpire')) {
174
        return null;
175
      }
176
    }
177
178
    return $user;
179
  }
180
181
  /**
182
   * Finds user by email change token
183
   *
184
   * @param  string      $token email change token
185
   * @return static|null
186
   */
187
  public function findByChangeEmailToken($token)
188
  {
189
    $user = static::findOne([
190
      'change_email_token' => $token,
191
      'status' => self::STATUS_ACTIVE,
192
    ]);
193
194
    if($user) {
195
      if(!$user->isTokenCurrent($token, 'user.verifyAccountTokenExpire')) {
196
        return null;
197
      }
198
    }
199
200
    return $user;
201
  }
202
203
  /**
204
   * Finds out if a token is current or expired
205
   *
206
   * @param  string      $token verification token
207
   * @param  string      $paramPath Yii app param path
208
   * @return boolean
209
   */
210
  public function isTokenCurrent($token, String $paramPath = 'user.passwordResetTokenExpire') {
211
    $expire = \Yii::$app->params[$paramPath];
212
    $parts = explode('_', $token);
213
    $timestamp = (int) end($parts);
214
    if ($timestamp + $expire < time()) {
215
      // token expired
216
      return false;
217
    }
218
    return true;
219
  }
220
221
  /*
222
   * Checks if $token ends with the $match string
223
   *
224
   * @param string    $token verification token (the haystack)
225
   * @param string    $match the needle to search for
226
   */
227
  public function isTokenConfirmed($token = null, String $match = self::CONFIRMED_STRING) {
228
    if(is_null($token)) $token = $this->verify_email_token;
229
    return substr($token, -strlen($match)) === $match;
230
  }
231
232
  /**
233
   * @inheritdoc
234
   */
235
  public function getId()
236
  {
237
    return $this->getPrimaryKey();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPrimaryKey() also could return the type array which is incompatible with the return type mandated by yii\web\IdentityInterface::getId() of integer|string.
Loading history...
238
  }
239
240
  /**
241
   * @inheritdoc
242
   */
243
  public function getAuthKey()
244
  {
245
    return $this->auth_key;
246
  }
247
248
  public function getTimezone() {
249
    return $this->timezone;
250
  }
251
252
  public function isVerified() {
253
    if(is_null($this->verify_email_token)) {
0 ignored issues
show
introduced by
The condition is_null($this->verify_email_token) is always false.
Loading history...
254
      // for old users who verified their accounts before the addition of
255
      // '_confirmed' to the token
256
      return true;
257
    } else {
258
      return !!$this->verify_email_token && $this->isTokenConfirmed($this->verify_email_token);
259
    }
260
  }
261
262
  /**
263
   * @inheritdoc
264
   */
265
  public function validateAuthKey($authKey)
266
  {
267
    return $this->getAuthKey() === $authKey;
268
  }
269
270
  /**
271
   * Validates password
272
   *
273
   * @param  string  $password password to validate
274
   * @return boolean if password provided is valid for current user
275
   */
276
  public function validatePassword($password)
277
  {
278
    return Yii::$app
279
      ->getSecurity()
280
      ->validatePassword($password, $this->password_hash);
281
  }
282
283
  /**
284
   * Generates password hash from password and sets it to the model
285
   *
286
   * @param string $password
287
   */
288
  public function setPassword($password)
289
  {
290
    $this->password_hash = Yii::$app
291
      ->getSecurity()
292
      ->generatePasswordHash($password);
293
  }
294
295
  /**
296
   * Generates email verification token
297
   */
298
  public function generateVerifyEmailToken()
299
  {
300
    $this->verify_email_token = $this->getRandomVerifyString();
301
  }
302
303
304
  public function confirmVerifyEmailToken()
305
  {
306
    $this->verify_email_token .= self::CONFIRMED_STRING;
307
  }
308
309
  /**
310
   * Removes email verification token
311
   */
312
  public function removeVerifyEmailToken()
313
  {
314
    $this->verify_email_token = null;
315
  }
316
317
  /**
318
   * Generates email change tokens
319
   */
320
  public function generateChangeEmailToken() {
321
    $this->change_email_token = $this->getRandomVerifyString();
0 ignored issues
show
Bug Best Practice introduced by
The property change_email_token does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
322
  }
323
324
  /**
325
   * Removes change email token
326
   */
327
  public function removeChangeEmailToken()
328
  {
329
    $this->change_email_token = null;
0 ignored issues
show
Bug Best Practice introduced by
The property change_email_token does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
330
  }
331
332
  /**
333
   * Generates "remember me" authentication key
334
   */
335
  public function generateAuthKey()
336
  {
337
    $this->auth_key = Yii::$app
338
      ->getSecurity()
339
      ->generateRandomString();
340
  }
341
342
  /**
343
   * Generates new password reset token
344
   */
345
  public function generatePasswordResetToken()
346
  {
347
    $this->password_reset_token = $this->getRandomVerifyString();
348
  }
349
350
  /**
351
   * Removes password reset token
352
   */
353
  public function removePasswordResetToken()
354
  {
355
    $this->password_reset_token = null;
356
  }
357
358
  public function sendEmailReport($date) {
359
    if(!$this->isPartnerEnabled()) return false; // no partner emails set
360
361
    list($start, $end) = $this->time->getUTCBookends($date);
362
363
    $scores_of_month   = $this->user_behavior->calculateScoresOfLastMonth();
0 ignored issues
show
Bug introduced by
The method calculateScoresOfLastMonth() does not exist on common\interfaces\UserBehaviorInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to common\interfaces\UserBehaviorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

363
    /** @scrutinizer ignore-call */ 
364
    $scores_of_month   = $this->user_behavior->calculateScoresOfLastMonth();
Loading history...
364
    $graph = Yii::$container
365
      ->get(\common\components\Graph::class)
366
      ->create($scores_of_month);
367
368
    $score          = $this->user_behavior->calculateScoreByUTCRange($start, $end);
0 ignored issues
show
Bug introduced by
The method calculateScoreByUTCRange() does not exist on common\interfaces\UserBehaviorInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to common\interfaces\UserBehaviorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

368
    /** @scrutinizer ignore-call */ 
369
    $score          = $this->user_behavior->calculateScoreByUTCRange($start, $end);
Loading history...
369
    $user_behaviors   = $this->getUserBehaviors($date);
370
    $user_questions = $this->getUserQuestions($date);
371
372
    $messages = [];
373
    foreach($this->getPartnerEmails() as $email) {
374
      if($email) {
375
        $messages[] = Yii::$app->mailer->compose('checkinReport', [
376
          'user'          => $this,
377
          'email'         => $email,
378
          'date'          => $date,
379
          'user_behaviors'  => $user_behaviors,
380
          'questions'     => $user_questions,
381
          'chart_content' => $graph,
382
          'categories'    => \common\models\Category::$categories,
383
          'score'         => $score,
384
          'behaviors_list'  => \common\models\Behavior::$behaviors,
385
        ])->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name])
386
        ->setReplyTo($this->email)
387
        ->setSubject($this->email." has scored high in The Faster Scale App")
388
        ->setTo($email);
389
      }
390
    }
391
392
    return Yii::$app->mailer->sendMultiple($messages);
393
  }
394
395
  public function getExportData() {
396
    $query = (new Query)
397
      ->select(
398
      'l.id,        
399
       l.date      AS "date",
400
       l.behavior_id AS "behavior_id",
401
       (SELECT q1.answer
402
        FROM question q1
403
        WHERE q1.question = 1
404
          AND q1.user_behavior_id = l.id) AS "question1",
405
       (SELECT q1.answer
406
        FROM question q1
407
        WHERE q1.question = 2
408
          AND q1.user_behavior_id = l.id) AS "question2",
409
       (SELECT q1.answer
410
        FROM question q1
411
        WHERE q1.question = 3
412
          AND q1.user_behavior_id = l.id) AS "question3"')
413
      ->from('user_behavior_link l')
414
      ->join("LEFT JOIN", "question q", "l.id = q.user_behavior_id")
415
      ->where('l.user_id=:user_id', ["user_id" => Yii::$app->user->id])
416
      ->groupBy('l.id,
417
          l.date,
418
          "question1",
419
          "question2",
420
          "question3"')
421
      ->orderBy('l.date DESC');
422
423
    return $query
424
      ->createCommand()
425
      ->query();
426
427
/* Plaintext Query
428
SELECT l.id,
429
       l.date      AS "date",
430
       l.behavior_id AS "behavior_id",
431
       (SELECT q1.answer
432
        FROM question q1
433
        WHERE q1.question = 1
434
          AND q1.user_behavior_id = l.id) AS "question1",
435
       (SELECT q1.answer
436
        FROM question q1
437
        WHERE q1.question = 2
438
          AND q1.user_behavior_id = l.id) AS "question2",
439
       (SELECT q1.answer
440
        FROM question q1
441
        WHERE q1.question = 3
442
          AND q1.user_behavior_id = l.id) AS "question3"
443
FROM   user_behavior_link l
444
       LEFT JOIN question q
445
         ON l.id = q.user_behavior_id
446
WHERE  l.user_id = 1
447
GROUP  BY l.id,
448
          l.date,
449
          "question1",
450
          "question2",
451
          "question3",
452
ORDER  BY l.date DESC;
453
*/
454
  }
455
456
  public function sendSignupNotificationEmail() {
457
    return \Yii::$app->mailer->compose('signupNotification')
458
      ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name])
459
      ->setTo(\Yii::$app->params['adminEmail'])
460
      ->setSubject('A new user has signed up for '.\Yii::$app->name)
461
      ->send();
462
  }
463
464
  public function sendVerifyEmail() {
465
    return \Yii::$app->mailer->compose('verifyEmail', ['user' => $this])
466
      ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name])
467
      ->setTo($this->email)
468
      ->setSubject('Please verify your '.\Yii::$app->name .' account')
469
      ->send();
470
  }
471
472
  public function sendDeleteNotificationEmail() {
473
    if($this->isPartnerEnabled()) return false; // no partner emails set
474
475
    $messages = [];
476
    foreach(array_merge([$this->email], $this->getPartnerEmails()) as $email) {
477
      if($email) {
478
        $messages[] = Yii::$app->mailer->compose('partnerDeleteNotification', [
479
          'user' => $this,
480
          'email' => $email
481
        ])->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name])
482
        ->setReplyTo($this->email)
483
        ->setSubject($this->email." has deleted their The Faster Scale App account")
484
        ->setTo($email);
485
      }
486
    }
487
488
    return Yii::$app->mailer->sendMultiple($messages);
489
  }
490
491
  public function getUserQuestions($local_date = null) {
492
    if(is_null($local_date)) $local_date = $this->time->getLocalDate();
493
    $questions = $this->getQuestionData($local_date);
494
    return $this->parseQuestionData($questions);
495
  }
496
497
  public function getUserBehaviors($local_date = null) {
498
    if(is_null($local_date)) $local_date = $this->time->getLocalDate();
499
500
    $behaviors = $this->getBehaviorData($local_date);
501
    $behaviors = $this->user_behavior::decorateWithCategory($behaviors);
0 ignored issues
show
Bug introduced by
The method decorateWithCategory() does not exist on common\interfaces\UserBehaviorInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to common\interfaces\UserBehaviorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

501
    /** @scrutinizer ignore-call */ 
502
    $behaviors = $this->user_behavior::decorateWithCategory($behaviors);
Loading history...
502
    return $this->parseBehaviorData($behaviors);
503
  }
504
505
  public function parseQuestionData($questions) {
506
    if(!$questions) return [];
507
508
    $question_answers = [];
509
    foreach($questions as $question) {
510
      $behavior = $question['behavior'];
511
512
      $question_answers[$behavior['id']]['question'] = [
513
        "id" => $behavior['id'],
514
        "title" => $behavior['name']
515
      ];
516
517
      $question_answers[$behavior['id']]["answers"][] = [
518
        "title" => $this->question::$QUESTIONS[$question['question']],
0 ignored issues
show
Bug introduced by
Accessing QUESTIONS on the interface common\interfaces\QuestionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
519
        "answer" => $question['answer']
520
      ];
521
    }
522
523
    return $question_answers;
524
  }
525
 
526
  public function parseBehaviorData($behaviors) {
527
    if(!$behaviors) return [];
528
529
    $opts_by_cat = [];
530
    foreach($behaviors as $behavior) {
531
      $indx = $behavior['behavior']['category_id'];
532
533
      $opts_by_cat[$indx]['category_name'] = $behavior['behavior']['category']['name'];
534
      $opts_by_cat[$indx]['behaviors'][] = [
535
        "id" => $behavior['behavior_id'],
536
        "name"=>$behavior['behavior']['name']];
537
    }
538
539
    return $opts_by_cat;
540
  }
541
542
  public function getQuestionData($local_date) {
543
    list($start, $end) = $this->time->getUTCBookends($local_date);
544
545
    $questions = $this->question->find()
546
      ->where("user_id=:user_id 
0 ignored issues
show
Bug introduced by
'user_id=:user_id ... AND date < :end_date' of type string is incompatible with the type array expected by parameter $condition of yii\db\QueryInterface::where(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

546
      ->where(/** @scrutinizer ignore-type */ "user_id=:user_id 
Loading history...
547
      AND date > :start_date 
548
      AND date < :end_date", 
549
    [
0 ignored issues
show
Unused Code introduced by
The call to yii\db\QueryInterface::where() has too many arguments starting with array('user_id' => yii::...t, ':end_date' => $end). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

549
      ->/** @scrutinizer ignore-call */ where("user_id=:user_id 

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
550
      "user_id" => Yii::$app->user->id, 
551
      ':start_date' => $start, 
552
      ":end_date" => $end
553
    ])
554
    ->asArray()
555
    ->all();
556
557
    $questions = $this->user_behavior::decorate($questions);
0 ignored issues
show
Bug introduced by
The method decorate() does not exist on common\interfaces\UserBehaviorInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to common\interfaces\UserBehaviorInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

557
    /** @scrutinizer ignore-call */ 
558
    $questions = $this->user_behavior::decorate($questions);
Loading history...
558
559
    return $questions;
560
  }
561
562
  public function getBehaviorData($local_date) {
563
    list($start, $end) = $this->time->getUTCBookends($local_date);
564
565
    return $this->user_behavior->find()
566
      ->where("user_id=:user_id 
0 ignored issues
show
Bug introduced by
'user_id=:user_id ... AND date < :end_date' of type string is incompatible with the type array expected by parameter $condition of yii\db\QueryInterface::where(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

566
      ->where(/** @scrutinizer ignore-type */ "user_id=:user_id 
Loading history...
567
      AND date > :start_date 
568
      AND date < :end_date", 
569
    [
0 ignored issues
show
Unused Code introduced by
The call to yii\db\QueryInterface::where() has too many arguments starting with array('user_id' => yii::...t, ':end_date' => $end). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

569
      ->/** @scrutinizer ignore-call */ where("user_id=:user_id 

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
570
      "user_id" => Yii::$app->user->id, 
571
      ':start_date' => $start, 
572
      ":end_date" => $end
573
    ])
574
    ->asArray()
575
    ->all();
576
  }
577
578
  public function isPartnerEnabled() {
579
    if((is_integer($this->email_threshold)
580
       && $this->email_threshold >= 0)
581
         && ($this->partner_email1
582
           || $this->partner_email2
583
           || $this->partner_email3)) {
584
      return true;
585
    }
586
    return false;
587
  }
588
589
  public function isOverThreshold($score) {
590
    if(!$this->isPartnerEnabled()) return false;
591
592
    $threshold = $this->email_threshold;
593
594
    return (!is_null($threshold) && $score > $threshold)
0 ignored issues
show
introduced by
The condition is_null($threshold) is always false.
Loading history...
595
            ? true
596
            : false;
597
  }
598
599
  public function cleanExportData($data) {
600
   $order = array_flip(["date", "behavior", "category", "question1", "question2", "question3"]);
601
602
   $ret = array_map(
603
     function($row) use ($order) {
604
       // change timestamp to local time (for the user)
605
       $row['date'] = $this->time->convertUTCToLocal($row['date'], false);
606
       
607
       // clean up things we don't need
608
       $row['category'] = $row['behavior']['category']['name'];
609
       $row['behavior'] = $row['behavior']['name'];
610
       unset($row['id']);
611
       unset($row['behavior_id']);
612
613
       // sort the array into a sensible order
614
       uksort($row, function($a, $b) use ($order) {
615
        return $order[$a] <=> $order[$b];
616
       });
617
       return $row;
618
     }, 
619
     $data
620
   );
621
   return $ret;
622
  }
623
624
  /*
625
   * getIdHash()
626
   *
627
   * @return String a user-identifying hash
628
   *
629
   * After generating the hash, we run it through a url-safe base64 encoding to
630
   * shorten it. This generated string is currently used as an identifier in
631
   * URLs, so the shorter the better. the url-safe version has been ripped from
632
   * https://secure.php.net/manual/en/function.base64-encode.php#103849
633
   *
634
   * It does NOT take into account the user's email address. The email address
635
   * is changeable by the user. If that was used for this function, the
636
   * returned hash would change when the user updates their email. That would
637
   * obviously not be desirable.
638
   */
639
  public function getIdHash() {
640
    return rtrim(
641
      strtr(
642
        base64_encode(
643
          hash('sha256', $this->id."::".$this->created_at, true)
644
        ),
645
      '+/', '-_'),
646
    '=');
647
  }
648
649
  /*
650
   * getRandomVerifyString()
651
   * 
652
   * @return String a randomly generated string with a timestamp appended
653
   *
654
   * This is generally used for verification purposes: verifying an email, password change, or email address change.
655
   */
656
  private function getRandomVerifyString() {
657
    return Yii::$app
658
      ->getSecurity()
659
      ->generateRandomString() . '_' . time();
660
  }
661
}
662