Passed
Push — master ( a78bf1...88e21c )
by Derek Stephen
02:17
created

UserService::getPersonSvc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Del\Service;
4
5
use DateTime;
6
use Del\Criteria\UserCriteria;
7
use Del\Entity\EmailLink;
8
use Del\Entity\User;
9
use Del\Person\Entity\Person;
10
use Del\Entity\UserInterface;
11
use Del\Exception\EmailLinkException;
12
use Del\Exception\UserException;
13
use Del\Repository\EmailLink as EmailLinkRepository;
14
use Del\Repository\UserRepository;
15
use Del\Person\Service\PersonService;
16
use Del\Value\User\State;
17
use Doctrine\ORM\EntityManager;
18
use InvalidArgumentException;
19
use Laminas\Crypt\Password\Bcrypt;
20
21
class UserService
22
{
23
    protected EntityManager $em;
24
    private PersonService $personSvc;
25
    private string $userClass;
26
27
    private UserRepository $userRepository;
28
29 28
    public function __construct(EntityManager $entityManager,  PersonService $personService)
30
    {
31 28
        $this->em = $entityManager;
32 28
        $this->personSvc = $personService;
33 28
        $this->setUserClass(User::class);
34
    }
35
36
    /**
37
     * @param array $data
38
     * @return UserInterface
39
     * @throws \Exception
40
     */
41 8
    public function createFromArray(array $data): UserInterface
42
    {
43
        /** @var UserInterface $user */
44 8
        $user = new $this->userClass();
45 8
        $person = isset($data['person']) ? $data['person'] : new Person();
46 8
        $user->setPerson($person);
47 8
        isset($data['id']) ? $user->setId($data['id']) : null;
48 8
        isset($data['email']) ? $user->setEmail($data['email']) : null;
49 8
        isset($data['password']) ? $user->setPassword($data['password']) : null;
50 8
        isset($data['state']) ? $user->setState(new State($data['state'])) : null;
51 8
        isset($data['registrationDate']) ? $user->setRegistrationDate(new DateTime($data['registrationDate'])) : null;
52 8
        isset($data['lastLogin']) ? $user->setLastLogin(new DateTime($data['lastLogin'])) : null;
53
54 8
        return $user;
55
    }
56
57
    /**
58
     * @return array
59
     */
60 1
    public function toArray(UserInterface $user): array
61
    {
62
        return [
63 1
            'id' => $user->getID(),
64 1
            'email' => $user->getEmail(),
65 1
            'person' => $user->getPerson(),
66 1
            'password' => $user->getPassword(),
67 1
            'state' => $user->getState()->getValue(),
68 1
            'registrationDate' => $user->getRegistrationDate() === null ? null : $user->getRegistrationDate()->format('Y-m-d H:i:s'),
69 1
            'lastLoginDate' => $user->getLastLoginDate() === null ? null : $user->getLastLoginDate()->format('Y-m-d H:i:s'),
70
        ];
71
    }
72
73
    /**
74
     * @param UserInterface $user
75
     * @return UserInterface
76
     * @throws \Doctrine\ORM\OptimisticLockException
77
     */
78 26
    public function saveUser(UserInterface $user): UserInterface
79
    {
80 26
        return $this->getUserRepository()->save($user);
81
    }
82
83
    /**
84
     * @param int $id
85
     * @return UserInterface|null
86
     */
87 1
    public function findUserById(int $id): ?UserInterface
88
    {
89 1
        $criteria = new UserCriteria();
90 1
        $criteria->setId($id);
91 1
        $results = $this->getUserRepository()->findByCriteria($criteria);
92
93 1
        return $results && count($results) ? $results[0] : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression $results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
94
    }
95
96
    /**
97
     * @param string $email
98
     * @return UserInterface|null
99
     */
100 3
    public function findUserByEmail(string $email): ?UserInterface
101
    {
102 3
        $criteria = new UserCriteria();
103 3
        $criteria->setEmail($email);
104 3
        $result = $this->getUserRepository()->findByCriteria($criteria);
105 3
        return $result && count($result) ? $result[0] : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
106
    }
107
108 27
    protected function getUserRepository(): UserRepository
109
    {
110 27
        if (!isset($this->userRepository)) {
111 27
            $this->userRepository = $this->em->getRepository($this->userClass);
112
        }
113
114 27
        return $this->userRepository;
115
    }
116
117 5
    private function getEmailLinkRepository(): EmailLinkRepository
118
    {
119 5
        return $this->em->getRepository(EmailLink::class);
120
    }
121
122 1
    public function hasProfile(UserInterface $user): bool
123
    {
124 1
        $has = false;
125 1
        $person = $user->getPerson();
126
127 1
        if (!empty($person->getFirstname()) && !empty($person->getLastname())) {
128 1
            $has = true;
129
        }
130
131 1
        return $has;
132
    }
133
134
    /**
135
     * @param array $data
136
     * @return UserInterface
137
     * @throws UserException
138
     * @throws \Doctrine\ORM\OptimisticLockException
139
     */
140 4
    public function registerUser(array $data): UserInterface
141
    {
142 4
        if (!isset($data['email']) || !isset($data['password']) || !isset($data['confirm'])) {
143 1
            throw new InvalidArgumentException('Required fields missing', 400);
144
        }
145
146 3
        if ($data['password'] !== $data['confirm']) {
147 1
            throw new UserException(UserException::WRONG_PASSWORD);
148
        }
149
150 2
        $email = $data['email'];
151 2
        $password = $data['password'];
152 2
        $this->duplicateUserCheck($email);
153
154 1
        return $this->createUser($email, $password);
155
    }
156
157
    /**
158
     * @param string $email
159
     * @return void
160
     * @throws UserException
161
     */
162 2
    private function duplicateUserCheck(string $email): void
163
    {
164 2
        if($this->findUserByEmail($email)) {
165 1
            throw new UserException(UserException::USER_EXISTS, 400);
166
        }
167
    }
168
169
    /**
170
     * this creates a new user from an email
171
     * @param string $email
172
     * @return UserInterface
173
     */
174
    public function registerNewUserWithoutPassword(string $email): UserInterface
175
    {
176
        $this->duplicateUserCheck($email);
177
        $password = $this->createRandomPassword();
178
179
        return  $this->createUser($email, $password);
180
    }
181
182
    /**
183
     * @return string
184
     */
185
    private function createRandomPassword(): string
186
    {
187
        return \openssl_random_pseudo_bytes(12);
188
    }
189
190
    /**
191
     * @param string $email
192
     * @param string $password
193
     * @return UserInterface
194
     */
195 1
    private function createUser(string $email, string $password): UserInterface
196
    {
197 1
        $person = new Person();
198
        /** @var UserInterface $user */
199 1
        $user = new $this->userClass();
200 1
        $state = new State(State::STATE_UNACTIVATED);
201 1
        $user->setPerson($person);
202 1
        $user->setEmail($email);
203 1
        $user->setRegistrationDate(new DateTime());
204 1
        $user->setState($state);
205
206 1
        $bcrypt = new Bcrypt();
207 1
        $bcrypt->setCost(14);
208 1
        $encryptedPassword = $bcrypt->create($password);
209 1
        $user->setPassword($encryptedPassword);
210 1
        $this->saveUser($user);
211
212 1
        return $user;
213
    }
214
215
    /**
216
     * @param UserInterface $user
217
     * @param $password
218
     * @return UserInterface
219
     * @throws \Doctrine\ORM\OptimisticLockException
220
     */
221 26
    public function changePassword(UserInterface $user, string $password): UserInterface
222
    {
223 26
        $bcrypt = new Bcrypt();
224 26
        $bcrypt->setCost(14);
225
226 26
        $encryptedPassword = $bcrypt->create($password);
227 26
        $user->setPassword($encryptedPassword);
228 26
        $this->saveUser($user);
229
230 26
        return $user;
231
    }
232
233
    /**
234
     * @param UserInterface $user
235
     * @param int $expiry_days
236
     * @return EmailLink
237
     * @throws \Doctrine\ORM\OptimisticLockException
238
     */
239 2
    public function generateEmailLink(UserInterface $user, int $expiry_days = 7): EmailLink
240
    {
241 2
        $date = new DateTime();
242 2
        $date->modify('+'.$expiry_days.' days');
243 2
        $token = md5(uniqid(rand(), true));
244 2
        $link = new EmailLink();
245 2
        $link->setUser($user);
246 2
        $link->setToken($token);
247 2
        $link->setExpiryDate($date);
248
249 2
        return $this->getEmailLinkRepository()->save($link);
250
    }
251
252
    /**
253
     * @param EmailLink $link
254
     * @throws \Doctrine\ORM\ORMException
255
     */
256 2
    public function deleteEmailLink(EmailLink $link): void
257
    {
258
        /** @var EmailLink $link */
259 2
        $link = $this->em->merge($link);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\EntityManager::merge() has been deprecated: 2.7 This method is being removed from the ORM and won't have any replacement ( Ignorable by Annotation )

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

259
        $link = /** @scrutinizer ignore-deprecated */ $this->em->merge($link);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
260 2
        $this->getEmailLinkRepository()->delete($link);
261
    }
262
263
    /**
264
     * @param UserInterface $user
265
     * @param bool $deletePerson
266
     * @throws \Doctrine\ORM\OptimisticLockException
267
     */
268 7
    public function deleteUser(UserInterface $user, bool $deletePerson = false):  void
269
    {
270 7
        $this->getUserRepository()->delete($user,$deletePerson);
271
    }
272
273
    /**
274
     * @param string $email
275
     * @param string $token
276
     * @return EmailLink
277
     * @throws EmailLinkException
278
     */
279 4
    public function findEmailLink(string $email, string $token): EmailLink
280
    {
281 4
        $link = $this->getEmailLinkRepository()->findByToken($token);
282 4
        if(!$link) {
283 1
            throw new EmailLinkException(EmailLinkException::LINK_NOT_FOUND);
284
        }
285 3
        if($link->getUser()->getEmail() != $email) {
286 1
            throw new EmailLinkException(EmailLinkException::LINK_NO_MATCH);
287
        }
288 2
        if($link->getExpiryDate() < new DateTime()) {
289 1
            throw new EmailLinkException(EmailLinkException::LINK_EXPIRED);
290
        }
291 1
        return $link;
292
    }
293
294
    /**
295
     * @param string $email
296
     * @param string $password
297
     * @return int
298
     * @throws UserException
299
     */
300 6
    public function authenticate(string $email, string $password): int
301
    {
302 6
        $criteria = new UserCriteria();
303 6
        $criteria->setEmail($email);
304
305 6
        $user = $this->getUserRepository()->findByCriteria($criteria);
306
307 6
        if(empty($user)) {
308 1
            throw new UserException(UserException::USER_NOT_FOUND);
309
        }
310
311
        /** @var UserInterface $user  */
312 5
        $user = $user[0];
313
314 5
        switch($user->getState()->getValue()) {
315 5
            case State::STATE_UNACTIVATED:
316 1
                throw new UserException(UserException::USER_UNACTIVATED);
317 4
            case State::STATE_DISABLED:
318 3
            case State::STATE_SUSPENDED:
319 1
                throw new UserException(UserException::USER_DISABLED);
320 3
            case State::STATE_BANNED:
321 1
                throw new UserException(UserException::USER_BANNED);
322
        }
323
324 2
        $bcrypt = new Bcrypt();
325 2
        $bcrypt->setCost(14);
326
327 2
        if(!$bcrypt->verify($password, $user->getPassword()))
328
        {
329 1
            throw new UserException(UserException::WRONG_PASSWORD);
330
        }
331
332 1
        return $user->getID();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $user->getID() could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
333
    }
334
335
    /**
336
     * @param UserCriteria $criteria
337
     * @return array
338
     */
339 1
    public function findByCriteria(UserCriteria $criteria): array
340
    {
341 1
        return $this->getUserRepository()->findByCriteria($criteria);
342
    }
343
344
    /**
345
     * @param UserCriteria $criteria
346
     * @return UserInterface|null
347
     */
348 3
    public function findOneByCriteria(UserCriteria $criteria): ?UserInterface
349
    {
350 3
        $results = $this->getUserRepository()->findByCriteria($criteria);
351
352 3
        return count($results) > 0 ? $results[0] : null;
353
    }
354
355
    /**
356
     * @param UserInterface $user
357
     * @param $password
358
     * @return bool
359
     */
360 1
    public function checkPassword(UserInterface $user, string $password): bool
361
    {
362 1
        $bcrypt = new Bcrypt();
363 1
        $bcrypt->setCost(14);
364
365 1
        return $bcrypt->verify($password, $user->getPassword());
366
    }
367
368
    /**
369
     * @param string $fullyQualifiedClassName
370
     */
371 28
    public function setUserClass(string $fullyQualifiedClassName): void
372
    {
373 28
        $this->userClass = $fullyQualifiedClassName;
374
    }
375
376
    /**
377
     * @return PersonService
378
     */
379 1
    public function getPersonSvc(): PersonService
380
    {
381 1
        return $this->personSvc;
382
    }
383
}
384