BookController::export()   B
last analyzed

Complexity

Conditions 6
Paths 12

Size

Total Lines 48
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 33
nc 12
nop 10
dl 0
loc 48
rs 8.7697
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
}