BookController::getClosestMonthStart()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
namespace App\Controller;
4
5
use App\Book\EntryOverviewHelper;
6
use App\Book\Export\BookExporter;
7
use App\Book\Lesson;
8
use App\Book\Student\AbsenceExcuseResolver;
9
use App\Book\Student\StudentInfo;
10
use App\Book\Student\StudentInfoResolver;
11
use App\Entity\DateLesson;
12
use App\Entity\ExcuseNote;
13
use App\Entity\Grade;
14
use App\Entity\GradeMembership;
15
use App\Entity\GradeTeacher;
16
use App\Entity\LessonAttendance;
17
use App\Entity\LessonAttendanceExcuseStatus;
18
use App\Entity\LessonAttendanceType;
19
use App\Entity\LessonEntry;
20
use App\Entity\Section;
21
use App\Entity\Student;
22
use App\Entity\StudyGroupMembership;
23
use App\Entity\Teacher;
24
use App\Entity\TimetableLesson;
25
use App\Entity\Tuition;
26
use App\Entity\User;
27
use App\Grouping\DateWeekOfYearStrategy;
28
use App\Grouping\Grouper;
29
use App\Grouping\LessonDayStrategy;
30
use App\Grouping\TuitionGradeGroup;
31
use App\Grouping\TuitionGradeStrategy;
32
use App\Repository\ExcuseNoteRepositoryInterface;
33
use App\Repository\GradeResponsibilityRepositoryInterface;
34
use App\Repository\LessonEntryRepositoryInterface;
35
use App\Repository\StudentRepositoryInterface;
36
use App\Repository\TimetableLessonRepositoryInterface;
37
use App\Repository\TuitionRepositoryInterface;
38
use App\Security\Voter\LessonEntryVoter;
39
use App\Settings\BookSettings;
40
use App\Settings\TimetableSettings;
41
use App\Settings\TuitionGradebookSettings;
42
use App\Sorting\DateStrategy;
43
use App\Sorting\DateWeekOfYearGroupStrategy;
44
use App\Sorting\LessonDayGroupStrategy;
45
use App\Sorting\LessonStrategy;
46
use App\Sorting\SortDirection;
47
use App\Sorting\Sorter;
48
use App\Sorting\StringGroupStrategy;
49
use App\Sorting\StudentStrategy;
50
use App\Sorting\TuitionStrategy;
51
use App\Utils\ArrayUtils;
52
use App\View\Filter\GradeFilter;
53
use App\View\Filter\SectionFilter;
54
use App\View\Filter\StudentAwareGradeFilter;
55
use App\View\Filter\StudentAwareTuitionFilter;
56
use App\View\Filter\TeacherFilter;
57
use App\View\Filter\TuitionFilter;
58
use DateTime;
59
use Exception;
60
use SchulIT\CommonBundle\Helper\DateHelper;
61
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
62
use Symfony\Component\HttpFoundation\Request;
63
use Symfony\Component\HttpFoundation\Response;
64
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
65
use Symfony\Component\Routing\Annotation\Route;
66
67
#[Route(path: '/book')]
68
class BookController extends AbstractController {
69
70
    use CalendarWeeksTrait;
0 ignored issues
show
Bug introduced by
The trait App\Controller\CalendarWeeksTrait requires the property $query which is not provided by App\Controller\BookController.
Loading history...
71
72
    private const ItemsPerPage = 25;
73
74
    private const StudentsPerPage = 35;
75
76
    private function getClosestMonthStart(DateTime $dateTime): DateTime {
77
        $dateTime = clone $dateTime;
78
        $dateTime->setDate((int)$dateTime->format('Y'), (int)$dateTime->format('m'), 1);
79
        return $dateTime;
80
    }
81
82
    /**
83
     * @param DateTime $end $end - $start must not be greater than one year!
84
     * @return DateTime[] All first days of the month with their month number as key.
85
     */
86
    private function listCalendarMonths(DateTime $start, DateTime $end): array {
87
        $firstDays = [ ];
88
        $current = $this->getClosestMonthStart($start);
89
90
        while($current < $end) {
91
            $firstDays[(int)$current->format('m')] = clone $current;
92
            $current = $current->modify('+1 month');
93
        }
94
        return $firstDays;
95
    }
96
97
    private function resolveSelectedDateForTuitionView(Request $request, ?Section $currentSection, DateHelper $dateHelper): ?DateTime {
98
        $selectedDate = null;
99
        try {
100
            if($request->query->has('date')) {
101
                $selectedDate = new DateTime($request->query->get('date', null));
102
                $selectedDate->setTime(0, 0, 0);
103
104
                $selectedDate = $this->getClosestMonthStart($selectedDate);
105
            }
106
        } catch (Exception) {
107
            $selectedDate = null;
108
        }
109
110
        if($selectedDate === null && $currentSection !== null) {
111
            $selectedDate = $this->getClosestMonthStart($dateHelper->getToday());
112
        }
113
114
        if($selectedDate !== null && $currentSection !== null && $dateHelper->isBetween($selectedDate, $currentSection->getStart(), $currentSection->getEnd()) !== true) {
0 ignored issues
show
Bug introduced by
It seems like $currentSection->getEnd() can also be of type null; however, parameter $end of SchulIT\CommonBundle\Hel...DateHelper::isBetween() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

114
        if($selectedDate !== null && $currentSection !== null && $dateHelper->isBetween($selectedDate, $currentSection->getStart(), /** @scrutinizer ignore-type */ $currentSection->getEnd()) !== true) {
Loading history...
Bug introduced by
It seems like $currentSection->getStart() can also be of type null; however, parameter $start of SchulIT\CommonBundle\Hel...DateHelper::isBetween() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

114
        if($selectedDate !== null && $currentSection !== null && $dateHelper->isBetween($selectedDate, /** @scrutinizer ignore-type */ $currentSection->getStart(), $currentSection->getEnd()) !== true) {
Loading history...
115
            // Additional check if maybe parts of the month are inside the selected section (at the beginning)
116
            $start = clone $selectedDate;
117
            $end = (clone $selectedDate)->modify('+1 month')->modify('-1 day');
118
119
            // case 1: selected month is partially at the beginning of the section
120
            if($dateHelper->isBetween($start, $currentSection->getStart(), $currentSection->getEnd()) === false && $dateHelper->isBetween($end, $currentSection->getStart(), $currentSection->getEnd())) {
121
                $selectedDate = clone $currentSection->getStart();
122
            } else {
123
                $selectedDate = $this->getClosestMonthStart($currentSection->getEnd());
0 ignored issues
show
Bug introduced by
It seems like $currentSection->getEnd() can also be of type null; however, parameter $dateTime of App\Controller\BookContr...:getClosestMonthStart() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

123
                $selectedDate = $this->getClosestMonthStart(/** @scrutinizer ignore-type */ $currentSection->getEnd());
Loading history...
124
            }
125
        }
126
127
        return $selectedDate;
128
    }
129
130
    /**
131
     * @return Tuition[]
132
     */
133
    private function resolveOwnTuitions(?Section $currentSection, User $user, TuitionRepositoryInterface $tuitionRepository): array {
134
        if($currentSection === null) {
135
            return [ ];
136
        }
137
138
        if ($user->isStudentOrParent()) {
139
            return $tuitionRepository->findAllByStudents($user->getStudents()->toArray(), $currentSection);
140
        } else if ($user->isTeacher()) {
141
            return $tuitionRepository->findAllByTeacher($user->getTeacher(), $currentSection);
0 ignored issues
show
Bug introduced by
It seems like $user->getTeacher() can also be of type null; however, parameter $teacher of App\Repository\TuitionRe...ace::findAllByTeacher() does only seem to accept App\Entity\Teacher, maybe add an additional type check? ( Ignorable by Annotation )

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

141
            return $tuitionRepository->findAllByTeacher(/** @scrutinizer ignore-type */ $user->getTeacher(), $currentSection);
Loading history...
142
        }
143
144
        return [ ];
145
    }
146
147
    /**
148
     * @return Grade[]
149
     */
150
    private function resolveOwnGrades(?Section $currentSection, User $user): array {
151
        if($currentSection === null) {
152
            return [ ];
153
        }
154
155
        if ($user->isStudentOrParent()) {
156
            return ArrayUtils::unique(
157
                $user->getStudents()->map(fn(Student $student) => $student->getGrade($currentSection))
158
            );
159
        } else if ($user->isTeacher()) {
160
            return $user->getTeacher()->getGrades()->
161
                filter(fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getSection() === $currentSection)
162
                ->map(fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getGrade())
163
                ->toArray();
164
        }
165
166
        return [ ];
167
    }
168
169
    #[Route(path: '/entry', name: 'book')]
170
    public function index(SectionFilter $sectionFilter, GradeFilter $gradeFilter, TuitionFilter $tuitionFilter, TeacherFilter $teacherFilter,
171
                          TuitionRepositoryInterface $tuitionRepository, ExcuseNoteRepositoryInterface $excuseNoteRepository, DateHelper $dateHelper, Request $request,
172
                          EntryOverviewHelper $entryOverviewHelper, AbsenceExcuseResolver $absenceExcuseResolver, BookSettings $settings,
173
                          GradeResponsibilityRepositoryInterface $responsibilityRepository, LessonEntryRepositoryInterface $lessonEntryRepository): Response {
174
        /** @var User $user */
175
        $user = $this->getUser();
176
177
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
178
        $gradeFilterView = $gradeFilter->handle($request->query->get('grade'), $sectionFilterView->getCurrentSection(), $user);
179
        $tuitionFilterView = $tuitionFilter->handle($request->query->get('tuition'), $sectionFilterView->getCurrentSection(), $user);
180
        $teacherFilterView = $teacherFilter->handle($request->query->get('teacher'), $sectionFilterView->getCurrentSection(), $user, $gradeFilterView->getCurrentGrade() === null && $tuitionFilterView->getCurrentTuition() === null);
181
182
        $selectedDate = $this->resolveSelectedDate($request, $sectionFilterView->getCurrentSection(), $dateHelper);
183
184
        if($tuitionFilterView->getCurrentTuition() !== null) {
185
            $selectedDate = $this->resolveSelectedDateForTuitionView($request, $sectionFilterView->getCurrentSection(), $dateHelper);
186
        }
187
188
        $ownGrades = $this->resolveOwnGrades($sectionFilterView->getCurrentSection(), $user);
189
        $ownTuitions = $this->resolveOwnTuitions($sectionFilterView->getCurrentSection(), $user, $tuitionRepository);
190
191
        // Lessons / Entries
192
        $overview = null;
193
        $overallOverview = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $overallOverview is dead and can be removed.
Loading history...
194
        $missingExcuseCount = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $missingExcuseCount is dead and can be removed.
Loading history...
195
        $info = [ ];
196
        $responsibilities = [ ];
197
        $entriesWithExercises = [ ];
198
199
        if($selectedDate !== null) {
200
            if ($gradeFilterView->getCurrentGrade() !== null) {
201
                $overview = $entryOverviewHelper->computeOverviewForGrade($gradeFilterView->getCurrentGrade(), $selectedDate, (clone $selectedDate)->modify('+6 days'));
202
203
                $students = $gradeFilterView->getCurrentGrade()->getMemberships()->filter(fn(GradeMembership $membership) => $membership->getSection()->getId() === $sectionFilterView->getCurrentSection()->getId())->map(fn(GradeMembership $membership) => $membership->getStudent())->toArray();
204
                $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], $sectionFilterView->getCurrentSection(), true);
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...face::findAllByGrades() does only seem to accept App\Entity\Section, maybe add an additional type check? ( Ignorable by Annotation )

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

204
                $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection(), true);
Loading history...
205
                $info = $absenceExcuseResolver->resolveBulk($students, $tuitions);
206
207
                if($sectionFilterView->getCurrentSection() !== null) {
208
                    $responsibilities = $responsibilityRepository->findAllByGrade($gradeFilterView->getCurrentGrade(), $sectionFilterView->getCurrentSection());
209
                }
210
211
                $entriesWithExercises = $lessonEntryRepository->findAllByGradeWithExercises($gradeFilterView->getCurrentGrade(), $dateHelper->getToday()->modify(sprintf('-%d days', $settings->getExercisesDays())), $dateHelper->getToday());
212
            } else if ($tuitionFilterView->getCurrentTuition() !== null) {
213
                $overview = $entryOverviewHelper->computeOverviewForTuition($tuitionFilterView->getCurrentTuition(), $selectedDate, (clone $selectedDate)->modify('+1 month')->modify('-1 day'));
214
215
                $students = $tuitionFilterView->getCurrentTuition()->getStudyGroup()->getMemberships()->map(fn(StudyGroupMembership $membership) => $membership->getStudent());
216
                $info = $absenceExcuseResolver->resolveBulk($students->toArray(), [ $tuitionFilterView->getCurrentTuition() ]);
217
218
                if($sectionFilterView->getCurrentSection() !== null && $tuitionFilterView->getCurrentTuition()->getStudyGroup()->getGrades()->count() === 1) {
219
                    $responsibilities = $responsibilityRepository->findAllByGrade($tuitionFilterView->getCurrentTuition()->getStudyGroup()->getGrades()->first(), $sectionFilterView->getCurrentSection());
220
                }
221
            } else if($teacherFilterView->getCurrentTeacher() !== null) {
222
                $overview = $entryOverviewHelper->computeOverviewForTeacher($teacherFilterView->getCurrentTeacher(), $selectedDate, (clone $selectedDate)->modify('+6 days'));
223
                $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...ace::findAllByTeacher() does only seem to accept App\Entity\Section, maybe add an additional type check? ( Ignorable by Annotation )

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

223
                $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
224
225
                // IDs of already handled students
226
                $studentIds = [ ];
227
                $students = [ ];
228
229
                foreach($tuitions as $tuition) {
230
                    /** @var StudyGroupMembership $membership */
231
                    foreach($tuition->getStudyGroup()->getMemberships() as $membership) {
232
                        $student = $membership->getStudent();
233
234
                        if(in_array($student->getId(), $studentIds)) {
235
                            continue;
236
                        }
237
238
                        $studentIds[] = $student->getId();
239
                        $students[] = $student;
240
                    }
241
                }
242
243
                $info = $absenceExcuseResolver->resolveBulk($students, $tuitions);
244
            }
245
        }
246
247
        $teacherGrades = [ ];
248
        if($teacherFilterView->getCurrentTeacher() !== null) {
249
            $teacherGrades = $teacherFilterView->getCurrentTeacher()->getGrades()
250
                ->filter(fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getSection()->getId() === $sectionFilterView->getCurrentSection()->getId())
251
                ->map(fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getGrade()->getId())
252
                ->toArray();
253
        }
254
255
        $missingExcuses = array_filter($info, function(StudentInfo $info) use ($teacherFilterView, $teacherGrades, $sectionFilterView, $settings) {
256
            if($teacherFilterView->getCurrentTeacher() === null) {
257
                return $info->getNotExcusedOrNotSetLessonsCount() > 0;
258
            }
259
260
            // When filter view is active, check settings first
261
            $studentGrade = $info->getStudent()->getGrade($sectionFilterView->getCurrentSection())?->getId();
262
263
            if($studentGrade === null) {
0 ignored issues
show
introduced by
The condition $studentGrade === null is always false.
Loading history...
264
                // fallback (this should not happen)
265
                return $info->getNotExcusedOrNotSetLessonsCount() > 0;
266
            }
267
268
            $isStudentInTeacherGrade = count(array_intersect([$studentGrade], $teacherGrades)) > 0;
269
270
            if(in_array($studentGrade, $settings->getGradesGradeTeacherExcuses()) && $isStudentInTeacherGrade) {
271
                return $info->getNotExcusedOrNotSetLessonsCount() > 0;
272
            }
273
274
            if(in_array($studentGrade, $settings->getGradesTuitionTeacherExcuses()) && $isStudentInTeacherGrade !== true) {
275
                return $info->getNotExcusedOrNotSetLessonsCount() > 0;
276
            }
277
278
            return false;
279
        });
280
        $missingExcuseCount = array_sum(
281
            array_map(fn(StudentInfo $info) => $info->getNotExcusedOrNotSetLessonsCount(), $missingExcuses));
282
283
        $weekStarts = [ ];
284
        $monthStarts = [ ];
285
286
        if($sectionFilterView->getCurrentSection() !== null) {
287
            $weekStarts = $this->listCalendarWeeks($sectionFilterView->getCurrentSection()->getStart(), $sectionFilterView->getCurrentSection()->getEnd());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getC...ntSection()->getStart() can also be of type null; however, parameter $start of App\Controller\BookController::listCalendarWeeks() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

287
            $weekStarts = $this->listCalendarWeeks(/** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection()->getStart(), $sectionFilterView->getCurrentSection()->getEnd());
Loading history...
Bug introduced by
It seems like $sectionFilterView->getCurrentSection()->getEnd() can also be of type null; however, parameter $end of App\Controller\BookController::listCalendarWeeks() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

287
            $weekStarts = $this->listCalendarWeeks($sectionFilterView->getCurrentSection()->getStart(), /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection()->getEnd());
Loading history...
288
            $monthStarts = $this->listCalendarMonths($sectionFilterView->getCurrentSection()->getStart(), $sectionFilterView->getCurrentSection()->getEnd());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection()->getEnd() can also be of type null; however, parameter $end of App\Controller\BookContr...r::listCalendarMonths() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

288
            $monthStarts = $this->listCalendarMonths($sectionFilterView->getCurrentSection()->getStart(), /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection()->getEnd());
Loading history...
Bug introduced by
It seems like $sectionFilterView->getC...ntSection()->getStart() can also be of type null; however, parameter $start of App\Controller\BookContr...r::listCalendarMonths() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

288
            $monthStarts = $this->listCalendarMonths(/** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection()->getStart(), $sectionFilterView->getCurrentSection()->getEnd());
Loading history...
289
        }
290
291
        $lateStudentsByLesson = [ ];
292
        $absentStudentsByLesson = [ ];
293
294
        if($overview !== null) {
295
            foreach ($overview->getDays() as $day) {
296
                $excusesByStudent = [];
297
298
                foreach ($day->getLessons() as $lesson) {
299
                    if ($lesson->getEntry() === null) {
300
                        continue;
301
                    }
302
303
                    $uuid = $lesson->getEntry()->getUuid()->toString();
304
                    $lateStudentsByLesson[$uuid] = [];
305
                    $absentStudentsByLesson[$uuid] = [];
306
307
                    if ($lesson->getAbsentCount() === 0 && $lesson->getLateCount() === 0) {
308
                        continue;
309
                    }
310
311
                    /** @var LessonAttendance $attendance */
312
                    foreach ($lesson->getEntry()->getAttendances() as $attendance) {
313
                        if ($attendance->getType() === LessonAttendanceType::Late) {
314
                            $lateStudentsByLesson[$uuid][] = $attendance;
315
                        } else {
316
                            if ($attendance->getType() === LessonAttendanceType::Absent) {
317
                                $studentUuid = $attendance->getStudent()->getUuid()->toString();
318
319
                                if (!isset($excusesByStudent[$studentUuid])) {
320
                                    $excusesByStudent[$studentUuid] = $excuseNoteRepository->findByStudentsAndDate([$attendance->getStudent()], $day->getDate());
321
                                }
322
323
                                /** @var ExcuseNote $excuseNote */
324
                                foreach ($excusesByStudent[$studentUuid] as $excuseNote) {
325
                                    if ($attendance->getExcuseStatus() === LessonAttendanceExcuseStatus::NotSet && (new DateLesson())->setDate($day->getDate())->setLesson($lesson->getLessonNumber())->isBetween($excuseNote->getFrom(), $excuseNote->getUntil())) {
326
                                        $attendance->setExcuseStatus(LessonAttendanceExcuseStatus::Excused);
327
                                    }
328
                                }
329
330
                                $absentStudentsByLesson[$uuid][] = $attendance;
331
                            }
332
                        }
333
                    }
334
                }
335
            }
336
        }
