UserService::toArray()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 2
b 0
f 0
nc 4
nop 1
dl 0
loc 10
ccs 8
cts 8
cp 1
crap 3
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
     * @throws UserException
174
     */
175
    public function registerNewUserWithoutPassword(string $email): UserInterface
176
    {
177
        $this->duplicateUserCheck($email);
178
        $password = $this->createRandomPassword();
179
180
        return  $this->createUser($email, $password);
181
    }
182
183
    /**
184
     * @return string
185
     */
186
    private function createRandomPassword(): string
187
    {
188
        return \openssl_random_pseudo_bytes(12);
189
    }
190
191
    /**
192
     * @param string $email
193
     * @param string $password
194
     * @return UserInterface
195
     */
196 1
    private function createUser(string $email, string $password): UserInterface
197
    {
198 1
        $person = new Person();
199
        /** @var UserInterface $user */
200 1
        $user = new $this->userClass();
201 1
        $state = new State(State::STATE_UNACTIVATED);
202 1
        $user->setPerson($person);
203 1
        $user->setEmail($email);
204 1
        $user->setRegistrationDate(new DateTime());
205 1
        $user->setState($state);
206
207 1
        $bcrypt = new Bcrypt();
208 1
        $bcrypt->setCost(14);
209 1
        $encryptedPassword = $bcrypt->create($password);
210 1
        $user->setPassword($encryptedPassword);
211 1
        $this->saveUser($user);
212
213 1
        return $user;
214
    }
215
216
    /**
217
     * @param UserInterface $user
218
     * @param $password
219
     * @return UserInterface
220
     * @throws \Doctrine\ORM\OptimisticLockException
221
     */
222 26
    public function changePassword(UserInterface $user, string $password): UserInterface
223
    {
224 26
        $bcrypt = new Bcrypt();
225 26
        $bcrypt->setCost(14);
226
227 26
        $encryptedPassword = $bcrypt->create($password);
228 26
        $user->setPassword($encryptedPassword);
229 26
        $this->saveUser($user);
230
231 26
        return $user;
232
    }
233
234
    /**
235
     * @param UserInterface $user
236
     * @param int $expiry_days
237
     * @return EmailLink
238
     * @throws \Doctrine\ORM\OptimisticLockException
239
     */
240 2
    public function generateEmailLink(UserInterface $user, int $expiry_days = 7): EmailLink
241
    {
242 2
        $date = new DateTime();
243 2
        $date->modify('+'.$expiry_days.' days');
244 2
        $token = md5(uniqid(rand(), true));
245 2
        $link = new EmailLink();
246 2
        $link->setUser($user);
247 2
        $link->setToken($token);
248 2
        $link->setExpiryDate($date);
249
250 2
        return $this->getEmailLinkRepository()->save($link);
251
    }
252
253
    /**
254
     * @param EmailLink $link
255
     * @throws \Doctrine\ORM\ORMException
256
     */
257 2
    public function deleteEmailLink(EmailLink $link): void
258
    {
259
        /** @var EmailLink $link */
260 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

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