Completed
Push — master ( cf2713...aee5ae )
by Sherif
02:07
created

UserService   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 389
Duplicated Lines 2.83 %

Coupling/Cohesion

Components 3
Dependencies 7

Importance

Changes 0
Metric Value
wmc 47
lcom 3
cbo 7
dl 11
loc 389
rs 8.64
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A account() 0 13 2
A can() 0 22 3
A hasRoles() 0 4 3
A assignRoles() 11 11 1
B login() 0 16 9
A loginSocial() 0 15 4
A register() 0 15 3
A block() 0 8 3
A unblock() 0 4 1
A sendReset() 0 9 2
A resetPassword() 0 33 5
A changePassword() 0 9 2
A confirmEmail() 0 8 2
A sendConfirmationEmail() 0 10 2
A saveProfile() 0 14 2
A logout() 0 4 1
A refreshToken() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like UserService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UserService, and based on these observations, apply Extract Interface, too.

1
<?php namespace App\Modules\Users\Services;
2
3
use App\Modules\Core\BaseClasses\BaseService;
4
use Illuminate\Support\Arr;
5
use App\Modules\Users\Repositories\UserRepository;
6
use App\Modules\Permissions\Services\PermissionService;
7
use App\Modules\OauthClients\Services\OauthClientService;
8
use App\Modules\Notifications\Services\NotificationService;
9
use App\Modules\Users\Proxy\LoginProxy;
10
use App\Modules\Core\Utl\Media;
11
12
class UserService extends BaseService
13
{
14
    /**
15
     * @var PermissionService
16
     */
17
    protected $permissionService;
18
19
    /**
20
     * @var LoginProxy
21
     */
22
    protected $loginProxy;
23
24
    /**
25
     * @var Media
26
     */
27
    protected $media;
28
29
    /**
30
     * @var NotificationService
31
     */
32
    protected $notificationService;
33
34
    /**
35
     * @var OauthClientService
36
     */
37
    protected $oauthClientService;
38
39
    /**
40
     * Init new object.
41
     *
42
     * @param   UserRepository       $repo
43
     * @param   PermissionService    $permissionService
44
     * @param   LoginProxy           $loginProxy
45
     * @param   Media                $media
46
     * @param   NotificationService  $notificationService
47
     * @param   OauthClientService   $oauthClientService
48
     * @return  void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
49
     */
50
    public function __construct(
51
        UserRepository $repo, 
52
        PermissionService $permissionService, 
53
        LoginProxy $loginProxy, 
54
        Media $media, 
55
        NotificationService $notificationService,
56
        OauthClientService $oauthClientService)
57
    {
58
        $this->permissionService   = $permissionService;
59
        $this->loginProxy          = $loginProxy;
60
        $this->media               = $media;
61
        $this->notificationService = $notificationService;
62
        $this->oauthClientService  = $oauthClientService;
63
        parent::__construct($repo);
64
    }
65
66
    /**
67
     * Return the logged in user account.
68
     *
69
     * @param  array   $relations
70
     * @return boolean
71
     */
72
    public function account($relations = ['roles.permissions'])
73
    {
74
        $permissions = [];
75
        $user        = $this->repo->find(\Auth::id(), $relations);
76
        foreach ($user->roles as $role) {
77
            $role->permissions->each(function ($permission) use (&$permissions) {
78
                $permissions[$permission->repo][$permission->id] = $permission->name;
79
            });
80
        }
81
        $user->permissions = $permissions;
82
83
        return $user;
84
    }
85
86
    /**
87
     * Check if the logged in user or the given user
88
     * has the given permissions on the given model.
89
     *
90
     * @param  string $permissionName
91
     * @param  string $model
92
     * @param  mixed  $userId
93
     * @return boolean
94
     */
95
    public function can($permissionName, $model, $userId = false)
96
    {
97
        $permission = $this->permissionService->first([
98
            'and' => [
99
                'model' => $model,
100
                'name'  => $permissionName,
101
                'roles' => [
102
                    'op' => 'has',
103
                    'val' => [
104
                        'users' => [
105
                            'op' => 'has',
106
                            'val' => [
107
                                'users.id' => $userId ?: \Auth::id()
108
                            ]
109
                        ]
110
                    ]
111
                ]
112
            ]
113
        ]);
114
115
        return $permission ? true : false;
116
    }
117
118
    /**
119
     * Check if the logged in or the given user has the given role.
120
     *
121
     * @param  string[] $roles
122
     * @param  mixed    $user
123
     * @return boolean
124
     */
125
    public function hasRoles($roles, $user = false)
126
    {
127
        return $this->repo->countRoles($user ?: \Auth::id(), $roles) ? true : false;
128
    }
129
130
    /**
131
     * Assign the given role ids to the given user.
132
     *
133
     * @param  integer $userId
134
     * @param  array   $roleIds
135
     * @return object
136
     */
137 View Code Duplication
    public function assignRoles($userId, $roleIds)
0 ignored issues
show
Unused Code introduced by
The parameter $roleIds is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
    {
139
        $user = false;
140
        \DB::transaction(function () use ($userId, $permissionIds, &$user) {
0 ignored issues
show
Bug introduced by
The variable $permissionIds does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
141
            $user = $this->repo->find($userId);
142
            $this->repo->detachPermissions($userId);
143
            $this->repo->attachPermissions($userId, $roleIds);
0 ignored issues
show
Bug introduced by
The variable $roleIds does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
144
        });
145
146
        return $user;
147
    }
148
149
    /**
150
     * Handle a login request to the application.
151
     *
152
     * @param  string  $email
153
     * @param  string  $password
154
     * @param  boolean $adminLogin
155
     * @return object
156
     */
157
    public function login($email, $password, $adminLogin = false)
158
    {
159
        if (! $user = $this->repo->first(['email' => $email])) {
160
            \ErrorHandler::loginFailed();
161
        } elseif ($adminLogin && ! $this->hasRoles(['Admin'], $user)) {
162
            \ErrorHandler::loginFailed();
163
        } elseif (! $adminLogin && $this->hasRoles(['Admin'], $user)) {
164
            \ErrorHandler::loginFailed();
165
        } elseif ($user->blocked) {
166
            \ErrorHandler::userIsBlocked();
167
        } elseif (! config('skeleton.disable_confirm_email') && ! $user->confirmed) {
168
            \ErrorHandler::emailNotConfirmed();
169
        }
170
171
        return ['user' => $user, 'tokens' => $this->loginProxy->login($user->email, $password)];
172
    }
173
174
    /**
175
     * Handle a social login request of the none admin to the application.
176
     *
177
     * @param  string $authCode
178
     * @param  string $accessToken
179
     * @param  string $type
180
     * @return array
181
     */
182
    public function loginSocial($authCode, $accessToken, $type)
183
    {
184
        $access_token = $authCode ? Arr::get(\Socialite::driver($type)->getAccessTokenResponse($authCode), 'access_token') : $accessToken;
185
        $user         = \Socialite::driver($type)->userFromToken($access_token);
186
187
        if (! $user->email) {
188
            \ErrorHandler::noSocialEmail();
189
        }
190
191
        if (! $this->repo->first(['email' => $user->email])) {
192
            $this->register(['email' => $user->email, 'password' => ''], true);
0 ignored issues
show
Bug introduced by
The call to register() misses a required argument $password.

This check looks for function calls that miss required arguments.

Loading history...
Documentation introduced by
array('email' => $user->email, 'password' => '') is of type array<string,?,{"email":"?","password":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
193
        }
194
195
        return $this->loginProxy->login($user->email, config('skeleton.social_pass'));
196
    }
197
    
198
    /**
199
     * Handle a registration request.
200
     *
201
     * @param  string  $name
202
     * @param  string  $email
203
     * @param  string  $password
204
     * @param  boolean $skipConfirmEmail
205
     * @return array
206
     */
207
    public function register($name, $email, $password, $skipConfirmEmail = false)
208
    {
209
        $user = $this->repo->save([
210
            'name'      => $name,
211
            'email'     => $email,
212
            'password'  => $password,
213
            'confirmed' => $skipConfirmEmail
214
        ]);
215
216
        if (! $skipConfirmEmail && ! config('skeleton.disable_confirm_email')) {
217
            $this->sendConfirmationEmail($user->email);
218
        }
219
220
        return $user;
221
    }
222
    
223
    /**
224
     * Block the user.
225
     *
226
     * @param  integer $userId
227
     * @return object
228
     */
229
    public function block($userId)
230
    {
231
        if (\Auth::id() == $userId || $this->hasRoles(['Admin'], $user) !== false) {
0 ignored issues
show
Bug introduced by
The variable $user does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
232
            \ErrorHandler::noPermissions();
233
        }
234
        
235
        return $this->repo->save(['id' => $userId, 'blocked' => 1]);
236
    }
237
238
    /**
239
     * Unblock the user.
240
     *
241
     * @param  integer $userId
242
     * @return object
243
     */
244
    public function unblock($userId)
245
    {
246
        return $this->repo->save(['id' => $userId, 'blocked' => 0]);
247
    }
248
249
    /**
250
     * Send a reset link to the given user.
251
     *
252
     * @param  string  $email
253
     * @return void
254
     */
255
    public function sendReset($email)
256
    {
257
        if (! $user = $this->repo->first(['email' => $email])) {
258
            \ErrorHandler::notFound('email');
259
        }
260
261
        $token = \Password::getService()->create($user);
262
        $this->notificationService->notify($user, 'ResetPassword', $token);
263
    }
264
265
    /**
266
     * Reset the given user's password.
267
     *
268
     * @param   string  $email
269
     * @param   string  $password
270
     * @param   string  $passwordConfirmation
271
     * @param   string  $token
272
     * @return string|void
273
     */
274
    public function resetPassword($email, $password, $passwordConfirmation, $token)
275
    {
276
        $response = \Password::reset([
277
            'email'                 => $email, 
278
            'password'              => $password, 
279
            'password_confirmation' => $passwordConfirmation, 
280
            'token'                 => $token
281
        ], function ($user, $password) {
282
            $this->repo->save(['id' => $user->id, 'password' => $password]);
283
        });
284
285
        switch ($response) {
286
            case \Password::PASSWORD_RESET:
287
                return 'success';
288
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
289
290
            case \Password::INVALID_TOKEN:
291
                \ErrorHandler::invalidResetToken('token');
292
                break;
293
294
            case \Password::INVALID_PASSWORD:
295
                \ErrorHandler::invalidResetPassword('email');
296
                break;
297
298
            case \Password::INVALID_USER:
299
                \ErrorHandler::notFound('user');
300
                break;
301
302
            default:
303
                \ErrorHandler::generalError();
304
                break;
305
        }
306
    }
307
308
    /**
309
     * Change the logged in user password.
310
     *
311
     * @param  string  $password
312
     * @param  string  $oldPassword
313
     * @return void
314
     */
315
    public function changePassword($password, $oldPassword)
316
    {
317
        $user = \Auth::user();
318
        if (! \Hash::check($oldPassword, $user->password)) {
319
            \ErrorHandler::invalidOldPassword();
320
        }
321
322
        $this->repo->save(['id' => $user->id, 'password' => $password]);
323
    }
324
325
    /**
326
     * Confirm email using the confirmation code.
327
     *
328
     * @param  string $confirmationCode
329
     * @return void
330
     */
331
    public function confirmEmail($confirmationCode)
332
    {
333
        if (! $user = $this->repo->first(['confirmation_code' => $confirmationCode])) {
334
            \ErrorHandler::invalidConfirmationCode();
335
        }
336
337
        $this->repo->save(['id' => $user->id, 'confirmed' => 1, 'confirmation_code' => null]);
338
    }
339
340
    /**
341
     * Send the confirmation mail.
342
     *
343
     * @param  string $email
344
     * @return void
345
     */
346
    public function sendConfirmationEmail($email)
347
    {
348
        $user = $this->repo->first(['email' => $email]);
349
        if ($user->confirmed) {
350
            \ErrorHandler::emailAlreadyConfirmed();
351
        }
352
353
        $this->repo->save(['id' => $user->id, 'confirmation_code' => sha1(microtime())]);
354
        $this->notificationService->notify($user, 'ConfirmEmail');
355
    }
356
357
    /**
358
     * Save the given data to the logged in user.
359
     *
360
     * @param  string $name
361
     * @param  string $email
362
     * @param  string $profilePicture
363
     * @return void
364
     */
365
    public function saveProfile($name, $email, $profilePicture = false)
366
    {
367
        if ($profilePicture) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $profilePicture of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
368
            $data['profile_picture'] = $this->media->uploadImageBas64($profilePicture, 'admins/profile_pictures');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
Documentation introduced by
$profilePicture is of type string, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
369
        }
370
        
371
        $data['id'] = \Auth::id();
0 ignored issues
show
Bug introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
372
        return $this->repo->save([
373
            'id'             => \Auth::id(),
374
            'name'           => $name,
375
            'email'          => $email,
376
            'profilePicture' => $profilePicture,
377
        ]);
378
    }
379
380
    /**
381
     * Logs out the user, revoke access token and refresh token.
382
     *
383
     * @return void
384
     */
385
    public function logout()
386
    {
387
        $this->oauthClientService->revokeAccessToken(\Auth::user()->token());
388
    }
389
390
    /**
391
     * Attempt to refresh the access token using the given refresh token.
392
     *
393
     * @param  string $refreshToken
394
     * @return array
395
     */
396
    public function refreshToken($refreshToken)
397
    {
398
        return $this->loginProxy->refreshToken($refreshToken);
399
    }
400
}
401