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