Completed
Push — master ( bfbb50...8023d3 )
by Sherif
09:55
created

UserService::saveProfile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace App\Modules\Users\Services;
4
5
use App\Modules\Core\BaseClasses\BaseService;
6
use Illuminate\Support\Arr;
7
use App\Modules\Users\Repositories\UserRepository;
8
use App\Modules\Permissions\Services\PermissionService;
9
use App\Modules\OauthClients\Services\OauthClientService;
10
use App\Modules\Notifications\Services\NotificationService;
11
use App\Modules\Users\Proxy\LoginProxy;
12
13
class UserService extends BaseService
14
{
15
    /**
16
     * @var PermissionService
17
     */
18
    protected $permissionService;
19
20
    /**
21
     * @var LoginProxy
22
     */
23
    protected $loginProxy;
24
25
    /**
26
     * @var NotificationService
27
     */
28
    protected $notificationService;
29
30
    /**
31
     * @var OauthClientService
32
     */
33
    protected $oauthClientService;
34
35
    /**
36
     * Init new object.
37
     *
38
     * @param   UserRepository       $repo
39
     * @param   PermissionService    $permissionService
40
     * @param   LoginProxy           $loginProxy
41
     * @param   NotificationService  $notificationService
42
     * @param   OauthClientService   $oauthClientService
43
     * @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...
44
     */
45
    public function __construct(
46
        UserRepository $repo,
47
        PermissionService $permissionService,
48
        LoginProxy $loginProxy,
49
        NotificationService $notificationService,
50
        OauthClientService $oauthClientService
51
    ) {
52
        $this->permissionService   = $permissionService;
53
        $this->loginProxy          = $loginProxy;
54
        $this->notificationService = $notificationService;
55
        $this->oauthClientService  = $oauthClientService;
56
        parent::__construct($repo);
57
    }
58
59
    /**
60
     * Return the logged in user account.
61
     *
62
     * @param  array   $relations
63
     * @return boolean
64
     */
65
    public function account($relations = ['roles.permissions'])
66
    {
67
        $permissions = [];
68
        $user        = $this->repo->find(\Auth::id(), $relations);
69
        foreach ($user->roles as $role) {
70
            $role->permissions->each(function ($permission) use (&$permissions) {
71
                $permissions[$permission->repo][$permission->id] = $permission->name;
72
            });
73
        }
74
        $user->permissions = $permissions;
75
76
        return $user;
77
    }
78
79
    /**
80
     * Check if the logged in user or the given user
81
     * has the given permissions on the given model.
82
     *
83
     * @param  string $permissionName
84
     * @param  string $model
85
     * @param  mixed  $userId
86
     * @return boolean
87
     */
88
    public function can($permissionName, $model, $userId = false)
89
    {
90
        $permission = $this->permissionService->first([
91
            'and' => [
92
                'model' => $model,
93
                'name'  => $permissionName,
94
                'roles' => [
95
                    'op' => 'has',
96
                    'val' => [
97
                        'users' => [
98
                            'op' => 'has',
99
                            'val' => [
100
                                'users.id' => $userId ?: \Auth::id()
101
                            ]
102
                        ]
103
                    ]
104
                ]
105
            ]
106
        ]);
107
108
        return $permission ? true : false;
109
    }
110
111
    /**
112
     * Check if the logged in or the given user has the given role.
113
     *
114
     * @param  string[] $roles
115
     * @param  mixed    $user
116
     * @return boolean
117
     */
118
    public function hasRoles($roles, $user = false)
119
    {
120
        return $this->repo->countRoles($user ?: \Auth::id(), $roles) ? true : false;
121
    }
122
123
    /**
124
     * Assign the given role ids to the given user.
125
     *
126
     * @param  integer $userId
127
     * @param  array   $roleIds
128
     * @return object
129
     */
130 View Code Duplication
    public function assignRoles($userId, $roleIds)
0 ignored issues
show
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...
131
    {
132
        $user = false;
133
        \DB::transaction(function () use ($userId, $roleIds, &$user) {
134
            $user = $this->repo->find($userId);
135
            $this->repo->detachPermissions($userId);
136
            $this->repo->attachPermissions($userId, $roleIds);
137
        });
138
139
        return $user;
140
    }
141
142
    /**
143
     * Handle the login request to the application.
144
     *
145
     * @param  string  $email
146
     * @param  string  $password
147
     * @param  string  $role
148
     * @return object
149
     */
150
    public function login($email, $password, $role = false)
0 ignored issues
show
Unused Code introduced by
The parameter $role 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...
151
    {
152
        if (! $user = $this->repo->first(['email' => $email])) {
153
            \Errors::loginFailed();
154
        } elseif ($user->blocked) {
155
            \Errors::userIsBlocked();
156
        } elseif (! config('skeleton.disable_confirm_email') && ! $user->confirmed) {
157
            \Errors::emailNotConfirmed();
158
        }
159
160
        return ['user' => $user, 'tokens' => $this->loginProxy->login($user->email, $password)];
161
    }
162
163
    /**
164
     * Handle the social login request to the application.
165
     *
166
     * @param  string $authCode
167
     * @param  string $accessToken
168
     * @return array
169
     */
170
    public function loginSocial($authCode, $accessToken, $type)
171
    {
172
        $access_token = $authCode ? Arr::get(\Socialite::driver($type)->getAccessTokenResponse($authCode), 'access_token') : $accessToken;
173
        $user         = \Socialite::driver($type)->userFromToken($access_token);
174
175
        if (! $user->email) {
176
            \Errors::noSocialEmail();
177
        }
178
179
        if (! $this->repo->first(['email' => $user->email])) {
180
            $this->register($user->email, '', true);
0 ignored issues
show
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...
181
        }
182
183
        return $this->loginProxy->login($user->email, config('skeleton.social_pass'));
184
    }
185
    
186
    /**
187
     * Handle the registration request.
188
     *
189
     * @param  string  $name
190
     * @param  string  $email
191
     * @param  string  $password
192
     * @param  boolean $skipConfirmEmail
193
     * @return array
194
     */
195
    public function register($name, $email, $password, $skipConfirmEmail = false)
196
    {
197
        $user = $this->repo->save([
198
            'name'      => $name,
199
            'email'     => $email,
200
            'password'  => $password,
201
            'confirmed' => $skipConfirmEmail
202
        ]);
203
204
        if (! $skipConfirmEmail && ! config('skeleton.disable_confirm_email')) {
205
            $this->sendConfirmationEmail($user->email);
206
        }
207
208
        return $user;
209
    }
210
    
211
    /**
212
     * Block the user.
213
     *
214
     * @param  integer $userId
215
     * @return object
216
     */
217
    public function block($userId)
218
    {
219
        if (\Auth::id() == $userId) {
220
            \Errors::noPermissions();
221
        }
222
        
223
        return $this->repo->save(['id' => $userId, 'blocked' => 1]);
224
    }
225
226
    /**
227
     * Unblock the user.
228
     *
229
     * @param  integer $userId
230
     * @return object
231
     */
232
    public function unblock($userId)
233
    {
234
        return $this->repo->save(['id' => $userId, 'blocked' => 0]);
235
    }
236
237
    /**
238
     * Send a reset link to the given user.
239
     *
240
     * @param  string  $email
241
     * @return void
242
     */
243
    public function sendReset($email)
244
    {
245
        if (! $user = $this->repo->first(['email' => $email])) {
246
            \Errors::notFound('email');
247
        }
248
249
        $token = \Password::getService()->create($user);
250
        $this->notificationService->notify($user, 'ResetPassword', $token);
251
    }
252
253
    /**
254
     * Reset the given user's password.
255
     *
256
     * @param   string  $email
257
     * @param   string  $password
258
     * @param   string  $passwordConfirmation
259
     * @param   string  $token
260
     * @return string|void
261
     */
262
    public function resetPassword($email, $password, $passwordConfirmation, $token)
263
    {
264
        $response = \Password::reset([
265
            'email'                 => $email,
266
            'password'              => $password,
267
            'password_confirmation' => $passwordConfirmation,
268
            'token'                 => $token
269
        ], function ($user, $password) {
270
            $this->repo->save(['id' => $user->id, 'password' => $password]);
271
        });
272
273
        switch ($response) {
274
            case \Password::PASSWORD_RESET:
275
                return 'success';
276
                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...
277
278
            case \Password::INVALID_TOKEN:
279
                \Errors::invalidResetToken();
280
                break;
281
282
            case \Password::INVALID_PASSWORD:
283
                \Errors::invalidResetPassword();
284
                break;
285
286
            case \Password::INVALID_USER:
287
                \Errors::notFound('user');
288
                break;
289
        }
290
    }
291
292
    /**
293
     * Change the logged in user password.
294
     *
295
     * @param  string  $password
296
     * @param  string  $oldPassword
297
     * @return void
298
     */
299
    public function changePassword($password, $oldPassword)
300
    {
301
        $user = \Auth::user();
302
        if (! \Hash::check($oldPassword, $user->password)) {
303
            \Errors::invalidOldPassword();
304
        }
305
306
        $this->repo->save(['id' => $user->id, 'password' => $password]);
307
    }
308
309
    /**
310
     * Confirm email using the confirmation code.
311
     *
312
     * @param  string $confirmationCode
313
     * @return void
314
     */
315
    public function confirmEmail($confirmationCode)
316
    {
317
        if (! $user = $this->repo->first(['confirmation_code' => $confirmationCode])) {
318
            \Errors::invalidConfirmationCode();
319
        }
320
321
        $this->repo->save(['id' => $user->id, 'confirmed' => 1, 'confirmation_code' => null]);
322
    }
323
324
    /**
325
     * Send the confirmation mail.
326
     *
327
     * @param  string $email
328
     * @return void
329
     */
330
    public function sendConfirmationEmail($email)
331
    {
332
        $user = $this->repo->first(['email' => $email]);
333
        if ($user->confirmed) {
334
            \Errors::emailAlreadyConfirmed();
335
        }
336
337
        $this->repo->save(['id' => $user->id, 'confirmation_code' => sha1(microtime())]);
338
        $this->notificationService->notify($user, 'ConfirmEmail');
339
    }
340
341
    /**
342
     * Save the given data to the logged in user.
343
     *
344
     * @param  string $name
345
     * @param  string $email
346
     * @param  string $profilePicture
347
     * @return void
348
     */
349
    public function saveProfile($name, $email, $profilePicture = false)
350
    {
351
        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...
352
            $data['profile_picture'] = \Media::uploadImageBas64($profilePicture, 'users/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...
353
        }
354
        
355
        $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...
356
        return $this->repo->save([
357
            'id'             => \Auth::id(),
358
            'name'           => $name,
359
            'email'          => $email,
360
            'profilePicture' => $profilePicture,
361
        ]);
362
    }
363
364
    /**
365
     * Logs out the user, revoke access token and refresh token.
366
     *
367
     * @return void
368
     */
369
    public function logout()
370
    {
371
        $this->oauthClientService->revokeAccessToken(\Auth::user()->token());
372
    }
373
374
    /**
375
     * Attempt to refresh the access token using the given refresh token.
376
     *
377
     * @param  string $refreshToken
378
     * @return array
379
     */
380
    public function refreshToken($refreshToken)
381
    {
382
        return $this->loginProxy->refreshToken($refreshToken);
383
    }
384
}
385