Test Failed
Pull Request — master (#166)
by Maximo
05:27
created

Users::resetPassword()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 12
ccs 0
cts 9
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Canvas\Models;
5
6
use Canvas\Traits\PermissionsTrait;
7
use Canvas\Traits\SubscriptionPlanLimitTrait;
8
use Phalcon\Cashier\Billable;
9
use Canvas\Exception\ServerErrorHttpException;
10
use Carbon\Carbon;
11
use Phalcon\Validation;
12
use Phalcon\Validation\Validator\Email;
0 ignored issues
show
Bug introduced by
The type Phalcon\Validation\Validator\Email was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use Phalcon\Validation\Validator\PresenceOf;
0 ignored issues
show
Bug introduced by
The type Phalcon\Validation\Validator\PresenceOf was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use Phalcon\Validation\Validator\Regex;
0 ignored issues
show
Bug introduced by
The type Phalcon\Validation\Validator\Regex was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use Phalcon\Validation\Validator\Uniqueness;
16
use Canvas\Traits\FileSystemModelTrait;
17
use Phalcon\Security\Random;
0 ignored issues
show
Bug introduced by
The type Phalcon\Security\Random was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use Baka\Database\Contracts\HashTableTrait;
19
use Canvas\Contracts\Notifications\NotifiableTrait;
20
use Phalcon\Traits\EventManagerAwareTrait;
21
use Phalcon\Di;
22
use Canvas\Auth\App as AppAuth;
23
use Exception;
24
use Canvas\Validations\PasswordValidation;
25
use Baka\Auth\Models\Users as BakUser;
26
use Canvas\Hashing\Password;
27
28
/**
29
 * Class Users.
30
 *
31
 * @package Canvas\Models
32
 *
33
 * @property Users $user
34
 * @property Config $config
35
 * @property Apps $app
36
 * @property Companies $defaultCompany
37
 * @property \Phalcon\Di $di
38
 */
39
class Users extends \Baka\Auth\Models\Users
40
{
41
    use PermissionsTrait;
42
    use Billable;
43
    use SubscriptionPlanLimitTrait;
44
    use FileSystemModelTrait;
45
    use HashTableTrait;
46
    use NotifiableTrait;
47
    use EventManagerAwareTrait;
48
49
    /**
50
     * Default Company Branch.
51
     *
52
     * @var integer
53
     */
54
    public $default_company_branch;
55
56
    /**
57
     * Roles id.
58
     *
59
     * @var integer
60
     */
61
    public $roles_id;
62
63
    /**
64
     * Stripe id.
65
     *
66
     * @var string
67
     */
68
    public $stripe_id;
69
70
    /**
71
     * Card last four numbers.
72
     *
73
     * @var integer
74
     */
75
    public $card_last_four;
76
77
    /**
78
     * Card Brand.
79
     *
80
     * @var integer
81
     */
82
    public $card_brand;
83
84
    /**
85
     * Trial end date.
86
     *
87
     * @var string
88
     */
89
    public $trial_ends_at;
90
91
    /**
92
     * Provide the app plan id
93
     * if the user is signing up a new company.
94
     *
95
     * @var integer
96
     */
97
    public $appPlanId = null;
98
99
    /**
100
     * Active subscription id.Not an actual table field, used temporarily.
101
     * @var string
102
     */
103
    public $active_subscription_id;
104
105
    /**
106
     * System Module Id.
107
     * @var integer
108
     */
109
    public $system_modules_id = 2;
110
111
    /**
112
     * User email activation code.
113
     *
114
     * @var string
115
     */
116
    public $user_activation_email;
117
118
    /**
119
     * Initialize method for model.
120
     */
121
    public function initialize()
122
    {
123
        $this->setSource('users');
124
125
        //overwrite parent relationships
126
        $this->hasOne('id', 'Baka\Auth\Models\Sessions', 'users_id', ['alias' => 'session']);
127
        $this->hasMany('id', 'Baka\Auth\Models\Sessions', 'users_id', ['alias' => 'sessions']);
128
        $this->hasMany('id', 'Baka\Auth\Models\SessionKeys', 'users_id', ['alias' => 'sessionKeys']);
129
        $this->hasMany('id', 'Baka\Auth\Models\Banlist', 'users_id', ['alias' => 'bans']);
130
        $this->hasMany('id', 'Baka\Auth\Models\Sessions', 'users_id', ['alias' => 'sessions']);
131
        $this->hasMany('id', 'Canvas\Models\UserConfig', 'users_id', ['alias' => 'config']);
132
        $this->hasMany('id', 'Canvas\Models\UserLinkedSources', 'users_id', ['alias' => 'sources']);
133
134
        $this->hasOne(
135
            'default_company',
136
            'Canvas\Models\Companies',
137
            'id',
138
            ['alias' => 'defaultCompany']
139
        );
140
141
        $this->hasOne(
142
            'default_company',
143
            'Canvas\Models\Companies',
144
            'id',
145
            ['alias' => 'currentCompany']
146
        );
147
148
        $this->hasOne(
149
            'id',
150
            'Canvas\Models\UserRoles',
151
            'users_id',
152
            ['alias' => 'permission']
153
        );
154
155
        $this->hasMany(
156
            'id',
157
            'Canvas\Models\UserRoles',
158
            'users_id',
159
            ['alias' => 'permissions']
160
        );
161
162
        $this->hasManyToMany(
163
            'id',
164
            'Canvas\Models\UserRoles',
165
            'users_id',
166
            'roles_id',
167
            'Canvas\Models\Roles',
168
            'id',
169
            [
170
                'alias' => 'roles',
171
                'params' => [
172
                    'limit' => 1,
173
                    'conditions' => 'Canvas\Models\UserRoles.apps_id = ' . $this->di->getApp()->getId(),
174
                ]
175
            ]
176
        );
177
178
        $this->hasMany(
179
            'id',
180
            'Canvas\Models\Subscription',
181
            'user_id',
182
            [
183
                'alias' => 'allSubscriptions',
184
                'params' => [
185
                    'conditions' => 'apps_id = ?0',
186
                    'bind' => [$this->di->getApp()->getId()],
187
                    'order' => 'id DESC'
188
                ]
189
            ]
190
        );
191
192
        $this->hasMany(
193
            'id',
194
            'Canvas\Models\UsersAssociatedApps',
195
            'users_id',
196
            [
197
                'alias' => 'companies',
198
                'params' => [
199
                    'conditions' => 'apps_id = ?0',
200
                    'bind' => [$this->di->getApp()->getId()],
201
                ]
202
            ]
203
        );
204
205
        $this->hasMany(
206
            'id',
207
            'Canvas\Models\UsersAssociatedApps',
208
            'users_id',
209
            [
210
                'alias' => 'apps',
211
            ]
212
        );
213
214
        $this->hasOne(
215
            'id',
216
            'Canvas\Models\UsersAssociatedApps',
217
            'users_id',
218
            [
219
                'alias' => 'app',
220
                'params' => [
221
                    'conditions' => 'apps_id = ?0',
222
                    'bind' => [Di::getDefault()->getApp()->getId()]
223
                ]
224
            ]
225
        );
226
227
        $this->hasMany(
228
            'id',
229
            'Canvas\Models\UserWebhooks',
230
            'users_id',
231
            ['alias' => 'userWebhook']
232
        );
233
234
        $systemModule = SystemModules::getSystemModuleByModelName(self::class);
235
        $this->hasOne(
236
            'id',
237
            'Canvas\Models\FileSystemEntities',
238
            'entity_id',
239
            [
240
                'alias' => 'files',
241
                'params' => [
242
                    'conditions' => 'system_modules_id = ?0',
243
                    'bind' => [$systemModule->getId()]
244
                ]
245
            ]
246
        );
247
248
        $this->hasOne(
249
            'id',
250
            'Canvas\Models\FileSystemEntities',
251
            'entity_id',
252
            [
253
                'alias' => 'photo',
254
                'params' => [
255
                    'conditions' => 'system_modules_id = ?0',
256
                    'bind' => [$systemModule->getId()]
257
                ]
258
            ]
259
        );
260
    }
261
262
    /**
263
     * Validations and business logic.
264
     */
265
    public function validation()
266
    {
267
        $validator = new Validation();
268
        $validator->add(
269
            'email',
270
            new Email([
271
                'field' => 'email',
272
                'required' => true,
273
            ])
274
        );
275
276
        $validator->add(
277
            'displayname',
278
            new PresenceOf([
279
                'field' => 'displayname',
280
                'required' => true,
281
            ])
282
        );
283
284
        // Unique values
285
        $validator->add(
286
            'email',
287
            new Uniqueness([
288
                'field' => 'email',
289
                'message' => _('This email already has an account.'),
290
            ])
291
        );
292
293
        return $this->validate($validator);
294
    }
295
296
    /**
297
     * Returns table name mapped in the model.
298
     *
299
     * @return string
300
     */
301
    public function getSource() : string
302
    {
303
        return 'users';
304
    }
305
306
    /**
307
    * Set hashtable settings table, userConfig ;).
308
    *
309
    * @return void
310
    */
311
    private function createSettingsModel(): void
0 ignored issues
show
Unused Code introduced by
The method createSettingsModel() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
312
    {
313
        $this->settingsModel = new UserConfig();
314
    }
315
316
    /**
317
     * Get the User key for redis.
318
     *
319
     * @return string
320
     */
321
    public function getKey() : int
322
    {
323
        return $this->id;
324
    }
325
326
    /**
327
     * A company owner is the first person that register this company
328
     * This only ocurres when signing up the first time, after that all users invites
329
     * come with a default_company id attached.
330
     *
331
     * @return boolean
332
     */
333
    public function isFirstSignup(): bool
334
    {
335
        return empty($this->default_company);
336
    }
337
338
    /**
339
     * Does the user have a role assign to him?
340
     *
341
     * @return boolean
342
     */
343
    public function hasRole(): bool
344
    {
345
        return !empty($this->roles_id);
346
    }
347
348
    /**
349
     * Get all of the subscriptions for the user.
350
     */
351
    public function subscriptions()
352
    {
353
        $this->hasMany(
354
            'id',
355
            'Canvas\Models\Subscription',
356
            'user_id',
357
            [
358
                'alias' => 'subscriptions',
359
                'params' => [
360
                    'conditions' => 'apps_id = ?0 and companies_id = ?1',
361
                    'bind' => [$this->di->getApp()->getId(), $this->default_company],
362
                    'order' => 'id DESC'
363
                ]
364
            ]
365
        );
366
367
        return $this->getRelated('subscriptions');
368
    }
369
370
    /**
371
     * Strat a free trial.
372
     *
373
     * @param Users $user
374
     * @return Subscription
375
     */
376
    public function startFreeTrial() : Subscription
377
    {
378
        $defaultPlan = AppsPlans::getDefaultPlan();
379
        $trialEndsAt = Carbon::now()->addDays($this->di->getApp()->plan->free_trial_dates);
380
381
        $subscription = new Subscription();
382
        $subscription->user_id = $this->getId();
383
        $subscription->companies_id = $this->default_company;
384
        $subscription->apps_id = $this->di->getApp()->getId();
385
        $subscription->apps_plans_id = $this->di->getApp()->default_apps_plan_id;
386
        $subscription->name = $defaultPlan->name;
387
        $subscription->stripe_id = $defaultPlan->stripe_id;
388
        $subscription->stripe_plan = $defaultPlan->stripe_plan;
389
        $subscription->quantity = 1;
390
        $subscription->trial_ends_at = $trialEndsAt->toDateTimeString();
391
        $subscription->trial_ends_days = $trialEndsAt->diffInDays(Carbon::now());
392
        $subscription->is_freetrial = 1;
393
        $subscription->is_active = 1;
394
395
        if (!$subscription->save()) {
396
            throw new ServerErrorHttpException((string)current($this->getMessages()));
397
        }
398
399
        $this->trial_ends_at = $subscription->trial_ends_at;
400
        $this->update();
401
402
        return $subscription;
403
    }
404
405
    /**
406
     * Before create.
407
     *
408
     * @return void
409
     */
410
    public function beforeCreate()
411
    {
412
        parent::beforeCreate();
413
        $random = new Random();
414
        $this->user_activation_email = $random->uuid();
415
416
        //this is only empty when creating a new user
417
        if (!$this->isFirstSignup()) {
418
            //confirm if the app reach its limit
419
            $this->isAtLimit();
420
        }
421
422
        //Assign admin role to the system if we dont get a specify role
423
        if (!$this->hasRole()) {
424
            $role = Roles::getByName('Admins');
425
            $this->roles_id = $role->getId();
426
        }
427
    }
428
429
    /**
430
     * What the current company the users is logged in with
431
     * in this current session?
432
     *
433
     * @return integer
434
     */
435
    public function currentCompanyId(): int
436
    {
437
        $defaultCompanyId = $this->get(Companies::cacheKey());
438
        return !is_null($defaultCompanyId) ? (int) $defaultCompanyId : (int) $this->default_company;
439
    }
440
441
    /**
442
     * Overwrite the user relationship.
443
     * use Phalcon Registry to assure we mantian the same instance accross the request.
444
     */
445
    public function getDefaultCompany(): Companies
446
    {
447
        $registry = Di::getDefault()->getRegistry();
448
        $key = 'company_' . Di::getDefault()->getApp()->getId() . '_' . $this->getId();
449
        if (!isset($registry[$key])) {
450
            $registry[$key] = Companies::findFirstOrFail($this->currentCompanyId());
0 ignored issues
show
Bug introduced by
$this->currentCompanyId() of type integer is incompatible with the type array expected by parameter $parameters of Baka\Database\Model::findFirstOrFail(). ( Ignorable by Annotation )

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

450
            $registry[$key] = Companies::findFirstOrFail(/** @scrutinizer ignore-type */ $this->currentCompanyId());
Loading history...
451
        }
452
        return  $registry[$key];
453
    }
454
455
    /**
456
     * What the current company brach the users is logged in with
457
     * in this current session?
458
     *
459
     * @return integer
460
     */
461
    public function currentCompanyBranchId(): int
462
    {
463
        return (int) $this->default_company_branch;
464
    }
465
466
    /**
467
     * What to do after the creation of a new users
468
     *  - Assign default role.
469
     *
470
     * @return void
471
     */
472
    public function afterCreate()
473
    {
474
        //need to run it here, since we overwirte the default_company id and null this function objective
475
        $isFirstSignup = $this->isFirstSignup();
476
477
        /**
478
         * if we dont find the userdata di lets create it.
479
         * @todo this is not ideal lets fix it later
480
         */
481
        if (!$this->di->has('userData')) {
482
            $this->di->setShared('userData', $this);
483
        }
484
485
        $this->fire('user:afterSignup', $this, $isFirstSignup);
486
487
        //update user activity when its not a empty user
488
        if (!$isFirstSignup) {
489
            $this->updateAppActivityLimit();
490
        }
491
    }
492
493
    /**
494
     * Upload Files.
495
     *
496
     * @todo move this to the baka class
497
     *
498
     * @return void
499
     */
500
    public function afterSave()
501
    {
502
        $this->associateFileSystem();
503
    }
504
505
    /**
506
     * Get the list of all the associated apps this users has.
507
     *
508
     * @return array
509
     */
510
    public function getAssociatedApps(): array
511
    {
512
        return array_map(function ($apps) {
513
            return $apps['apps_id'];
514
        }, $this->getApps(['columns' => 'apps_id', 'group' => 'apps_id'])->toArray());
515
    }
516
517
    /**
518
     * Get an array of the associates companies Ids.
519
     *
520
     * @return array
521
     */
522
    public function getAssociatedCompanies(): array
523
    {
524
        return array_map(function ($company) {
525
            return $company['companies_id'];
526
        }, $this->getCompanies(['columns' => 'companies_id'])->toArray());
527
    }
528
529
    /**
530
     * Get user by key.
531
     * @param string $userActivationEmail
532
     * @return Users
533
     */
534
    public static function getByUserActivationEmail(string $userActivationEmail): Users
535
    {
536
        return self::findFirst([
537
            'conditions' => 'user_activation_email = ?0 and user_active =?1 and is_deleted = 0',
538
            'bind' => [$userActivationEmail, 1],
539
        ]);
540
    }
541
542
    /**
543
     * Overwrite the relationship.
544
     *
545
     * @return void
546
     */
547
    public function getPhoto()
548
    {
549
        return $this->getFileByName('photo');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getFileByName('photo') targeting Canvas\Models\Users::getFileByName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
550
    }
551
552
    /**
553
     * Update the user current default company.
554
     *
555
     * @param integer $companyId
556
     * @return void
557
     */
558
    public function switchDefaultCompanyByBranch(int $branchId): void
559
    {
560
        if ($branch = CompaniesBranches::findFirst($branchId)) {
561
            if ($branch->company) {
562
                if ($branch->company->userAssociatedToCompany($this)) {
563
                    $this->default_company = $branch->company->getId();
564
                    $this->default_company_branch = $branch->getId();
565
                    //set the default company id per the specific app , we do this so we can have multip default companies per diff apps
566
                    $this->set(Companies::cacheKey(), $this->default_company);
567
                }
568
            }
569
        }
570
    }
571
572
    /**
573
     * Update the password for a current user.
574
     *
575
     * @param string $newPassword
576
     * @return boolean
577
     */
578
    public function updatePassword(string $currentPassword, string $newPassword, string $verifyPassword) : bool
579
    {
580
        $currentPassword = trim($currentPassword);
581
        $newPassword = trim($newPassword);
582
        $verifyPassword = trim($verifyPassword);
583
584
        $app = Di::getDefault()->getApp();
585
586
        if ($app->ecosystemAuth()) {
587
            $userAppData = $this->getApp([
588
                'conditions' => 'companies_id = :id:',
589
                'bind' => [
590
                    'id' => $this->currentCompanyId()
591
                ]
592
            ]);
593
            
594
            $password = $userAppData->password;
595
        } else {
596
            $password = $this->password;
597
        }
598
599
        // First off check that the current password matches the current password
600
        if (Password::check($currentPassword, $password)) {
601
            PasswordValidation::validate($newPassword, $verifyPassword);
602
603
            return $this->resetPassword($newPassword);
604
        }
605
606
        throw new Exception(_(' Your current password is incorrect .'));
607
    }
608
609
    /**
610
     * Reset the user passwrod.
611
     *
612
     * @param string $newPassword
613
     * @return bool
614
     */
615
    public function resetPassword(string $newPassword): bool
616
    {
617
        $app = Di::getDefault()->getApp();
618
619
        if ($app->ecosystemAuth()) {
620
            //update all companies password for the current user app
621
            AppAuth::updatePassword($this, Password::make($newPassword));
622
        } else {
623
            $this->password = Password::make($newPassword);
624
        }
625
626
        return true;
627
    }
628
629
    /**
630
     * user signup to the service.
631
     *
632
     * @return Users
633
     */
634
    public function signUp() : BakUser
635
    {
636
        $app = Di::getDefault()->getApp();
637
638
        if ($app->ecosystemAuth()) {
639
            try {
640
                //did we find the email?
641
                //does it have access to this app?
642
                // no?
643
                //ok les register / associate to this app
644
                // yes?
645
                //it meas he was invites so get the fuck out?
646
                $user = self::getByEmail($this->email);
647
648
                $userAppData = $user->countApps('apps_id = ' . $this->getDI()->getDefault()->getApp()->getId());
649
650
                if ($userAppData > 0) {
651
                    throw new Exception('This email already has an account.');
652
                }
653
654
                //assign user role for the current app
655
                $user->roles_id = Roles::getByName(Roles::DEFAULT)->getId();
656
657
                $this->fire('user:afterSignup', $user, true);
658
659
                //update the passwords for the current app
660
                AppAuth::updatePassword($user, Password::make($this->password));
661
662
                //overwrite the current user object
663
                $this->id = $user->getId();
664
                $this->email = $user->getEmail();
665
            } catch (Exception $e) {
666
                //if we cant find the user normal signup
667
                $user = parent::signUp();
668
669
                //update all the password for the apps
670
                AppAuth::updatePassword($user, $this->password);
671
            }
672
        } else {
673
            $user = parent::signUp();
674
        }
675
676
        return $user;
677
    }
678
679
    /**
680
     * Generate new forgot password hash.
681
     *
682
     * @return string
683
     */
684
    public function generateForgotHash(): string
685
    {
686
        $this->user_activation_forgot = $this->generateActivationKey();
687
        $this->updateOrFail();
688
689
        return $this->user_activation_forgot;
690
    }
691
}
692