337
338
        return $this->render('books/index.html.twig', [
339
            'sectionFilter' => $sectionFilterView,
340
            'gradeFilter' => $gradeFilterView,
341
            'tuitionFilter' => $tuitionFilterView,
342
            'teacherFilter' => $teacherFilterView,
343
            'ownGrades' => $ownGrades,
344
            'ownTuitions' => $ownTuitions,
345
            'selectedDate' => $selectedDate,
346
            'overview' => $overview,
347
            'weekStarts' => $weekStarts,
348
            'monthStarts' => $monthStarts,
349
            'missingExcuses' => $missingExcuses,
350
            'missingExcusesCount' => $missingExcuseCount,
351
            'absentStudentsByLesson' => $absentStudentsByLesson,
352
            'lateStudentsByLesson' => $lateStudentsByLesson,
353
            'responsibilities' => $responsibilities,
354
            'entriesWithExercises' => $entriesWithExercises,
355
            'exercisesDays' => $settings->getExercisesDays()
356
        ]);
357
    }
358
359
    #[Route(path: '/missing', name: 'missing_book_entries')]
360
    public function missing(Request $request, SectionFilter $sectionFilter, GradeFilter $gradeFilter, TeacherFilter $teacherFilter,
361
                            TuitionFilter $tuitionFilter, TimetableLessonRepositoryInterface $lessonRepository, TuitionRepositoryInterface $tuitionRepository,
362
                            DateHelper $dateHelper, Sorter $sorter, Grouper $grouper): Response {
363
        $this->denyAccessUnlessGranted(LessonEntryVoter::New);
364
365
        /** @var User $user */
366
        $user = $this->getUser();
367
368
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
369
        $gradeFilterView = $gradeFilter->handle($request->query->get('grade'), $sectionFilterView->getCurrentSection(), $user);
370
        $tuitionFilterView = $tuitionFilter->handle($request->query->get('tuition'), $sectionFilterView->getCurrentSection(), $user);
371
        $teacherFilterView = $teacherFilter->handle($request->query->get('teacher'), $sectionFilterView->getCurrentSection(), $user, $gradeFilterView->getCurrentGrade() === null && $tuitionFilterView->getCurrentTuition() === null);
372
373
        $ownGrades = $this->resolveOwnGrades($sectionFilterView->getCurrentSection(), $user);
374
        $ownTuitions = $this->resolveOwnTuitions($sectionFilterView->getCurrentSection(), $user, $tuitionRepository);
375
376
        $section = $sectionFilterView->getCurrentSection();
377
        $page = $request->query->getInt('page', 1);
378
        $paginator = null;
379
380
        if($section !== null) {
381
            $start = $section->getStart();
382
            $end = $dateHelper->getToday();
383
384
            if($gradeFilterView->getCurrentGrade() !== null) {
385
                $paginator = $lessonRepository->getMissingByGradePaginator(self::ItemsPerPage, $page, $gradeFilterView->getCurrentGrade(), $start, $end);
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type null; however, parameter $start of App\Repository\Timetable...ssingByGradePaginator() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

385
                $paginator = $lessonRepository->getMissingByGradePaginator(self::ItemsPerPage, $page, $gradeFilterView->getCurrentGrade(), /** @scrutinizer ignore-type */ $start, $end);
Loading history...
386
            } elseif($tuitionFilterView->getCurrentTuition() !== null) {
387
                $paginator = $lessonRepository->getMissingByTuitionPaginator(self::ItemsPerPage, $page, $tuitionFilterView->getCurrentTuition(), $start, $end);
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type null; however, parameter $start of App\Repository\Timetable...ingByTuitionPaginator() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

387
                $paginator = $lessonRepository->getMissingByTuitionPaginator(self::ItemsPerPage, $page, $tuitionFilterView->getCurrentTuition(), /** @scrutinizer ignore-type */ $start, $end);
Loading history...
388
            } else if($teacherFilterView->getCurrentTeacher() !== null) {
389
                $paginator = $lessonRepository->getMissingByTeacherPaginator(self::ItemsPerPage, $page, $teacherFilterView->getCurrentTeacher(), $start, $end);
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type null; however, parameter $start of App\Repository\Timetable...ingByTeacherPaginator() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

389
                $paginator = $lessonRepository->getMissingByTeacherPaginator(self::ItemsPerPage, $page, $teacherFilterView->getCurrentTeacher(), /** @scrutinizer ignore-type */ $start, $end);
Loading history...
390
            }
391
        }
392
393
        $missing = [ ];
394
        $pages = 0;
395
396
        if($paginator !== null) {
397
            $missing = [ ];
398
            $pages = ceil((float)$paginator->count() / self::ItemsPerPage);
399
400
            /** @var TimetableLesson $lessonEntity */
401
            foreach($paginator->getIterator() as $lessonEntity) {
402
                for($lessonNumber = $lessonEntity->getLessonStart(); $lessonNumber <= $lessonEntity->getLessonEnd(); $lessonNumber++) {
403
                    $missing[] = new Lesson(clone $lessonEntity->getDate(), $lessonNumber, $lessonEntity, null);
404
                }
405
            }
406
        }
407
408
        $groups = $grouper->group($missing, LessonDayStrategy::class);
409
        $sorter->sort($groups, LessonDayGroupStrategy::class, SortDirection::Descending);
410
        $sorter->sortGroupItems($groups, LessonStrategy::class);
411
412
        $ownGradesMissingCounts = [];
413
        $ownTuitionsMissingCounts = [];
414
415
        if($section !== null) {
416
            foreach ($ownGrades as $ownGrade) {
417
                $ownGradesMissingCounts[$ownGrade->getId()] = $lessonRepository->countMissingByGrade($ownGrade, $start, $end);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $end does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $start does not seem to be defined for all execution paths leading up to this point.
Loading history...
418
            }
419
420
            foreach ($ownTuitions as $ownTuition) {
421
                $ownTuitionsMissingCounts[$ownTuition->getId()] = $lessonRepository->countMissingByTuition($ownTuition, $start, $end);
422
            }
423
        }
424
425
        return $this->render('books/missing.html.twig', [
426
            'sectionFilter' => $sectionFilterView,
427
            'gradeFilter' => $gradeFilterView,
428
            'tuitionFilter' => $tuitionFilterView,
429
            'teacherFilter' => $teacherFilterView,
430
            'ownGrades' => $ownGrades,
431
            'ownTuitions' => $ownTuitions,
432
            'ownGradesMissingCounts' => $ownGradesMissingCounts,
433
            'ownTuitionsMissingCounts' => $ownTuitionsMissingCounts,
434
            'groups' => $groups,
435
            'page' => $page,
436
            'pages' => $pages
437
        ]);
438
    }
