Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
27 / 27
CRAP
100.00% covered (success)
100.00%
1 / 1
User
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
27 / 27
29
100.00% covered (success)
100.00%
1 / 1
 contact
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 groups
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isFreeloader
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 license
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 privileges
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getPrivilegesAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 personalData
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 settings
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 state
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 userAngelTypes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 isAngelTypeSupporter
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 logs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 news
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newsComments
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 oauth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 shiftEntries
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 worklogs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 worklogsCreated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sessions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 questionsAsked
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 questionsAnswered
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 messagesSent
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 messagesReceived
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 messages
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 shiftsCreated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 shiftsUpdated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayNameAttribute
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Models\User;
6
7use Carbon\Carbon;
8use Engelsystem\Models\AngelType;
9use Engelsystem\Models\BaseModel;
10use Engelsystem\Models\Group;
11use Engelsystem\Models\LogEntry;
12use Engelsystem\Models\Message;
13use Engelsystem\Models\News;
14use Engelsystem\Models\NewsComment;
15use Engelsystem\Models\OAuth;
16use Engelsystem\Models\Privilege;
17use Engelsystem\Models\Question;
18use Engelsystem\Models\Session;
19use Engelsystem\Models\Shifts\Shift;
20use Engelsystem\Models\Shifts\ShiftEntry;
21use Engelsystem\Models\UserAngelType;
22use Engelsystem\Models\Worklog;
23use Illuminate\Database\Eloquent\Builder;
24use Illuminate\Database\Eloquent\Collection;
25use Illuminate\Database\Eloquent\Factories\HasFactory;
26use Illuminate\Database\Eloquent\Relations\BelongsToMany;
27use Illuminate\Database\Eloquent\Relations\HasMany;
28use Illuminate\Database\Eloquent\Relations\HasOne;
29use Illuminate\Database\Query\Builder as QueryBuilder;
30use Illuminate\Support\Collection as SupportCollection;
31
32/**
33 * @property int                                $id
34 * @property string                             $name
35 * @property string                             $email
36 * @property string                             $password
37 * @property string                             $api_key
38 * @property Carbon|null                        $last_login_at
39 * @property Carbon|null                        $created_at
40 * @property Carbon|null                        $updated_at
41 *
42 * @property-read QueryBuilder|Contact          $contact
43 * @property-read QueryBuilder|License          $license
44 * @property-read QueryBuilder|PersonalData     $personalData
45 * @property-read QueryBuilder|Settings         $settings
46 * @property-read QueryBuilder|State            $state
47 * @property-read string                        $displayName
48 *
49 * @property-read Collection|Group[]            $groups
50 * @property-read Collection|LogEntry[]         $logs
51 * @property-read Collection|News[]             $news
52 * @property-read Collection|NewsComment[]      $newsComments
53 * @property-read Collection|OAuth[]            $oauth
54 * @property-read SupportCollection|Privilege[] $privileges
55 * @property-read Collection|AngelType[]        $userAngelTypes
56 * @property-read UserAngelType                 $pivot
57 * @property-read Collection|ShiftEntry[]       $shiftEntries
58 * @property-read Collection|Session[]          $sessions
59 * @property-read Collection|Worklog[]          $worklogs
60 * @property-read Collection|Worklog[]          $worklogsCreated
61 * @property-read Collection|Question[]         $questionsAsked
62 * @property-read Collection|Question[]         $questionsAnswered
63 * @property-read Collection|Message[]          $messagesReceived
64 * @property-read Collection|Message[]          $messagesSent
65 * @property-read Collection|Message[]          $messages
66 * @property-read Collection|Shift[]            $shiftsCreated
67 * @property-read Collection|Shift[]            $shiftsUpdated
68 *
69 * @method static QueryBuilder|User[] whereId($value)
70 * @method static QueryBuilder|User[] whereName($value)
71 * @method static QueryBuilder|User[] whereEmail($value)
72 * @method static QueryBuilder|User[] wherePassword($value)
73 * @method static QueryBuilder|User[] whereApiKey($value)
74 * @method static QueryBuilder|User[] whereLastLoginAt($value)
75 * @method static QueryBuilder|User[] whereCreatedAt($value)
76 * @method static QueryBuilder|User[] whereUpdatedAt($value)
77 */
78class User extends BaseModel
79{
80    use HasFactory;
81
82    /** @var bool enable timestamps */
83    public $timestamps = true; // phpcs:ignore
84
85    /** @var array<string, null> default attributes */
86    protected $attributes = [ // phpcs:ignore
87        'last_login_at' => null,
88    ];
89
90    /**
91     * The attributes that are mass assignable.
92     *
93     * @var array<string>
94     */
95    protected $fillable = [ // phpcs:ignore
96        'name',
97        'password',
98        'email',
99        'api_key',
100        'last_login_at',
101    ];
102
103    /** @var array<string> The attributes that should be hidden for serialization */
104    protected $hidden = [ // phpcs:ignore
105        'api_key',
106        'password',
107    ];
108
109    /** @var array<string, string> */
110    protected $casts = [ // phpcs:ignore
111        'last_login_at' => 'datetime',
112    ];
113
114    public function contact(): HasOne
115    {
116        return $this
117            ->hasOne(Contact::class)
118            ->withDefault();
119    }
120
121    public function groups(): BelongsToMany
122    {
123        return $this->belongsToMany(Group::class, 'users_groups');
124    }
125
126    public function isFreeloader(): bool
127    {
128        return $this->shiftEntries()
129                ->where('freeloaded', true)
130                ->count()
131            >= config('max_freeloadable_shifts');
132    }
133
134    public function license(): HasOne
135    {
136        return $this
137            ->hasOne(License::class)
138            ->withDefault();
139    }
140
141    public function privileges(): Builder
142    {
143        /** @var Builder $builder */
144        $builder = Privilege::query()
145            ->whereIn('id', function ($query): void {
146                /** @var QueryBuilder $query */
147                $query->select('privilege_id')
148                    ->from('group_privileges')
149                    ->join('users_groups', 'users_groups.group_id', '=', 'group_privileges.group_id')
150                    ->where('users_groups.user_id', '=', $this->id)
151                    ->distinct();
152            });
153
154        return $builder;
155    }
156
157    public function getPrivilegesAttribute(): SupportCollection
158    {
159        return $this->privileges()->get();
160    }
161
162    public function personalData(): HasOne
163    {
164        return $this
165            ->hasOne(PersonalData::class)
166            ->withDefault();
167    }
168
169    public function settings(): HasOne
170    {
171        return $this
172            ->hasOne(Settings::class)
173            ->withDefault();
174    }
175
176    public function state(): HasOne
177    {
178        return $this
179            ->hasOne(State::class)
180            ->withDefault();
181    }
182
183    public function userAngelTypes(): BelongsToMany
184    {
185        return $this
186            ->belongsToMany(AngelType::class, 'user_angel_type')
187            ->using(UserAngelType::class)
188            ->withPivot(UserAngelType::getPivotAttributes());
189    }
190
191    public function isAngelTypeSupporter(AngelType $angelType): bool
192    {
193        return $this->userAngelTypes()
194            ->wherePivot('angel_type_id', $angelType->id)
195            ->wherePivot('supporter', true)
196            ->exists();
197    }
198
199    public function logs(): HasMany
200    {
201        return $this->hasMany(LogEntry::class);
202    }
203
204    public function news(): HasMany
205    {
206        return $this->hasMany(News::class);
207    }
208
209    public function newsComments(): HasMany
210    {
211        return $this->hasMany(NewsComment::class);
212    }
213
214    public function oauth(): HasMany
215    {
216        return $this->hasMany(OAuth::class);
217    }
218
219    public function shiftEntries(): HasMany
220    {
221        return $this->hasMany(ShiftEntry::class);
222    }
223
224    public function worklogs(): HasMany
225    {
226        return $this->hasMany(Worklog::class);
227    }
228
229    public function worklogsCreated(): HasMany
230    {
231        return $this->hasMany(Worklog::class, 'creator_id');
232    }
233
234    public function sessions(): HasMany
235    {
236        return $this->hasMany(Session::class);
237    }
238
239    public function questionsAsked(): HasMany
240    {
241        return $this->hasMany(Question::class, 'user_id')
242            ->where('user_id', $this->id);
243    }
244
245    public function questionsAnswered(): HasMany
246    {
247        return $this->hasMany(Question::class, 'answerer_id')
248            ->where('answerer_id', $this->id);
249    }
250
251    public function messagesSent(): HasMany
252    {
253        return $this->hasMany(Message::class, 'user_id')
254            ->orderBy('created_at', 'DESC')
255            ->orderBy('id', 'DESC');
256    }
257
258    public function messagesReceived(): HasMany
259    {
260        return $this->hasMany(Message::class, 'receiver_id')
261            ->orderBy('read')
262            ->orderBy('created_at', 'DESC')
263            ->orderBy('id', 'DESC');
264    }
265
266    /**
267     * Returns a HasMany relation for all messages sent or received by the user.
268     */
269    public function messages(): HasMany
270    {
271        return $this->messagesSent()
272            ->union($this->messagesReceived())
273            ->orderBy('read')
274            ->orderBy('id', 'DESC');
275    }
276
277    public function shiftsCreated(): HasMany
278    {
279        return $this->hasMany(Shift::class, 'created_by');
280    }
281
282    public function shiftsUpdated(): HasMany
283    {
284        return $this->hasMany(Shift::class, 'updated_by');
285    }
286
287    public function getDisplayNameAttribute(): string
288    {
289        if (
290            config('display_full_name')
291            && !empty(trim($this->personalData->first_name . $this->personalData->last_name))
292        ) {
293            return trim(
294                trim((string) $this->personalData->first_name)
295                . ' ' .
296                trim((string) $this->personalData->last_name)
297            );
298        }
299
300        return $this->name;
301    }
302}