Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
13.91% covered (danger)
13.91%
21 / 151
6.90% covered (danger)
6.90%
6 / 87
CRAP
0.00% covered (danger)
0.00%
0 / 1
User
13.91% covered (danger)
13.91%
21 / 151
6.90% covered (danger)
6.90%
6 / 87
6101.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUsername
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setUsername
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOldUsername
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setOldUsername
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPasswordHash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPasswordHash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setEmail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isEmailVerified
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setEmailIsVerified
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmailVerificationToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setEmailVerificationToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFirstName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFirstName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLastName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setLastName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFullName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGender
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setGender
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStreet
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setStreet
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPostalCode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPostalCode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCity
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCity
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRegion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRegion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCountryCode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCountryCode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBirthdate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setBirthdate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPhone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPhone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPermissions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPermissions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPermissionMap
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 setPermissionMap
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 hasPermission
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addPermission
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 removePermission
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getRoot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRoot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getParentUserId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setParentUserId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMemberType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setMemberType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMemberLastPaid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setMemberLastPaid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWantsPostalMail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setWantsPostalMail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPostalTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPostalTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPostalName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPostalName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getJoinedOn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setJoinedOn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getJoinedReason
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setJoinedReason
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLeftOn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setLeftOn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLeftReason
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setLeftReason
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSolvNumber
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setSolvNumber
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSiCardNumber
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setSiCardNumber
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAvatarImageId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setAvatarImageId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNotes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setNotes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLastLoginAt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setLastLoginAt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRoles
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addRole
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 removeRole
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __toString
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 pretty
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 softDelete
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
2
 getIdFieldNameForSearch
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIdForSearch
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCriteriaForQuery
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getTitleForSearch
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEntityNameForStorage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEntityIdForStorage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Olz\Entity\Users;
4
5use Doctrine\Common\Collections\ArrayCollection;
6use Doctrine\Common\Collections\Collection;
7use Doctrine\Common\Collections\Criteria;
8use Doctrine\Common\Collections\Expr\Expression;
9use Doctrine\ORM\Mapping as ORM;
10use Olz\Entity\Common\DataStorageInterface;
11use Olz\Entity\Common\DataStorageTrait;
12use Olz\Entity\Common\OlzEntity;
13use Olz\Entity\Common\SearchableInterface;
14use Olz\Entity\Roles\Role;
15use Olz\Repository\Users\UserRepository;
16
17#[ORM\Table(name: 'users')]
18#[ORM\Index(name: 'username_index', columns: ['username'])]
19#[ORM\Entity(repositoryClass: UserRepository::class)]
20class User extends OlzEntity implements DataStorageInterface, SearchableInterface {
21    use DataStorageTrait;
22
23    #[ORM\Id]
24    #[ORM\Column(type: 'integer', nullable: false)]
25    #[ORM\GeneratedValue]
26    public int $id;
27
28    #[ORM\Column(type: 'integer', nullable: true)]
29    public ?int $parent_user;
30
31    #[ORM\Column(type: 'string', nullable: false)]
32    public string $username;
33
34    #[ORM\Column(type: 'string', nullable: true)]
35    public ?string $old_username;
36
37    #[ORM\Column(type: 'text', nullable: true)]
38    public ?string $password;
39
40    #[ORM\Column(type: 'text', nullable: true)]
41    public ?string $email;
42
43    #[ORM\Column(type: 'boolean', nullable: false)]
44    public bool $email_is_verified;
45
46    #[ORM\Column(type: 'text', nullable: true)]
47    public ?string $email_verification_token;
48
49    #[ORM\Column(type: 'text', nullable: false)]
50    public string $first_name;
51
52    #[ORM\Column(type: 'text', nullable: false)]
53    public string $last_name;
54
55    #[ORM\Column(type: 'string', length: 2, nullable: true, options: ['comment' => 'M(ale), F(emale), or O(ther)'])]
56    public ?string $gender;
57
58    #[ORM\Column(type: 'text', nullable: true)]
59    public ?string $street;
60
61    #[ORM\Column(type: 'text', nullable: true)]
62    public ?string $postal_code;
63
64    #[ORM\Column(type: 'text', nullable: true)]
65    public ?string $city;
66
67    #[ORM\Column(type: 'text', nullable: true)]
68    public ?string $region;
69
70    #[ORM\Column(type: 'string', length: 3, nullable: true, options: ['comment' => 'two-letter code (ISO-3166-alpha-2)'])]
71    public ?string $country_code;
72
73    #[ORM\Column(type: 'date', nullable: true)]
74    public ?\DateTime $birthdate;
75
76    #[ORM\Column(type: 'text', nullable: true)]
77    public ?string $phone;
78
79    #[ORM\Column(type: 'text', nullable: true)]
80    public ?string $avatar_image_id;
81
82    #[ORM\Column(type: 'text', nullable: false)]
83    public string $permissions;
84
85    #[ORM\Column(type: 'text', nullable: true)]
86    public ?string $root;
87
88    #[ORM\Column(type: 'string', length: 3, nullable: true, options: ['comment' => 'Aktiv, Ehrenmitglied, Verein, Sponsor'])]
89    public ?string $member_type;
90
91    #[ORM\Column(type: 'date', nullable: true)]
92    public ?\DateTime $member_last_paid;
93
94    #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => 0])]
95    public bool $wants_postal_mail;
96
97    #[ORM\Column(type: 'text', nullable: true, options: ['comment' => 'if not {m: Herr, f: Frau, o: }'])]
98    public ?string $postal_title;
99
100    #[ORM\Column(type: 'text', nullable: true, options: ['comment' => "if not 'First Last'"])]
101    public ?string $postal_name;
102
103    #[ORM\Column(type: 'date', nullable: true)]
104    public ?\DateTime $joined_on;
105
106    #[ORM\Column(type: 'text', nullable: true)]
107    public ?string $joined_reason;
108
109    #[ORM\Column(type: 'date', nullable: true)]
110    public ?\DateTime $left_on;
111
112    #[ORM\Column(type: 'text', nullable: true)]
113    public ?string $left_reason;
114
115    #[ORM\Column(type: 'text', nullable: true)]
116    public ?string $solv_number;
117
118    #[ORM\Column(type: 'text', nullable: true)]
119    public ?string $si_card_number;
120
121    #[ORM\Column(type: 'text', nullable: false, options: ['default' => ''])]
122    public string $notes;
123
124    /** @var Collection<int|string, Role>&iterable<Role> */
125    #[ORM\JoinTable(name: 'users_roles')]
126    #[ORM\ManyToMany(targetEntity: Role::class, inversedBy: 'users')]
127    private Collection $roles;
128
129    #[ORM\Column(type: 'datetime', nullable: true)]
130    private ?\DateTime $last_login_at;
131
132    public function __construct() {
133        $this->roles = new ArrayCollection();
134    }
135
136    public function getId(): ?int {
137        return $this->id ?? null;
138    }
139
140    public function setId(int $new_value): void {
141        $this->id = $new_value;
142    }
143
144    public function getUsername(): string {
145        return $this->username;
146    }
147
148    public function setUsername(string $new_value): void {
149        $this->username = $new_value;
150    }
151
152    public function getOldUsername(): ?string {
153        return $this->old_username;
154    }
155
156    public function setOldUsername(?string $new_value): void {
157        $this->old_username = $new_value;
158    }
159
160    public function getPasswordHash(): ?string {
161        return $this->password;
162    }
163
164    public function setPasswordHash(?string $new_value): void {
165        $this->password = $new_value;
166    }
167
168    public function getEmail(): ?string {
169        return $this->email;
170    }
171
172    public function setEmail(?string $new_value): void {
173        $this->email = $new_value;
174    }
175
176    public function isEmailVerified(): bool {
177        return $this->email_is_verified;
178    }
179
180    public function setEmailIsVerified(bool $new_value): void {
181        $this->email_is_verified = $new_value;
182    }
183
184    public function getEmailVerificationToken(): ?string {
185        return $this->email_verification_token;
186    }
187
188    public function setEmailVerificationToken(?string $new_value): void {
189        $this->email_verification_token = $new_value;
190    }
191
192    public function getFirstName(): string {
193        return $this->first_name;
194    }
195
196    public function setFirstName(string $new_value): void {
197        $this->first_name = $new_value;
198    }
199
200    public function getLastName(): string {
201        return $this->last_name;
202    }
203
204    public function setLastName(string $new_value): void {
205        $this->last_name = $new_value;
206    }
207
208    public function getFullName(): string {
209        return "{$this->getFirstName()} {$this->getLastName()}";
210    }
211
212    public function getGender(): ?string {
213        return $this->gender;
214    }
215
216    public function setGender(?string $new_value): void {
217        $this->gender = $new_value;
218    }
219
220    public function getStreet(): ?string {
221        return $this->street;
222    }
223
224    public function setStreet(?string $new_value): void {
225        $this->street = $new_value;
226    }
227
228    public function getPostalCode(): ?string {
229        return $this->postal_code;
230    }
231
232    public function setPostalCode(?string $new_value): void {
233        $this->postal_code = $new_value;
234    }
235
236    public function getCity(): ?string {
237        return $this->city;
238    }
239
240    public function setCity(?string $new_value): void {
241        $this->city = $new_value;
242    }
243
244    public function getRegion(): ?string {
245        return $this->region;
246    }
247
248    public function setRegion(?string $new_value): void {
249        $this->region = $new_value;
250    }
251
252    public function getCountryCode(): ?string {
253        return $this->country_code;
254    }
255
256    public function setCountryCode(?string $new_value): void {
257        $this->country_code = $new_value;
258    }
259
260    public function getBirthdate(): ?\DateTime {
261        return $this->birthdate;
262    }
263
264    public function setBirthdate(?\DateTime $new_value): void {
265        $this->birthdate = $new_value;
266    }
267
268    public function getPhone(): ?string {
269        return $this->phone;
270    }
271
272    public function setPhone(?string $new_value): void {
273        $this->phone = $new_value;
274    }
275
276    public function getPermissions(): string {
277        return $this->permissions;
278    }
279
280    public function setPermissions(string $new_value): void {
281        $this->permissions = $new_value;
282    }
283
284    /** @return array<string, true> */
285    public function getPermissionMap(): array {
286        $permission_list = preg_split('/[ ]+/', $this->permissions ?? '');
287        if (!is_array($permission_list)) {
288            return [];
289        }
290        $permission_map = [];
291        foreach ($permission_list as $permission) {
292            if (strlen($permission) > 0) {
293                $permission_map[$permission] = true;
294            }
295        }
296        return $permission_map;
297    }
298
299    /** @param array<string, bool> $new_value */
300    public function setPermissionMap(array $new_value): void {
301        $permission_list = [];
302        foreach ($new_value as $key => $value) {
303            if ($value) {
304                $permission_list[] = $key;
305            }
306        }
307        $this->permissions = ' '.implode(' ', $permission_list).' ';
308    }
309
310    public function hasPermission(string $has_permission): bool {
311        $permission_map = $this->getPermissionMap();
312        return $permission_map[$has_permission] ?? false;
313    }
314
315    public function addPermission(string $add_permission): void {
316        $permission_map = $this->getPermissionMap();
317        $permission_map[$add_permission] = true;
318        $this->setPermissionMap($permission_map);
319    }
320
321    public function removePermission(string $remove_permission): void {
322        $permission_map = $this->getPermissionMap();
323        $permission_map[$remove_permission] = false;
324        $this->setPermissionMap($permission_map);
325    }
326
327    public function getRoot(): ?string {
328        return $this->root;
329    }
330
331    public function setRoot(?string $new_value): void {
332        $this->root = $new_value;
333    }
334
335    public function getParentUserId(): ?int {
336        return $this->parent_user;
337    }
338
339    public function setParentUserId(?int $new_value): void {
340        $this->parent_user = $new_value;
341    }
342
343    public function getMemberType(): ?string {
344        return $this->member_type;
345    }
346
347    public function setMemberType(?string $new_value): void {
348        $this->member_type = $new_value;
349    }
350
351    public function getMemberLastPaid(): ?\DateTime {
352        return $this->member_last_paid;
353    }
354
355    public function setMemberLastPaid(?\DateTime $new_value): void {
356        $this->member_last_paid = $new_value;
357    }
358
359    public function getWantsPostalMail(): bool {
360        return $this->wants_postal_mail;
361    }
362
363    public function setWantsPostalMail(bool $new_value): void {
364        $this->wants_postal_mail = $new_value;
365    }
366
367    public function getPostalTitle(): ?string {
368        return $this->postal_title;
369    }
370
371    public function setPostalTitle(?string $new_value): void {
372        $this->postal_title = $new_value;
373    }
374
375    public function getPostalName(): ?string {
376        return $this->postal_name;
377    }
378
379    public function setPostalName(?string $new_value): void {
380        $this->postal_name = $new_value;
381    }
382
383    public function getJoinedOn(): ?\DateTime {
384        return $this->joined_on;
385    }
386
387    public function setJoinedOn(?\DateTime $new_value): void {
388        $this->joined_on = $new_value;
389    }
390
391    public function getJoinedReason(): ?string {
392        return $this->joined_reason;
393    }
394
395    public function setJoinedReason(?string $new_value): void {
396        $this->joined_reason = $new_value;
397    }
398
399    public function getLeftOn(): ?\DateTime {
400        return $this->left_on;
401    }
402
403    public function setLeftOn(?\DateTime $new_value): void {
404        $this->left_on = $new_value;
405    }
406
407    public function getLeftReason(): ?string {
408        return $this->left_reason;
409    }
410
411    public function setLeftReason(?string $new_value): void {
412        $this->left_reason = $new_value;
413    }
414
415    public function getSolvNumber(): ?string {
416        return $this->solv_number;
417    }
418
419    public function setSolvNumber(?string $new_value): void {
420        $this->solv_number = $new_value;
421    }
422
423    public function getSiCardNumber(): ?string {
424        return $this->si_card_number;
425    }
426
427    public function setSiCardNumber(?string $new_value): void {
428        $this->si_card_number = $new_value;
429    }
430
431    public function getAvatarImageId(): ?string {
432        return $this->avatar_image_id;
433    }
434
435    public function setAvatarImageId(?string $new_value): void {
436        $this->avatar_image_id = $new_value;
437    }
438
439    public function getNotes(): string {
440        return $this->notes;
441    }
442
443    public function setNotes(string $new_value): void {
444        $this->notes = $new_value;
445    }
446
447    public function getLastLoginAt(): ?\DateTime {
448        return $this->last_login_at;
449    }
450
451    public function setLastLoginAt(?\DateTime $new_value): void {
452        $this->last_login_at = $new_value;
453    }
454
455    /** @return Collection<int|string, Role>&iterable<Role> */
456    public function getRoles(): Collection {
457        return $this->roles;
458    }
459
460    public function addRole(Role $role): void {
461        if (!$this->roles->contains($role)) {
462            $this->roles->add($role);
463        }
464    }
465
466    public function removeRole(Role $role): void {
467        $this->roles->removeElement($role);
468    }
469
470    // ---
471
472    public function __toString(): string {
473        $username = $this->getUsername();
474        $id = $this->getId();
475        return "{$username} (User ID: {$id})";
476    }
477
478    public function pretty(): string {
479        $is_verified = $this->isEmailVerified() ? '✅ verified' : '❌ not verified';
480        $has_permission = $this->hasPermission('verified_email') ? '✅ permission' : '❌ no permission';
481        $roles = implode(', ', array_map(
482            fn ($role): string => $role->getUsername(),
483            [...$this->roles],
484        ));
485        $has_password = $this->getPasswordHash() ? '✅ password' : '❌ no password';
486        $is_child = $this->getParentUserId() ? "🚸 child of {$this->getParentUserId()}" : '✅ parent';
487        return <<<ZZZZZZZZZZ
488            Name: {$this->getFullName()}
489            Username: {$this->getUsername()} (old: {$this->getOldUsername()})
490            E-Mail: {$this->getEmail()} ({$is_verified} / {$has_permission})
491            Password: {$has_password} / {$is_child}
492            Permissions: {$this->getPermissions()}
493            Roles ({$this->roles->count()}): {$roles}
494            ZZZZZZZZZZ;
495    }
496
497    public function softDelete(): void {
498        $this->entityUtils()->updateOlzEntity($this, ['onOff' => false]);
499        $this->setEmail('');
500        $this->setPasswordHash('');
501        $this->setPhone('');
502        $this->setGender(null);
503        $this->setBirthdate(null);
504        $this->setStreet(null);
505        $this->setPostalCode(null);
506        $this->setCity(null);
507        $this->setRegion(null);
508        $this->setCountryCode(null);
509        $this->setPermissions('');
510        $this->setRoot(null);
511        $this->setMemberType(null);
512        $this->setMemberLastPaid(null);
513        $this->setWantsPostalMail(false);
514        $this->setPostalTitle(null);
515        $this->setPostalName(null);
516        $this->setJoinedOn(null);
517        $this->setJoinedReason(null);
518        $this->setLeftOn(null);
519        $this->setLeftReason(null);
520        $this->setSolvNumber(null);
521        $this->setSiCardNumber(null);
522        $this->setNotes('');
523        $this->roles->clear();
524    }
525
526    // ---
527
528    public static function getIdFieldNameForSearch(): string {
529        return 'id';
530    }
531
532    public function getIdForSearch(): int {
533        return $this->getId() ?? 0;
534    }
535
536    public static function getCriteriaForQuery(string $query): Expression {
537        return Criteria::expr()->orX(
538            Criteria::expr()->contains('first_name', $query),
539            Criteria::expr()->contains('last_name', $query),
540            Criteria::expr()->contains('username', $query),
541            Criteria::expr()->contains('email', $query),
542        );
543    }
544
545    public function getTitleForSearch(): string {
546        return $this->getFullName();
547    }
548
549    public static function getEntityNameForStorage(): string {
550        return 'users';
551    }
552
553    public function getEntityIdForStorage(): string {
554        return "{$this->getId()}";
555    }
556}