439
440
    #[Route(path: '/student', name: 'book_students')]
441
    public function students(SectionFilter $sectionFilter, GradeFilter $gradeFilter, TuitionFilter $tuitionFilter, TeacherFilter $teacherFilter,
442
                             TuitionRepositoryInterface $tuitionRepository, StudentRepositoryInterface $studentRepository, StudentInfoResolver $studentInfoResolver,
443
                             Sorter $sorter, Request $request): Response {
444
        /** @var User $user */
445
        $user = $this->getUser();
446
447
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
448
        $tuitionFilterView = $tuitionFilter->handle($request->query->get('tuition'), $sectionFilterView->getCurrentSection(), $user);
449
        $gradeFilterView = $gradeFilter->handle($request->query->get('grade'), $sectionFilterView->getCurrentSection(), $user);
450
        $teacherFilterView = $teacherFilter->handle($request->query->get('teacher'), $sectionFilterView->getCurrentSection(), $user, $gradeFilterView->getCurrentGrade() === null && $tuitionFilterView->getCurrentTuition() === null);
451
452
        $ownGrades = $this->resolveOwnGrades($sectionFilterView->getCurrentSection(), $user);
453
        $ownTuitions = $this->resolveOwnTuitions($sectionFilterView->getCurrentSection(), $user, $tuitionRepository);
454
455
        $page = $request->query->getInt('page', 1);
456
        $paginator = [ ];
457
        $tuitions = [ ];
458
        if($gradeFilterView->getCurrentGrade() !== null && $sectionFilterView->getCurrentSection() !== null) {
459
            $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], $sectionFilterView->getCurrentSection());
