Passed
Push — master ( e7daf4...e2c26a )
by Sylvain
09:40
created

UserRepository::getOneById()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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