Passed
Push — master ( 3c5ecd...a595a7 )
by Andre
04:04
created

Providers::retrieveUser()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 40
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 7
eloc 23
c 3
b 0
f 0
nc 7
nop 2
dl 0
loc 40
rs 8.6186
1
<?php
2
/**
3
 * Socializer plugin for Craft CMS 3.x
4
 *
5
 * @link      https://enupal.com/
6
 * @copyright Copyright (c) 2019 Enupal LLC
7
 */
8
9
namespace enupal\socializer\services;
10
11
use Craft;
12
use craft\db\Query;
13
use craft\elements\User;
14
use craft\fields\Dropdown;
15
use craft\fields\Email;
16
use craft\fields\Number;
17
use craft\fields\PlainText;
18
use craft\helpers\UrlHelper;
19
use enupal\socializer\elements\Provider;
20
use enupal\socializer\records\Provider as ProviderRecord;
21
use Hybridauth\Provider\Amazon;
22
use Hybridauth\Provider\Authentiq;
23
use Hybridauth\Provider\BitBucket;
24
use Hybridauth\Provider\Blizzard;
25
use Hybridauth\Provider\Discord;
26
use Hybridauth\Provider\Disqus;
27
use Hybridauth\Provider\Dribbble;
28
use Hybridauth\Provider\Facebook;
29
use Hybridauth\Provider\Foursquare;
30
use Hybridauth\Provider\GitHub;
31
use Hybridauth\Provider\GitLab;
32
use Hybridauth\Provider\Instagram;
33
use Hybridauth\Provider\LinkedIn;
34
use Hybridauth\Provider\Mailru;
35
use Hybridauth\Provider\MicrosoftGraph;
36
use Hybridauth\Provider\Odnoklassniki;
37
use Hybridauth\Provider\ORCID;
38
use Hybridauth\Provider\QQ;
39
use Hybridauth\Provider\Reddit;
40
use Hybridauth\Provider\Slack;
41
use Hybridauth\Provider\Spotify;
42
use Hybridauth\Provider\StackExchange;
43
use Hybridauth\Provider\Steam;
44
use Hybridauth\Provider\SteemConnect;
45
use Hybridauth\Provider\Strava;
46
use Hybridauth\Provider\Telegram;
47
use Hybridauth\Provider\Tumblr;
48
use Hybridauth\Provider\TwitchTV;
49
use Hybridauth\Provider\Twitter;
50
use Hybridauth\Provider\Google;
51
use Hybridauth\Provider\Vkontakte;
52
use Hybridauth\Provider\WeChat;
53
use Hybridauth\Provider\WindowsLive;
54
use Hybridauth\Provider\WordPress;
55
use Hybridauth\Provider\Yahoo;
56
use Hybridauth\Provider\Yandex;
57
use Hybridauth\User\Profile;
58
use yii\base\Component;
59
use enupal\socializer\Socializer;
60
use yii\base\NotSupportedException;
61
use yii\db\Exception;
62
63
class Providers extends Component
64
{
65
    /**
66
     * @param $handle
67
     * @param $options
68
     * @return string
69
     * @throws NotSupportedException
70
     * @throws \yii\base\Exception
71
     */
72
    public function loginUrl($handle, $options = [])
73
    {
74
        $provider = $this->getProviderByHandle($handle);
75
        if (is_null($provider)){
76
            throw new NotSupportedException('Provider not found or disabled: '.$handle);
77
        }
78
79
        $options['provider'] = $handle;
80
81
        return UrlHelper::siteUrl('socializer/login', $options);
82
    }
83
84
    /**
85
     * @return array
86
     */
87
    public function getAllProviderTypes()
88
    {
89
        return [
90
            Amazon::class,
91
            Authentiq::class,
92
            BitBucket::class,
93
            Blizzard::class,
94
            Discord::class,
95
            Disqus::class,
96
            Dribbble::class,
97
            Facebook::class,
98
            Foursquare::class,
99
            GitHub::class,
100
            GitLab::class,
101
            Google::class,
102
            Instagram::class,
103
            LinkedIn::class,
104
            Mailru::class,
105
            MicrosoftGraph::class,
106
            Odnoklassniki::class,
107
            ORCID::class,
108
            Reddit::class,
109
            Slack::class,
110
            Spotify::class,
111
            StackExchange::class,
112
            Steam::class,
113
            Strava::class,
114
            SteemConnect::class,
115
            Telegram::class,
116
            Tumblr::class,
117
            TwitchTV::class,
118
            Twitter::class,
119
            Vkontakte::class,
120
            WeChat::class,
121
            WindowsLive::class,
122
            WordPress::class,
123
            Yandex::class,
124
            Yahoo::class,
125
            QQ::class
126
        ];
127
    }
128
129
    /**
130
     * @return array
131
     */
132
    public function getUserFieldsAsOptions()
133
    {
134
        $user = new User();
135
        $fields = $user->getFieldLayout()->getFields();
136
        $options = [[
137
            'label' => 'None',
138
            'value' => ''
139
        ]];
140
141
        foreach ($fields as $field) {
142
            if (!$this->validateFieldClass($field)){
143
                continue;
144
            }
145
146
            $option = [
147
                'label' => $field->name. ' ('.$field->handle.')',
148
                'value' => $field->handle
149
            ];
150
151
            $options[] = $option;
152
        }
153
154
        return $options;
155
    }
156
157
    /**
158
     * @param $field
159
     * @return bool
160
     */
161
    private function validateFieldClass($field)
162
    {
163
        $fieldClass = get_class($field);
164
165
        $supportedClasses = [
166
          PlainText::class => 1,
167
          Dropdown::class => 1
168
        ];
169
170
        if (isset($supportedClasses[$fieldClass])){
171
            return true;
172
        }
173
174
        return false;
175
    }
176
177
    /**
178
     * @return array
179
     */
180
    public function getUserProfileFieldsAsOptions()
181
    {
182
        return [
183
            [
184
                'label' => 'Email',
185
                'value' => 'email',
186
                'compatibleCraftFields' => [
187
                    PlainText::class
188
                ]
189
            ],
190
            [
191
                'label' => 'Identifier',
192
                'value' => 'identifier',
193
                'compatibleCraftFields' => [
194
                    PlainText::class,
195
                    Dropdown::class
196
                ]
197
            ],
198
            [
199
                'label' => 'Profile URL',
200
                'value' => 'profileURL',
201
                'compatibleCraftFields' => [
202
                    PlainText::class
203
                ]
204
            ],
205
            [
206
                'label' => 'WebSite URL',
207
                'value' => 'webSiteURL',
208
                'compatibleCraftFields' => [
209
                    PlainText::class
210
                ]
211
            ],
212
            [
213
                'label' => 'Photo URL',
214
                'value' => 'photoURL',
215
                'compatibleCraftFields' => [
216
                ]
217
            ],
218
            [
219
                'label' => 'Display Name',
220
                'value' => 'displayName',
221
                'compatibleCraftFields' => [
222
                    PlainText::class,
223
                    Dropdown::class
224
                ]
225
            ],
226
            [
227
                'label' => 'Description',
228
                'value' => 'description',
229
                'compatibleCraftFields' => [
230
                    PlainText::class
231
                ]
232
            ],
233
            [
234
                'label' => 'First Name',
235
                'value' => 'firstName',
236
                'compatibleCraftFields' => [
237
                    PlainText::class
238
                ]
239
            ],
240
            [
241
                'label' => 'Last Name',
242
                'value' => 'lastName',
243
                'compatibleCraftFields' => [
244
                    PlainText::class
245
                ]
246
            ],
247
            [
248
                'label' => 'Gender',
249
                'value' => 'gender',
250
                'compatibleCraftFields' => [
251
                    PlainText::class,
252
                    Dropdown::class
253
                ]
254
            ],
255
            [
256
                'label' => 'Language',
257
                'value' => 'language',
258
                'compatibleCraftFields' => [
259
                    PlainText::class
260
                ]
261
            ],
262
            [
263
                'label' => 'Age',
264
                'value' => 'age',
265
                'compatibleCraftFields' => [
266
                    PlainText::class,
267
                    Number::class
268
                ]
269
            ],
270
            [
271
                'label' => 'Birth Day',
272
                'value' => 'birthDay',
273
                'compatibleCraftFields' => [
274
                    PlainText::class,
275
                    Number::class
276
                ]
277
            ],
278
            [
279
                'label' => 'Birth Month',
280
                'value' => 'birthMonth',
281
                'compatibleCraftFields' => [
282
                    PlainText::class,
283
                    Number::class
284
                ]
285
            ],
286
            [
287
                'label' => 'Birth Year',
288
                'value' => 'birthYear',
289
                'compatibleCraftFields' => [
290
                    PlainText::class,
291
                    Number::class
292
                ]
293
            ],
294
            [
295
                'label' => 'Email Verified',
296
                'value' => 'emailVerified',
297
                'compatibleCraftFields' => [
298
                    PlainText::class,
299
                    Email::class
300
                ]
301
            ],
302
            [
303
                'label' => 'Phone',
304
                'value' => 'phone',
305
                'compatibleCraftFields' => [
306
                    PlainText::class
307
                ]
308
            ],
309
            [
310
                'label' => 'Address',
311
                'value' => 'address',
312
                'compatibleCraftFields' => [
313
                    PlainText::class
314
                ]
315
            ],
316
            [
317
                'label' => 'Country',
318
                'value' => 'country',
319
                'compatibleCraftFields' => [
320
                    PlainText::class,
321
                    Dropdown::class
322
                ]
323
            ],
324
            [
325
                'label' => 'Region',
326
                'value' => 'region',
327
                'compatibleCraftFields' => [
328
                    PlainText::class,
329
                    Dropdown::class
330
                ]
331
            ],
332
            [
333
                'label' => 'City',
334
                'value' => 'city',
335
                'compatibleCraftFields' => [
336
                    PlainText::class,
337
                    Dropdown::class
338
                ]
339
            ],
340
            [
341
                'label' => 'Zip',
342
                'value' => 'zip',
343
                'compatibleCraftFields' => [
344
                    PlainText::class
345
                ]
346
            ],
347
            [
348
                'label' => 'Data (JSON)',
349
                'value' => 'data',
350
                'compatibleCraftFields' => [
351
                    PlainText::class
352
                ]
353
            ]
354
        ];
355
    }
356
357
    /**
358
     * @return array
359
     */
360
    public function getDefaultFieldMapping()
361
    {
362
        $userProfileFields = Socializer::$app->providers->getUserProfileFieldsAsOptions();
363
        $options = [];
364
        foreach ($userProfileFields as $item) {
365
            $option = [
366
                'sourceFormField' => $item['value'],
367
                'targetUserField' => ''
368
            ];
369
            $options[] = $option;
370
        }
371
372
        return $options;
373
    }
374
375
    /**
376
     * @param bool $excludeCreated
377
     * @return array
378
     * @throws \ReflectionException
379
     */
380
    public function getProviderTypesAsOptions($excludeCreated = true)
381
    {
382
        $providers = $this->getAllProviderTypes();
383
384
        if ($excludeCreated){
385
            $providers = $this->getExcludeCreatedProviders();
386
        }
387
388
        $asOptions = [];
389
        foreach ($providers as $provider) {
390
            $option = [
391
                "label" => $this->getClassNameFromNamespace($provider),
392
                "value" => $provider
393
            ];
394
            $asOptions[] = $option;
395
        }
396
397
        return $asOptions;
398
    }
399
400
    /**
401
     * @return array
402
     */
403
    public function getExcludeCreatedProviders()
404
    {
405
        $providerTypes = $this->getAllProviderTypes();
406
        $providers = (new Query())
407
            ->select(['type'])
408
            ->from(["{{%enupalsocializer_providers}}"])
409
            ->all();
410
411
        foreach ($providers as $provider) {
412
            if (($key = array_search($provider["type"], $providerTypes)) !== false) {
413
                unset($providerTypes[$key]);
414
            }
415
        }
416
417
        return $providerTypes;
418
    }
419
420
    /**
421
     * @param $class
422
     * @return string
423
     * @throws \ReflectionException
424
     */
425
    public function getClassNameFromNamespace($class)
426
    {
427
        return (new \ReflectionClass($class))->getShortName();
428
    }
429
430
    /**
431
     * Returns a Provider model if one is found in the database by id
432
     *
433
     * @param int $id
434
     * @param int $siteId
435
     *
436
     * @return null|Provider
437
     */
438
    public function getProviderById(int $id, int $siteId = null)
439
    {
440
        /** @var Provider $provider */
441
        $provider = Craft::$app->getElements()->getElementById($id, Provider::class, $siteId);
442
443
        return $provider;
444
    }
445
446
    /**
447
     * Removes providers and related records from the database given the ids
448
     *
449
     * @param $providers
450
     *
451
     * @return bool
452
     * @throws \Throwable
453
     */
454
    public function deleteProviders($providers): bool
455
    {
456
        foreach ($providers as $key => $providerElement) {
457
            $provider = $this->getProviderById($providerElement->id);
458
459
            if ($provider) {
460
                $this->deleteProvider($provider);
461
            } else {
462
                Craft::error("Can't delete the payment form with id: {$providerElement->id}", __METHOD__);
463
            }
464
        }
465
466
        return true;
467
    }
468
469
    /**
470
     * @param Provider $provider
471
     *
472
     * @return bool
473
     * @throws \Throwable
474
     */
475
    public function deleteProvider(Provider $provider)
476
    {
477
        $transaction = Craft::$app->db->beginTransaction();
478
479
        try {
480
            // Delete the tokens
481
            $tokens = (new Query())
482
                ->select(['id'])
483
                ->from(["{{%enupalsocializer_tokens}}"])
484
                ->where(['providerId' => $provider->id])
485
                ->all();
486
487
            foreach ($tokens as $token) {
488
                Craft::$app->elements->deleteElementById($token['id']);
489
            }
490
491
            // Delete the Provider Element
492
            $success = Craft::$app->elements->deleteElementById($provider->id);
493
494
            if (!$success) {
495
                $transaction->rollback();
496
                Craft::error("Couldn’t delete Provider", __METHOD__);
497
498
                return false;
499
            }
500
501
            $transaction->commit();
502
        } catch (\Exception $e) {
503
            $transaction->rollback();
504
505
            throw $e;
506
        }
507
508
        return true;
509
    }
510
511
    /**
512
     * @param string $handle
513
     * @param int|null $siteId
514
     * @return array|Provider|null
515
     */
516
    public function getProviderByHandle(string $handle, int $siteId = null)
517
    {
518
        $query = Provider::find();
519
        $query->handle($handle);
520
        $query->siteId($siteId);
521
522
        return $query->one();
523
    }
524
525
    /**
526
     * @param string $type
527
     * @param int|null $siteId
528
     * @return array|Provider|null
529
     */
530
    public function getProviderByType(string $type, int $siteId = null)
531
    {
532
        $query = Provider::find();
533
        $query->type($type);
534
        $query->siteId($siteId);
535
536
        return $query->one();
537
    }
538
539
    /**
540
     * @param string $name
541
     * @param string $handle
542
     * @param string $type
543
     *
544
     * @return Provider
545
     * @throws \Exception
546
     * @throws \Throwable
547
     */
548
    public function createNewProvider(string $name, string $handle, string $type): Provider
549
    {
550
        $provider = new Provider();
551
552
        $provider->name = $name;
553
        $provider->handle = $handle;
554
        $provider->type = $type;
555
        $provider->enabled = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $enabled was declared of type boolean, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
556
557
        $this->saveProvider($provider);
558
559
        return $provider;
560
    }
561
562
    /**
563
     * @param Provider $provider
564
     * @return bool
565
     * @throws \Throwable
566
     * @throws \craft\errors\ElementNotFoundException
567
     * @throws \craft\errors\MissingComponentException
568
     * @throws \craft\errors\WrongEditionException
569
     * @throws \yii\base\Exception
570
     */
571
    public function loginOrRegisterUser(Provider $provider)
572
    {
573
        $adapter = $provider->getAdapter();
574
        $adapter->authenticate();
575
576
        $userProfile = $adapter->getUserProfile();
577
        $user = Craft::$app->getUser()->getIdentity();
578
579
        if (!$user){
580
            $user = $this->retrieveUser($userProfile, $provider);
581
        }
582
583
        if (!$user){
584
            Craft::error("Not user to login", __METHOD__);
585
            return false;
586
        }
587
588
        Socializer::$app->tokens->registerToken($user, $provider);
589
590
        if (!Craft::$app->getUser()->login($user)) {
591
            Craft::error("Something went wrong while login craft user", __METHOD__);
592
            return false;
593
        }
594
595
        return true;
596
    }
597
598
    /**
599
     * Register or get existing user
600
     * @param Profile $userProfile
601
     * @param Provider $provider
602
     * @return User
603
     * @throws \Throwable
604
     * @throws \craft\errors\ElementNotFoundException
605
     * @throws \craft\errors\WrongEditionException
606
     * @throws \yii\base\Exception
607
     */
608
    private function retrieveUser(Profile $userProfile, Provider $provider): User
609
    {
610
        if (is_null($userProfile->email)){
0 ignored issues
show
introduced by
The condition is_null($userProfile->email) is always false.
Loading history...
611
            throw new \Exception("Email address is not provided, please check the settings of your application");
612
        }
613
614
        $user = Craft::$app->users->getUserByUsernameOrEmail($userProfile->email);
615
616
        if ($user) {
0 ignored issues
show
introduced by
$user is of type craft\elements\User, thus it always evaluated to true.
Loading history...
617
            return $user;
618
        }
619
        $settings = Socializer::$app->settings->getSettings();
620
621
        if (!$settings->enableUserSignUp){
622
            return null;
623
        }
624
625
        Craft::$app->requireEdition(Craft::Pro);
626
        $user = new User();
627
        $user->email = $userProfile->email;
628
        $user->username = $userProfile->email;
629
        $user->firstName = $userProfile->firstName;
630
        $user->lastName = $userProfile->lastName;
631
632
        // validate populate
633
        $user = $this->populateUserModel($user, $provider, $userProfile);
634
635
        if (!Craft::$app->elements->saveElement($user)){
636
            Craft::error("Unable to create user: ".json_encode($user->getErrors()));
637
            throw new \Exception("Something went wrong while creating the user");
638
        }
639
640
        if ($settings->userGroupId){
641
            $userGroup = Craft::$app->getUserGroups()->getGroupById($settings->userGroupId);
642
            if ($userGroup){
643
                Craft::$app->getUsers()->assignUserToGroups($user->id, [$userGroup->id]);
644
            }
645
        }
646
647
        return $user;
648
    }
649
650
    /**
651
     * @param User $user
652
     * @param Provider $provider
653
     * @param Profile $profile
654
     * @return User
655
     */
656
    public function populateUserModel(User $user, Provider $provider, Profile $profile)
657
    {
658
        $settings = Socializer::$app->settings->getSettings();
659
660
        if (!$settings->enableFieldMapping) {
661
            return $user;
662
        }
663
664
        $fieldMapping = Socializer::$app->settings->getGlobalFieldMapping();
665
666
        if ($settings->enableFieldMappingPerProvider) {
667
            $fieldMapping = $provider->fieldMapping ?? $fieldMapping;
668
        }
669
670
        foreach ($fieldMapping as $item) {
671
            if(isset($item['targetUserField']) && $item['targetUserField']){
672
                $profileValue = $profile->{$item['sourceFormField']};
673
                $field = $user->getFieldLayout()->getFieldByHandle($item['targetUserField']);
674
                if ($field){
675
                    $user->setFieldValue($item['targetUserField'], $profileValue);
676
                }
677
            }
678
        }
679
680
        return $user;
681
    }
682
683
684
    /**
685
     * @param $provider Provider
686
     *
687
     * @return bool
688
     * @throws Exception
689
     * @throws \Throwable
690
     */
691
    public function saveProvider(Provider $provider)
692
    {
693
        if ($provider->id) {
694
            $providerRecord = ProviderRecord::findOne($provider->id);
695
696
            if (!$providerRecord) {
0 ignored issues
show
introduced by
$providerRecord is of type yii\db\ActiveRecord, thus it always evaluated to true.
Loading history...
697
                throw new Exception(Craft::t("enupal-socializer",'No Provider exists with the ID “{id}”', ['id' => $provider->id]));
698
            }
699
        }
700
701
        if (!$provider->validate()) {
702
            return false;
703
        }
704
705
        $transaction = Craft::$app->db->beginTransaction();
706
707
        try {
708
            // Set the field context
709
            Craft::$app->content->fieldContext = $provider->getFieldContext();
710
711
            if (Craft::$app->elements->saveElement($provider)) {
712
                $transaction->commit();
713
            }
714
        } catch (\Exception $e) {
715
            $transaction->rollback();
716
717
            throw $e;
718
        }
719
720
        return true;
721
    }
722
723
    /**
724
     * @param Provider $provider
725
     *
726
     * @return Provider
727
     */
728
    public function populateProviderFromPost(Provider $provider)
729
    {
730
        $request = Craft::$app->getRequest();
731
732
        $postFields = $request->getBodyParam('fields');
733
734
        $provider->setAttributes(/** @scrutinizer ignore-type */
735
            $postFields, false);
736
737
        return $provider;
738
    }
739
}