460
            $paginator = $studentRepository->getStudentsByGradePaginator(self::StudentsPerPage, $page, $gradeFilterView->getCurrentGrade(), $sectionFilterView->getCurrentSection());
461
        } else if($tuitionFilterView->getCurrentTuition() !== null) {
462
            $tuitions = [ $tuitionFilterView->getCurrentTuition() ];
463
            $paginator = $studentRepository->getStudentsByStudyGroupsPaginator(self::StudentsPerPage, $page, [$tuitionFilterView->getCurrentTuition()->getStudyGroup()]);
464
        } else if($teacherFilterView->getCurrentTeacher() !== null) {
465
            $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...ace::findAllByTeacher() does only seem to accept App\Entity\Section, maybe add an additional type check? ( Ignorable by Annotation )

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

465
            $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
466
            $studyGroups = array_map(fn(Tuition $tuition) => $tuition->getStudyGroup(), $tuitions);
467
468
            $paginator = $studentRepository->getStudentsByStudyGroupsPaginator(self::StudentsPerPage, $page, $studyGroups);
469
        }
470
471
        $students = !is_array($paginator) ? iterator_to_array($paginator->getIterator()) : $paginator;
472
        $pages = ceil((float)count($paginator) / self::StudentsPerPage);
473
474
        $sorter->sort($students, StudentStrategy::class);
