Student::enroll()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 40
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 17
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 40
rs 9.7
1
<?php
2
3
namespace App\Models;
4
5
use App\Events\LeadStatusUpdatedEvent;
6
use App\Events\StudentDeleting;
7
use App\Events\StudentUpdated;
8
use Backpack\CRUD\app\Models\Traits\CrudTrait;
9
use Carbon\Carbon;
10
use Illuminate\Database\Eloquent\Builder;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Support\Facades\App;
13
use Illuminate\Support\Str;
14
use Spatie\Activitylog\Traits\LogsActivity;
15
use Spatie\Image\Manipulations;
16
use Spatie\MediaLibrary\HasMedia;
17
use Spatie\MediaLibrary\InteractsWithMedia;
18
use Spatie\MediaLibrary\MediaCollections\Models\Media;
19
20
/**
21
 * @mixin IdeHelperStudent
22
 */
23
class Student extends Model implements HasMedia
24
{
25
    use CrudTrait;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Models\Traits\CrudTrait requires some properties which are not provided by App\Models\Student: $fakeColumns, $identifiableAttribute, $Type
Loading history...
26
    use InteractsWithMedia;
0 ignored issues
show
introduced by
The trait Spatie\MediaLibrary\InteractsWithMedia requires some properties which are not provided by App\Models\Student: $fallbackPath, $mediaConversionRegistrations, $forceDeleting, $fallbackUrl, $media, $collection_name
Loading history...
27
    use LogsActivity;
28
29
    protected $dispatchesEvents = [
30
        'deleting' => StudentDeleting::class,
31
        'updated' => StudentUpdated::class,
32
    ];
33
34
    public $timestamps = true;
35
36
    protected $guarded = [];
37
38
    public $incrementing = false;
39
40
    protected $with = ['user', 'phone', 'institution', 'profession'];
41
42
    protected $appends = ['email', 'name', 'firstname', 'lastname', 'student_age', 'student_birthdate', 'is_enrolled'];
43
44
    protected static bool $logUnguarded = true;
45
46
    public function scopeEnrolled($query)
47
    {
48
        return $query->whereHas('enrollments', function ($q) {
49
            return $q->whereHas('course', function ($q) {
50
                return $q->where('period_id', Period::get_default_period()->id);
51
            });
52
        });
53
    }
54
55
    public function scopeNewInPeriod(Builder $query, int $periodId)
56
    {
57
        return $query->whereIn('id', Period::find($periodId)->newStudents()->pluck(['student_id'])->toArray());
0 ignored issues
show
Bug introduced by
array('student_id') of type array<integer,string> is incompatible with the type string expected by parameter $column of Illuminate\Database\Query\Builder::pluck(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

57
        return $query->whereIn('id', Period::find($periodId)->newStudents()->pluck(/** @scrutinizer ignore-type */ ['student_id'])->toArray());
Loading history...
Bug introduced by
array('student_id') of type array<integer,string> is incompatible with the type Illuminate\Database\Query\Expression|string expected by parameter $column of Illuminate\Database\Eloquent\Builder::pluck(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

57
        return $query->whereIn('id', Period::find($periodId)->newStudents()->pluck(/** @scrutinizer ignore-type */ ['student_id'])->toArray());
Loading history...
58
    }
59
60
    public function registerMediaConversions(Media $media = null): void
61
    {
62
        $this->addMediaConversion('thumb')
63
            ->fit(Manipulations::FIT_MAX, 1200, 1200)
64
            ->optimize();
65
    }
66
67
    /** relations */
68
    public function user()
69
    {
70
        return $this->belongsTo(User::class, 'id', 'id');
71
    }
72
73
    public function attendance()
74
    {
75
        return $this->hasMany(Attendance::class);
76
    }
77
78
    public function periodAbsences(Period $period = null)
79
    {
80
        if ($period == null) {
81
            $period = Period::get_default_period();
82
        }
83
84
        return $this->hasMany(Attendance::class)
85
        ->where('attendance_type_id', 4) // absence
86
        ->whereHas('event', fn ($q) => $q->whereHas('course', fn ($c) => $c->where('period_id', $period->id)));
87
    }
88
89
    public function periodAttendance(Period $period = null)
90
    {
91
        if ($period == null) {
92
            $period = Period::get_default_period();
93
        }
94
95
        return $this->hasMany(Attendance::class)
96
        ->whereHas('event', fn ($q) => $q->whereHas('course', fn ($c) => $c->where('period_id', $period->id)));
97
    }
98
99
    public function contacts()
100
    {
101
        return $this->hasMany(Contact::class, 'student_id');
102
    }
103
104
    public function comments()
105
    {
106
        return $this->morphMany(Comment::class, 'commentable');
107
    }
108
109
    public function phone()
110
    {
111
        return $this->morphMany(PhoneNumber::class, 'phoneable');
112
    }
113
114
    public function enrollments()
115
    {
116
        return $this->hasMany(Enrollment::class)
117
            ->with('course')->orderByDesc('course_id');
118
    }
119
120
    public function leadType()
121
    {
122
        return $this->belongsTo(LeadType::class);
123
    }
124
125
    public function institution()
126
    {
127
        return $this->belongsTo(Institution::class);
128
    }
129
130
    public function profession()
131
    {
132
        return $this->belongsTo(Profession::class);
133
    }
134
135
    public function books()
136
    {
137
        return $this->belongsToMany(Book::class)->withPivot('id', 'code', 'status_id', 'expiry_date');
138
    }
139
140
    /** attributes */
141
    public function getFirstnameAttribute(): string
142
    {
143
        return $this->user ? Str::title($this->user->firstname) : '';
144
    }
145
146
    public function getLastnameAttribute(): string
147
    {
148
        if ($this->user) {
149
            return Str::upper($this->user->lastname);
150
        }
151
152
        return '';
153
    }
154
155
    public function getEmailAttribute(): ?string
156
    {
157
        return $this?->user?->email;
158
    }
159
160
    public function getNameAttribute(): string
161
    {
162
        return $this->user ? "{$this->firstname} {$this->lastname}" : '';
163
    }
164
165
    public function getStudentAgeAttribute()
166
    {
167
        return $this->birthdate ? Carbon::parse($this->birthdate)->age.' '.__('years old') : '';
0 ignored issues
show
Bug introduced by
Are you sure __('years old') of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
        return $this->birthdate ? Carbon::parse($this->birthdate)->age.' './** @scrutinizer ignore-type */ __('years old') : '';
Loading history...
168
    }
169
170
    public function getStudentBirthdateAttribute()
171
    {
172
        return $this->birthdate ? Carbon::parse($this->birthdate)->locale(App::getLocale())->isoFormat('LL') : '';
173
    }
174
175
    public function getIsEnrolledAttribute()
176
    {
177
        // if the student is currently enrolled
178
        if ($this->enrollments()->whereHas('course', fn ($q) => $q->where('period_id', Period::get_default_period()->id))->count() > 0) {
179
            return 1;
180
        }
181
182
        return false;
183
    }
184
185
    public function getLeadStatusNameAttribute()
186
    {
187
        return LeadType::find($this->lead_type_id)->name ?? null;
188
    }
189
190
    public function computedLeadStatus()
191
    {
192
        // if the student is currently enrolled, they are CONVERTED
193
        if ($this->is_enrolled) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->is_enrolled of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
194
            return 1;
195
        }
196
197
        // if the student has a special status, return it
198
        if ($this->leadType != null) {
199
            return $this->leadType->id;
200
        }
201
202
        // if the student was previously enrolled, they must be potential students
203
        if ($this->has('enrollments')) {
204
            return 4;
205
        }
206
207
        // otherwise, their status cannot be determined and should be left blank
208
        return null;
209
    }
210
211
    /** functions */
212
213
    /**
214
     * enroll the student in a course.
215
     * If the course has any children, we also enroll the student in the children courses.
216
     */
217
    public function enroll(Course $course): int
218
    {
219
        // avoid duplicates by retrieving an potential existing enrollment for the same course
220
        $enrollment = Enrollment::firstOrCreate(
221
            [
222
                'student_id' => $this->id,
223
                'course_id' => $course->id,
224
            ],
225
            [
226
                'responsible_id' => backpack_user()->id ?? 1,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
227
            ]
228
        );
229
230
        // if the course has children, enroll in children as well.
231
        if ($course->children_count > 0) {
232
            foreach ($course->children as $children_course) {
233
                Enrollment::firstOrCreate(
234
                    [
235
                        'student_id' => $this->id,
236
                        'course_id' => $children_course->id,
237
                        'parent_id' => $enrollment->id,
238
                    ],
239
                    [
240
                        'responsible_id' => backpack_user()->id ?? 1,
241
                    ]
242
                );
243
            }
244
        }
245
246
        $this->update(['lead_type_id' => null]); // fallback to default (converted)
247
248
        // to subscribe the student to mailing lists and so on
249
        $listId = config('mailing-system.mailerlite.activeStudentsListId');
250
        LeadStatusUpdatedEvent::dispatch($this, $listId);
251
252
        foreach ($this->contacts as $contact) {
253
            LeadStatusUpdatedEvent::dispatch($contact, $listId);
254
        }
255
256
        return $enrollment->id;
257
    }
258
259
    /** SETTERS */
260
    public function setFirstnameAttribute($value)
261
    {
262
        $this->user->update(['firstname' => $value]);
263
    }
264
265
    public function setLastnameAttribute($value)
266
    {
267
        $this->user->update(['lastname' => $value]);
268
    }
269
270
    public function setEmailAttribute($value)
271
    {
272
        $this->user->update(['email' => $value]);
273
    }
274
275
    public function getImageAttribute(): ?string
276
    {
277
        return $this->getMedia('profile-picture')->last()?->getUrl('thumb');
278
    }
279
280
    public function setImageAttribute($value)
281
    {
282
        // if the image was erased
283
        if ($value == null) {
284
            $this->clearMediaCollection('profile-picture');
285
        }
286
287
        // if a base64 was sent, store it in the db
288
        if (Str::startsWith($value, 'data:image')) {
289
            $this->addMediaFromBase64($value)
290
                ->usingFileName('profilePicture.jpg')
291
                ->toMediaCollection('profile-picture');
292
        }
293
    }
294
}
295