Passed
Push — master ( a4d77f...c3a602 )
by Adrien
15:17
created

UserRepository   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 201
Duplicated Lines 0 %

Test Coverage

Coverage 74.73%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 23
eloc 88
dl 0
loc 201
rs 10
c 3
b 1
f 0
ccs 71
cts 95
cp 0.7473

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getAccessibleSubQuery() 0 7 2
A getAllNonFamilyOwnersWithAccount() 0 8 1
A getAllFamilyOwners() 0 9 1
A getAllToQueueBalanceMessage() 0 26 2
A getOneById() 0 5 1
A deleteOldRegistrations() 0 20 3
B getOneByLoginPassword() 0 30 9
A exists() 0 15 2
A getAllAdministratorsToNotify() 0 12 1
A getOneByLoginOrEmail() 0 13 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Repository;
6
7
use Application\DBAL\Types\BookingTypeType;
8
use Application\Model\User;
9
use Cake\Chronos\Chronos;
10
use Doctrine\DBAL\Connection;
11
use Ecodev\Felix\Repository\LimitedAccessSubQuery;
12
13
/**
14
 * @extends AbstractRepository<User>
15
 */
16
class UserRepository extends AbstractRepository implements LimitedAccessSubQuery
17
{
18
    /**
19
     * Returns pure SQL to get ID of all objects that are accessible to given user.
20
     */
21 58
    public function getAccessibleSubQuery(?\Ecodev\Felix\Model\User $user): string
22
    {
23 58
        if (!$user) {
24 5
            return '-1';
25
        }
26
27 53
        return $this->getAllIdsQuery();
28
    }
29
30
    /**
31
     * Returns the user authenticated by its email and password.
32
     */
33 3
    public function getOneByLoginPassword(string $login, string $password): ?User
34
    {
35 3
        $user = $this->getOneByLoginOrEmail($login);
36
37 3
        if (!$user) {
38 1
            return null;
39
        }
40
41
        // Check user status
42 3
        if (!in_array($user->getStatus(), [User::STATUS_ACTIVE, User::STATUS_INACTIVE, User::STATUS_NEW], true)) {
43
            return null;
44
        }
45
46 3
        $hashFromDb = $user->getPassword();
47 3
        $isMd5 = mb_strlen($hashFromDb) === 32 && ctype_xdigit($hashFromDb);
48
49
        // If we found a user and he has a correct MD5 or correct new hash, then return the user
50 3
        if (($isMd5 && md5($password) === $hashFromDb) || password_verify($password, $hashFromDb)) {
51
52
            // Update the hash in DB, if we are still MD5, or if PHP default options changed
53 3
            if ($isMd5 || password_needs_rehash($hashFromDb, PASSWORD_DEFAULT)) {
54 3
                $user->setPassword($password);
55
            }
56 3
            $user->revokeToken();
57 3
            _em()->flush();
58
59 3
            return $user;
60
        }
61
62 1
        return null;
63
    }
64
65
    /**
66
     * Unsecured way to get a user from its ID.
67
     *
68
     * This should only be used in tests or controlled environment.
69
     */
70 24
    public function getOneById(int $id): ?User
71
    {
72 24
        $user = $this->getAclFilter()->runWithoutAcl(fn () => $this->findOneById($id));
73
74 24
        return $user;
75
    }
76
77
    /**
78
     * Unsecured way to get a user from its login or its email.
79
     *
80
     * This should only be used in tests or controlled environment.
81
     */
82 100
    public function getOneByLoginOrEmail(?string $loginOrEmail): ?User
83
    {
84
        /** @var null|User $user */
85 100
        $user = $this->getAclFilter()->runWithoutAcl(
86 100
            fn () => $this->createQueryBuilder('user')
87 100
                ->orWhere('user.login IS NOT NULL AND user.login = :value')
88 100
                ->orWhere('user.email IS NOT NULL AND user.email = :value')
89 100
                ->setParameter('value', $loginOrEmail)
90
                ->getQuery()
91
                ->getOneOrNullResult()
92
        );
93
94 100
        return $user;
95
    }
96
97
    /**
98
     * Get all administrators to notify by email.
99
     *
100
     * @return User[]
101
     */
102 2
    public function getAllAdministratorsToNotify(): array
103
    {
104 2
        $qb = $this->createQueryBuilder('user')
105 2
            ->andWhere('user.status = :status')
106 2
            ->andWhere('user.role = :role')
107 2
            ->andWhere("user.email IS NOT NULL AND user.email != ''")
108 2
            ->setParameter('status', User::STATUS_ACTIVE)
109 2
            ->setParameter('role', User::ROLE_ADMINISTRATOR);
110
111 2
        $result = $this->getAclFilter()->runWithoutAcl(fn () => $qb->getQuery()->getResult());
112
113 2
        return $result;
114
    }
115
116 4
    public function getAllToQueueBalanceMessage(bool $onlyNegativeBalance = false): array
117
    {
118 4
        $qb = $this->createQueryBuilder('user')
119 4
            ->addSelect('account')
120 4
            ->addSelect('booking')
121 4
            ->addSelect('bookable')
122 4
            ->join('user.accounts', 'account')
123 4
            ->join('user.bookings', 'booking')
124 4
            ->join('booking.bookable', 'bookable')
125 4
            ->andWhere('user.status != :status')
126 4
            ->andWhere("user.email IS NOT NULL AND user.email != ''")
127 4
            ->andWhere('bookable.bookingType IN (:bookingType)')
128 4
            ->andWhere('bookable.isActive = true')
129 4
            ->andWhere('bookable.periodicPrice != 0')
130 4
            ->setParameter('bookingType', [BookingTypeType::MANDATORY, BookingTypeType::ADMIN_ASSIGNED], Connection::PARAM_STR_ARRAY)
131 4
            ->setParameter('status', User::STATUS_ARCHIVED)
132 4
            ->addOrderBy('user.id')
133 4
            ->addOrderBy('bookable.name');
134
135 4
        if ($onlyNegativeBalance) {
136 2
            $qb->andWhere('account.balance < 0');
137
        }
138
139 4
        $result = $this->getAclFilter()->runWithoutAcl(fn () => $qb->getQuery()->getResult());
140
141 4
        return $result;
142
    }
143
144
    /**
145
     * Return all users that are family owners (and should have Account).
146
     *
147
     * @return User[]
148
     */
149 1
    public function getAllFamilyOwners(): array
150
    {
151 1
        $qb = $this->createQueryBuilder('user')
152 1
            ->addSelect('account')
153 1
            ->leftJoin('user.accounts', 'account')
154 1
            ->andWhere('user.owner IS NULL')
155 1
            ->addOrderBy('user.id');
156
157 1
        return $qb->getQuery()->getResult();
1 ignored issue
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
158
    }
159
160
    /**
161
     * Return all users that are not family owners but still have an Account.
162
     *
163
     * @return User[]
164
     */
165 1
    public function getAllNonFamilyOwnersWithAccount(): array
166
    {
167 1
        $qb = $this->createQueryBuilder('user')
168 1
            ->join('user.accounts', 'account')
169 1
            ->andWhere('user.owner IS NOT NULL')
170 1
            ->addOrderBy('user.id');
171
172 1
        return $qb->getQuery()->getResult();
1 ignored issue
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
173
    }
174
175
    public function exists(string $login, ?int $excludedId): bool
176
    {
177
        if (null === $excludedId) {
178
            $excludedId = -1;
179
        }
180
181
        $sql = 'SELECT 1 FROM user WHERE login = :login AND id != :excludedId LIMIT 1';
182
        $params = [
183
            'login' => $login,
184
            'excludedId' => $excludedId,
185
        ];
186
187
        $result = $this->getEntityManager()->getConnection()->executeQuery($sql, $params)->fetchOne();
188
189
        return (bool) $result;
190
    }
191
192
    /**
193
     * Delete unconfirmed registrations older than a few days (user + account).
194
     *
195
     * @return int number of deleted users
196
     */
197
    public function deleteOldRegistrations(): int
198
    {
199
        $qb = $this->createQueryBuilder('user')
200
            ->addSelect('account')
201
            ->andWhere('user.login IS NULL AND user.creationDate < :creationDate')
202
            ->leftJoin('user.accounts', 'account')
203
            ->setParameter('creationDate', (new Chronos())->subDay(3));
204
205
        $users = $qb->getQuery()->getResult();
206
207
        foreach ($users as $user) {
208
            $account = $user->getAccount();
209
            if ($account) {
210
                _em()->remove($account);
211
            }
212
            _em()->remove($user);
213
        }
214
        _em()->flush();
215
216
        return count($users);
217
    }
218
}
219