475
        $info = [ ];
476
477
        foreach($students as $student) {
478
            $info[] = $studentInfoResolver->resolveStudentInfo($student, $sectionFilterView->getCurrentSection(), $tuitions);
479
        }
480
481
        return $this->render('books/students.html.twig', [
482
            'sectionFilter' => $sectionFilterView,
483
            'gradeFilter' => $gradeFilterView,
484
            'tuitionFilter' => $tuitionFilterView,
485
            'teacherFilter' => $teacherFilterView,
486
            'ownGrades' => $ownGrades,
487
            'ownTuitions' => $ownTuitions,
488
            'info' => $info,
489
            'page' => $page,
490
            'pages' => $pages
491
        ]);
492
    }
493
494
    #[Route(path: '/student/{student}', name: 'book_student')]
495
    #[ParamConverter('student', class: Student::class, options: ['mapping' => ['student' => 'uuid']])]
496
    public function student(Student $student, SectionFilter $sectionFilter, StudentAwareTuitionFilter $tuitionFilter,
497
                            StudentAwareGradeFilter $gradeFilter, TeacherFilter  $teacherFilter, Request $request,
498
                            StudentInfoResolver $infoResolver, TuitionRepositoryInterface $tuitionRepository,
499
                            Sorter $sorter, Grouper $grouper, DateHelper $dateHelper, TimetableSettings $timetableSettings,
500
                            LessonEntryRepositoryInterface $entryRepository,
501
                            TimetableLessonRepositoryInterface $timetableLessonRepository): Response {
0 ignored issues
show
Unused Code introduced by
The parameter $timetableLessonRepository is not used and could be removed. ( Ignorable by Annotation )

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

501
                            /** @scrutinizer ignore-unused */ TimetableLessonRepositoryInterface $timetableLessonRepository): Response {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
502
        /** @var User $user */
503
        $user = $this->getUser();
504
505
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
506
        $tuitionFilterView = $tuitionFilter->handle($request->query->get('tuition'), $sectionFilterView->getCurrentSection(), $student);
507
        $gradeFilterView = $gradeFilter->handle($request->query->get('grade'), $sectionFilterView->getCurrentSection(), $student);
508
        $teacherFilterView = $teacherFilter->handle($request->query->get('teacher'), $sectionFilterView->getCurrentSection(), $user, $gradeFilterView->getCurrentGrade() === null && $tuitionFilterView->getCurrentTuition() === null);
509
510
        $tuitions = [ ];
511
512
        if($tuitionFilterView->getCurrentTuition() !== null) {
513
            $tuitions[] = $tuitionFilterView->getCurrentTuition();
514
        } else if($teacherFilterView->getCurrentTeacher() !== null) {
515
            $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...ace::findAllByTeacher() does only seem to accept App\Entity\Section, maybe add an additional type check? ( Ignorable by Annotation )

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

515
            $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
516
        } else if($gradeFilterView->getCurrentGrade() !== null) {
517
            $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...face::findAllByGrades() does only seem to accept App\Entity\Section, maybe add an additional type check? ( Ignorable by Annotation )

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

517
            $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
518
        }
519
520
        // Filter tuitions which the student is a part of
521
        $tuitions = array_filter($tuitions, function(Tuition $tuition) use ($student) {
522
            foreach($tuition->getStudyGroup()->getMemberships() as $membership) {
523
                if($membership->getStudent()->getId() === $student->getId()) {
524
                    return true;
525
                }
526
            }
527
            return false;
528
        });
529
530
        $min = $sectionFilterView->getCurrentSection()->getStart();
531
        $max = min(
532
            $dateHelper->getToday(),
533
            $sectionFilterView->getCurrentSection()->getEnd()
534
        );
535
536
        $entries = [ ];
537
        foreach($tuitions as $tuition) {
538
            $entries = array_merge($entries, $entryRepository->findAllByTuition($tuition, $min, $max));
0 ignored issues
show
Bug introduced by
It seems like $min can also be of type null; however, parameter $start of App\Repository\LessonEnt...ace::findAllByTuition() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

538
            $entries = array_merge($entries, $entryRepository->findAllByTuition($tuition, /** @scrutinizer ignore-type */ $min, $max));
Loading history...
539
        }
540
541
        $entries = ArrayUtils::createArrayWithKeys(
542
            $entries,
543
            function(LessonEntry $entry) {
544
                $keys = [ ];
545
                for($lessonNumber = $entry->getLessonStart(); $lessonNumber <= $entry->getLessonEnd(); $lessonNumber++) {
546
                    $keys[] = sprintf('%s_%d', $entry->getLesson()->getDate()->format('Ymd'), $lessonNumber);
547
                }
548
                return $keys;
549
            }
550
        );
551
552
        /**
553
         * @var string $key
554
         * @var LessonEntry $entry
555
         */
556
        foreach($entries as $key => $entry) {
557
            $lesson = null;
558
559
            if($entry->getLesson() !== null) {
560
                $lesson = [
561
                    'uuid' => $entry->getLesson()->getUuid()->toString(),
562
                    'date' => $entry->getLesson()->getDate()->format('c'),
563
                    'start' => $entry->getLesson()->getLessonStart(),
564
                    'end' => $entry->getLesson()->getLessonEnd(),
565
                    'teachers' => $entry->getLesson()->getTeachers()->map(fn(Teacher $teacher) => $teacher->getAcronym())->toArray(),
566
                    'subject' => $entry->getLesson()->getSubjectName()
567
                ];
568
            }
569
570
            $entries[$key] = [
571
                'uuid' => $entry->getUuid()->toString(),
572
                'lesson' => $lesson,
573
                'start' => $entry->getLessonStart(),
574
                'end' => $entry->getLessonEnd(),
575
                'is_cancelled' => $entry->isCancelled()
576
            ];
577
578
            if($entry->isCancelled()) {
579
                $entries[$key]['cancel_reason'] = $entry->getCancelReason();
580
            }
581
        }
582
583
        $info = $infoResolver->resolveStudentInfo($student, $sectionFilterView->getCurrentSection(), $tuitions);
584
585
        $days = $this->getListOfDays($min, $max, $timetableSettings->getDays());
0 ignored issues
show
Bug introduced by
It seems like $min can also be of type null; however, parameter $min of App\Controller\BookController::getListOfDays() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

585
        $days = $this->getListOfDays(/** @scrutinizer ignore-type */ $min, $max, $timetableSettings->getDays());
Loading history...
586
        $groups = $grouper->group($days, DateWeekOfYearStrategy::class);
587
588
        $sorter->sort($groups, DateWeekOfYearGroupStrategy::class, SortDirection::Descending);
589
        $sorter->sortGroupItems($groups, DateStrategy::class, SortDirection::Descending);
590
591
        $comments = [ ];
592
593
        foreach($info->getComments() as $comment) {
594
            $comments[] = [
595
                'date' => $comment->getDate()->format('c'),
596
                'teacher' => $comment->getTeacher()->getAcronym(),
597
                'comment' => $comment->getText()
598
            ];
599
        }
600
601
        return $this->render('books/student.html.twig', [
602
            'student' => $student,
603
            'info' => $info,
604
            'groups' => $groups,
605
            'sectionFilter' => $sectionFilterView,
606
            'gradeFilter' => $gradeFilterView,
607
            'tuitionFilter' => $tuitionFilterView,
608
            'teacherFilter' => $teacherFilterView,
609
            'numberOfLessons' => $timetableSettings->getMaxLessons(),
610
            'entries' => $entries,
611
            'comments' => $comments
612
        ]);
613
    }
