User::setCurrent()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Model;
6
7
use Application\Acl\Acl;
8
use Application\Api\Enum\UserRoleType;
9
use Application\Api\Output\GlobalPermissionsListType;
10
use Application\Api\Scalar\LoginType;
11
use Application\Enum\UserType;
12
use Application\Repository\UserRepository;
13
use Application\Service\Role;
14
use Application\Traits\HasInstitution;
15
use Application\Traits\HasSite;
16
use Application\Traits\HasSiteInterface;
17
use Cake\Chronos\Chronos;
18
use Doctrine\Common\Collections\ArrayCollection;
19
use Doctrine\Common\Collections\Collection as DoctrineCollection;
20
use Doctrine\ORM\Mapping as ORM;
21
use Ecodev\Felix\Api\Exception;
22
use Ecodev\Felix\Model\CurrentUser;
23
use Ecodev\Felix\Model\Traits\HasName;
24
use Ecodev\Felix\Utility;
25
use GraphQL\Doctrine\Attribute as API;
26
27
/**
28
 * User.
29
 */
30
#[ORM\UniqueConstraint(name: 'unique_login', columns: ['login', 'site'])]
31
#[ORM\UniqueConstraint(name: 'unique_email', columns: ['email', 'site'])]
32
#[ORM\Entity(UserRepository::class)]
33
class User extends AbstractModel implements \Ecodev\Felix\Model\User, HasSiteInterface
34
{
35
    use HasInstitution;
36
    use HasName;
37
    use HasSite;
38
39
    final public const ROLE_ANONYMOUS = 'anonymous';
40
    final public const ROLE_STUDENT = 'student';
41
    final public const ROLE_JUNIOR = 'junior';
42
    final public const ROLE_SENIOR = 'senior';
43
    final public const ROLE_MAJOR = 'major';
44
    final public const ROLE_ADMINISTRATOR = 'administrator';
45
46
    private static ?User $currentUser = null;
47
48
    /**
49
     * @var DoctrineCollection<Collection>
50
     */
51
    #[ORM\ManyToMany(targetEntity: Collection::class, mappedBy: 'users')]
52
    private DoctrineCollection $collections;
53
54
    /**
55
     * Set currently logged in user
56
     * WARNING: this method should only be called from \Application\Authentication\AuthenticationListener.
57
     */
58 144
    public static function setCurrent(?self $user): void
59
    {
60 144
        self::$currentUser = $user;
61
62
        // Initialize ACL filter with current user if a logged in one exists
63
        /** @var UserRepository $userRepository */
64 144
        $userRepository = _em()->getRepository(self::class);
65 144
        $aclFilter = $userRepository->getAclFilter();
66 144
        $aclFilter->setUser($user);
67
68 144
        CurrentUser::set($user);
69
    }
70
71
    /**
72
     * Returns currently logged user or null.
73
     */
74 82
    public static function getCurrent(): ?self
75
    {
76 82
        return self::$currentUser;
77
    }
78
79
    /**
80
     * After a `_em()->clear()` this will reload the current user, if any, in order
81
     * to refresh all data and relations and keep everything else working.
82
     */
83 2
    public static function reloadCurrentUser(): void
84
    {
85 2
        $user = self::$currentUser;
86 2
        if ($user) {
87
            $reloadedUser = _em()->getRepository(self::class)->getOneById($user->getId());
88
            self::$currentUser = $reloadedUser;
89
        }
90
    }
91
92
    #[ORM\Column(type: 'string', length: 191)]
93
    private string $login = '';
94
95
    #[API\Exclude]
96
    #[ORM\Column(type: 'string', length: 255)]
97
    private string $password = '';
98
99
    #[ORM\Column(type: 'string', length: 191, nullable: true)]
100
    private ?string $email = null;
101
102
    #[ORM\Column(type: 'UserRole', options: ['default' => self::ROLE_STUDENT])]
103
    private string $role = self::ROLE_STUDENT;
104
105
    #[ORM\Column(type: 'datetime', nullable: true)]
106
    private ?Chronos $activeUntil = null;
107
108
    #[ORM\Column(type: 'datetime', nullable: true)]
109
    private ?Chronos $termsAgreement = null;
110
111
    #[ORM\Column(type: 'enum', options: ['default' => UserType::Default])]
112
    private UserType $type = UserType::Default;
113
114
    /**
115
     * @param string $role role for new user
116
     */
117 41
    public function __construct(string $role = self::ROLE_STUDENT)
118
    {
119 41
        $this->collections = new ArrayCollection();
120 41
        $this->role = $role;
121
    }
122
123
    /**
124
     * Set login (eg: johndoe).
125
     */
126 2
    #[API\Input(type: LoginType::class)]
127
    public function setLogin(string $login): void
128
    {
129 2
        $this->login = $login;
130
    }
131
132
    /**
133
     * Get login (eg: johndoe).
134
     */
135 17
    #[API\Field(type: LoginType::class)]
136
    public function getLogin(): string
137
    {
138 17
        return $this->login;
139
    }
140
141
    /**
142
     * Encrypt and change the user password.
143
     */
144 3
    public function setPassword(string $password): void
145
    {
146
        // Ignore empty password that could be sent "by mistake" by the client
147
        // when agreeing to terms
148 3
        if ($password === '') {
149 1
            return;
150
        }
151
152 3
        $this->password = password_hash($password, PASSWORD_DEFAULT);
153
    }
154
155
    /**
156
     * Returns the hashed password.
157
     */
158 3
    #[API\Exclude]
159
    public function getPassword(): string
160
    {
161 3
        return $this->password;
162
    }
163
164
    /**
165
     * Set email.
166
     */
167 1
    #[API\Input(type: '?Email')]
168
    public function setEmail(?string $email): void
169
    {
170 1
        $this->email = $email;
171
    }
172
173
    /**
174
     * Get email.
175
     */
176 1
    #[API\Field(type: '?Email')]
177
    public function getEmail(): ?string
178
    {
179 1
        return $this->email;
180
    }
181
182
    /**
183
     * Get the user role.
184
     */
185 75
    #[API\Field(type: UserRoleType::class)]
186
    public function getRole(): string
187
    {
188 75
        return $this->role;
189
    }
190
191
    /**
192
     * Sets the user role.
193
     */
194 14
    #[API\Input(type: UserRoleType::class)]
195
    public function setRole(string $role): void
196
    {
197 14
        if (!Role::canUpdate(self::getCurrent(), $this->role, $role)) {
198 4
            $currentRole = self::getCurrent() ? self::getCurrent()->getRole() : self::ROLE_ANONYMOUS;
199
200 4
            throw new Exception($currentRole . ' is not allowed to change role from ' . $this->role . ' to ' . $role);
201
        }
202
203 10
        $this->role = $role;
204
    }
205
206
    /**
207
     * The date until the user is active. Or `null` if there is not limit in time.
208
     */
209 5
    public function getActiveUntil(): ?Chronos
210
    {
211 5
        return $this->activeUntil;
212
    }
213
214
    /**
215
     * The date until the user is active. Or `null` if there is not limit in time.
216
     */
217 2
    public function setActiveUntil(?Chronos $activeUntil): void
218
    {
219 2
        $this->activeUntil = $activeUntil;
220
    }
221
222
    /**
223
     * The date when the user agreed to the terms of usage.
224
     */
225
    public function getTermsAgreement(): ?Chronos
226
    {
227
        return $this->termsAgreement;
228
    }
229
230
    /**
231
     * The date when the user agreed to the terms of usage.
232
     *
233
     * A user cannot un-agree once he agreed.
234
     */
235
    public function setTermsAgreement(?Chronos $termsAgreement): void
236
    {
237
        $this->termsAgreement = $termsAgreement;
238
    }
239
240
    /**
241
     * Set user type.
242
     */
243 1
    public function setType(UserType $type): void
244
    {
245 1
        $this->type = $type;
246
    }
247
248
    /**
249
     * Get user type.
250
     */
251 2
    public function getType(): UserType
252
    {
253 2
        return $this->type;
254
    }
255
256
    /**
257
     * Get a list of global permissions for this user.
258
     */
259 3
    #[API\Field(type: GlobalPermissionsListType::class)]
260
    public function getGlobalPermissions(): array
261
    {
262 3
        $acl = new Acl();
263 3
        $types = [
264 3
            Artist::class,
265 3
            Card::class,
266 3
            Change::class,
267 3
            Collection::class,
268 3
            Country::class,
269 3
            Dating::class,
270 3
            Institution::class,
271 3
            Tag::class,
272 3
            Domain::class,
273 3
            DocumentType::class,
274 3
            News::class,
275 3
            Period::class,
276 3
            Material::class,
277 3
            AntiqueName::class,
278 3
            self::class,
279 3
        ];
280
281 3
        $permissions = ['create'];
282 3
        $result = [];
283
284 3
        $previousUser = self::getCurrent();
285 3
        self::setCurrent($this);
286 3
        foreach ($types as $type) {
287 3
            $instance = new $type();
288
289
            // Simulate current site on new object
290 3
            if ($instance instanceof HasSiteInterface) {
291 3
                $site = $this->getSite();
292 3
                $instance->setSite($site);
293
            }
294
295 3
            $sh = lcfirst(Utility::getShortClassName($instance));
296 3
            $result[$sh] = [];
297
298 3
            foreach ($permissions as $p) {
299 3
                $result[$sh][$p] = $acl->isCurrentUserAllowed($instance, $p);
300
            }
301
        }
302
303 3
        self::setCurrent($previousUser);
304
305 3
        return $result;
306
    }
307
308
    /**
309
     * Notify the Card that it was added to a Collection.
310
     * This should only be called by Collection::addCard().
311
     */
312
    public function collectionAdded(Collection $collection): void
313
    {
314
        $this->collections->add($collection);
315
    }
316
317
    /**
318
     * Notify the Card that it was removed from a Collection.
319
     * This should only be called by Collection::removeCard().
320
     */
321
    public function collectionRemoved(Collection $collection): void
322
    {
323
        $this->collections->removeElement($collection);
324
    }
325
326
    /**
327
     * Whether the user is allowed to log in or stay logged in.
328
     */
329 5
    public function canLogin(): bool
330
    {
331 5
        return !$this->getActiveUntil() || $this->getActiveUntil() > new Chronos();
332
    }
333
}
334