614
615
    /**
616
     * @param DateTime $min
617
     * @param DateTime $max
618
     * @param int[] $days
619
     * @return DateTime[]
620
     */
621
    private function getListOfDays(DateTime $min, DateTime $max, array $days): array {
622
        $result = [ ];
623
        $current = clone $min;
624
        while($current <= $max) {
625
            if(in_array((int)$current->format('w'), $days)) {
626
                $result[] = clone $current;
627
            }
628
            $current = $current->modify('+1 day');
629
        }
630
631
        return $result;
632
    }
633
634
    private function createResponse(string $content, string $contentType, string $filename): Response {
635
        $response = new Response($content);
636
        $response->headers->set('Content-Type', $contentType . '; charset=UTF-8');
637
        $response->headers->set('Content-Disposition', $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, transliterator_transliterate('Latin-ASCII', $filename)));
638
639
        return $response;
640
    }
641
642
    #[Route(path: '/export', name: 'book_export')]
643
    public function export(SectionFilter $sectionFilter, GradeFilter $gradeFilter, TeacherFilter $teacherFilter,
644
                           TuitionRepositoryInterface $tuitionRepository, TimetableLessonRepositoryInterface $lessonRepository,
645
                           Request $request, Grouper $grouper, Sorter $sorter, DateHelper $dateHelper, TuitionGradebookSettings $gradebookSettings) {
646
        /** @var User $user */
647
        $user = $this->getUser();
648
649
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
650
        $gradeFilterView = $gradeFilter->handle($request->query->get('grade'), $sectionFilterView->getCurrentSection(), $user);
651
        $teacherFilterView = $teacherFilter->handle($request->query->get('teacher'), $sectionFilterView->getCurrentSection(), $user, $request->query->get('teacher') !== '✗' && $gradeFilterView->getCurrentGrade() === null);
652
653
        if($gradeFilterView->getCurrentGrade() !== null) {
654
            $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...face::findAllByGrades() does only seem to accept App\Entity\Section, maybe add an additional type check? ( Ignorable by Annotation )

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

654
            $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
655
        } else if($teacherFilterView->getCurrentTeacher() !== null) {
656
            $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...ace::findAllByTeacher() does only seem to accept App\Entity\Section, maybe add an additional type check? ( Ignorable by Annotation )

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

656
            $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
657
        } else {
658
            $tuitions = []; // $tuitionRepository->findAllBySection($sectionFilterView->getCurrentSection());
659
        }
660
661
        $holtCounts = [ ];
662
        $missingCounts = [ ];
663
664
        foreach($tuitions as $tuition) {
665
            $holtCounts[$tuition->getId()] = $lessonRepository->countHoldLessons([$tuition], null);
666
            $missingCounts[$tuition->getId()] = $lessonRepository->countMissingByTuition(
667
                $tuition,
668
                $sectionFilterView->getCurrentSection()->getStart(),
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getC...ntSection()->getStart() can also be of type null; however, parameter $start of App\Repository\Timetable...countMissingByTuition() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

668
                /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection()->getStart(),
Loading history...
669
                min($dateHelper->getToday(), $sectionFilterView->getCurrentSection()->getEnd())
670
            );
671
        }
672
673
        $groups = $grouper->group($tuitions, TuitionGradeStrategy::class);
674
        $sorter->sort($groups, StringGroupStrategy::class);
675
        $sorter->sortGroupItems($groups, TuitionStrategy::class);
676
677
        if($gradeFilterView->getCurrentGrade() !== null) {
678
            $groups = array_filter($groups, fn(TuitionGradeGroup $group) => $group->getGrade()->getId() === $gradeFilterView->getCurrentGrade()->getId());
679
        }
680
681
        return $this->render('books/export.html.twig', [
682
            'groups' => $groups,
683
            'sectionFilter' => $sectionFilterView,
684
            'gradeFilter' => $gradeFilterView,
685
            'teacherFilter' => $teacherFilterView,
686
            'holdCounts' => $holtCounts,
687
            'missingCounts' => $missingCounts,
688
            'key' => $gradebookSettings->getEncryptedMasterKey(),
689
            'ttl' => $gradebookSettings->getTtlForSessionStorage()
690
        ]);
691
    }
692
693
    private function computeFileName(Tuition $tuition, Section $section, string $extension): string {
694
        $grades = $tuition->getStudyGroup()->getGrades()->map(fn(Grade $grade) => $grade->getName())->toArray();
695
        usort($grades, 'strnatcasecmp');
696
697
        return sprintf(
698
            '%d-%d-%s-%s.%s',
699
            $section->getYear(),
700
            $section->getNumber(),
701
            implode('-', $grades),
702
            $tuition->getName(),
703
            $extension
704
        );
705
    }
706
707
    #[Route(path: '/{section}/t/{tuition}/export/json', name: 'book_export_tuition_json')]
708
    #[ParamConverter('section', class: Section::class, options: ['mapping' => ['section' => 'uuid']])]
709
    #[ParamConverter('tuition', class: Tuition::class, options: ['mapping' => ['tuition' => 'uuid']])]
710
    public function exportTutionJson(Tuition $tuition, Section $section, BookExporter $exporter): Response {
711
        $filename = $this->computeFileName($tuition, $section, 'json');
712
        $json = $exporter->exportTuitionJson($tuition, $section);
713
        return $this->createResponse($json, 'application/json', $filename);
714
    }
715
716
    #[Route(path: '/{section}/t/{tuition}/export/xml', name: 'book_export_tuition_xml')]
717
    #[ParamConverter('section', class: Section::class, options: ['mapping' => ['section' => 'uuid']])]
718
    #[ParamConverter('tuition', class: Tuition::class, options: ['mapping' => ['tuition' => 'uuid']])]
719
    public function exportTuitionXml(Tuition $tuition, Section $section, BookExporter $exporter): Response {
720
        $filename = $this->computeFileName($tuition, $section, 'xml');
721
        $xml = $exporter->exportTuitionXml($tuition, $section);
722
        return $this->createResponse($xml, 'application/xml', $filename);
723
    }
724
725
    #[Route(path: '/{section}/g/{grade}/export/json', name: 'book_export_grade_json')]
726
    #[ParamConverter('section', class: Section::class, options: ['mapping' => ['section' => 'uuid']])]
727
    #[ParamConverter('grade', class: Grade::class, options: ['mapping' => ['grade' => 'uuid']])]
728
    public function exportGradeJson(Grade $grade, Section $section, BookExporter $exporter): Response {
729
        $filename = sprintf('%s-%d-%d.json', $grade->getName(), $section->getYear(), $section->getNumber());
730
        $json = $exporter->exportGradeJson($grade, $section);
731
        return $this->createResponse($json, 'application/json', $filename);
732
    }
733
734
    #[Route(path: '/{section}/g/{grade}/export/xml', name: 'book_export_grade_xml')]
735
    #[ParamConverter('section', class: Section::class, options: ['mapping' => ['section' => 'uuid']])]
736
    #[ParamConverter('grade', class: Grade::class, options: ['mapping' => ['grade' => 'uuid']])]
737
    public function exportGradeXml(Grade $grade, Section $section, BookExporter $exporter): Response {
738
        $filename = sprintf('%s-%d-%d.xml', $grade->getName(), $section->getYear(), $section->getNumber());
739
        $xml = $exporter->exportGradeXml($grade, $section);
740
        return $this->createResponse($xml, 'application/xml', $filename);
741
    }
742
}