AppointmentController::indexXhr()   F
last analyzed

Complexity

Conditions 34
Paths 5160

Size

Total Lines 207
Code Lines 124

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 1190

Importance

Changes 0
Metric Value
eloc 124
c 0
b 0
f 0
dl 0
loc 207
rs 0
ccs 0
cts 115
cp 0
cc 34
nc 5160
nop 16
crap 1190

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Symfony\Component\HttpFoundation\Response;
6
use App\Converter\StudyGroupsGradeStringConverter;
7
use App\Converter\TeacherStringConverter;
8
use App\Converter\UserStringConverter;
9
use App\Entity\Appointment;
10
use App\Entity\AppointmentCategory;
11
use App\Entity\IcsAccessToken;
12
use App\Entity\IcsAccessTokenType;
13
use App\Entity\Exam;
14
use App\Entity\Grade;
15
use App\Entity\MessageScope;
16
use App\Entity\Teacher;
17
use App\Entity\Tuition;
18
use App\Entity\User;
19
use App\Entity\UserType;
20
use App\Export\AppointmentIcsExporter;
21
use App\Form\IcsAccessTokenType as DeviceTokenTypeForm;
22
use App\Repository\AppointmentRepositoryInterface;
23
use App\Repository\ExamRepositoryInterface;
24
use App\Repository\ImportDateTypeRepositoryInterface;
25
use App\Security\IcsAccessToken\IcsAccessTokenManager;
26
use App\Security\Voter\AppointmentVoter;
27
use App\Security\Voter\ExamVoter;
28
use App\Settings\AppointmentsSettings;
29
use App\Settings\TimetableSettings;
30
use App\Timetable\TimetableTimeHelper;
31
use App\Utils\ArrayUtils;
32
use App\Utils\ColorUtils;
33
use App\View\Filter\AppointmentCategoriesFilter;
34
use App\View\Filter\GradesFilter;
35
use App\View\Filter\SectionFilter;
36
use App\View\Filter\StudentFilter;
37
use App\View\Filter\StudyGroupFilter;
38
use App\View\Filter\TeacherFilter;
39
use DateInterval;
40
use DateTime;
41
use Symfony\Component\HttpFoundation\Request;
42
use Symfony\Component\Routing\Annotation\Route;
43
use Symfony\Contracts\Translation\TranslatorInterface;
44
45
#[Route(path: '/appointments')]
46
class AppointmentController extends AbstractControllerWithMessages {
47
48
    #[Route(path: '', name: 'appointments')]
49
    public function index(SectionFilter $sectionFilter, AppointmentCategoriesFilter $categoryFilter, StudentFilter $studentFilter, StudyGroupFilter $studyGroupFilter,
50
                          TeacherFilter $teacherFilter, GradesFilter $gradesFilter, Request $request, ImportDateTypeRepositoryInterface $importDateTypeRepository): Response {
51
        /** @var User $user */
52
        $user = $this->getUser();
53
54
        $categoryFilterView = $categoryFilter->handle([ ]);
55
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
56
        $studentFilterView = $studentFilter->handle(null, $sectionFilterView->getCurrentSection(), $user);
57
        $studyGroupView = $studyGroupFilter->handle(null, $sectionFilterView->getCurrentSection(), $user);
58
        $teacherFilterView = $teacherFilter->handle(null, $sectionFilterView->getCurrentSection(), $user, false);
59
        $gradesFilterView = $gradesFilter->handle([], $sectionFilterView->getCurrentSection(), $user);
60
61
        return $this->renderWithMessages('appointments/index.html.twig', [
62
            'sectionFilter' => $sectionFilterView,
63
            'categoryFilter' => $categoryFilterView,
64
            'studentFilter' => $studentFilterView,
65
            'studyGroupFilter' => $studyGroupView,
66
            'teacherFilter' => $teacherFilterView,
67
            'examGradesFilter' => $gradesFilterView,
68
            'last_import' => $importDateTypeRepository->findOneByEntityClass(Appointment::class)
69
        ]);
70
    }
71
72
    #[Route(path: '/xhr', name: 'appointments_xhr', methods: ['GET'])]
73
    public function indexXhr(AppointmentRepositoryInterface $appointmentRepository, ColorUtils $colorUtils, TranslatorInterface $translator,
74
                             StudyGroupsGradeStringConverter $studyGroupsGradeStringConverter, TeacherStringConverter $teacherStringConverter,
75
                             AppointmentCategoriesFilter $categoryFilter, SectionFilter $sectionFilter, StudentFilter $studentFilter, StudyGroupFilter $studyGroupFilter,
76
                             TeacherFilter $teacherFilter, GradesFilter $gradesFilter, ExamRepositoryInterface $examRepository,
77
                             AppointmentsSettings $appointmentsSettings, TimetableTimeHelper $timetableTimeHelper, UserStringConverter $userStringConverter, Request $request): Response {
78
        /** @var User $user */
79
        $user = $this->getUser();
80
        $isStudent = $user->isStudent();
81
        $isParent = $user->isParent();
82
83
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
84
        $showAll = $request->query->getBoolean('all', false);
0 ignored issues
show
Unused Code introduced by
The assignment to $showAll is dead and can be removed.
Loading history...
85
        $categoryFilterView = $categoryFilter->handle(explode(',', $request->query->get('categories', '')));
86
        $studentFilterView = $studentFilter->handle($request->query->get('student', null), $sectionFilterView->getCurrentSection(), $user);
87
        $studyGroupView = $studyGroupFilter->handle($request->query->get('study_group', null), $sectionFilterView->getCurrentSection(), $user);
88
        $teacherFilterView = $teacherFilter->handle($request->query->get('teacher', null), $sectionFilterView->getCurrentSection(), $user, $studentFilterView->getCurrentStudent() === null && $studyGroupView->getCurrentStudyGroup() === null);
89
        $examGradesFilterView = $gradesFilter->handle(explode(',', $request->query->get('exam_grades', '')), $sectionFilterView->getCurrentSection(), $user);
90
91
        $appointments = [ ];
92
        $today = null;
93
94
        if($studentFilterView->getCurrentStudent() !== null) {
95
            $appointments = $appointmentRepository->findAllForStudents([$studentFilterView->getCurrentStudent()], $today);
96
        } else if($studyGroupView->getCurrentStudyGroup() !== null) {
97
            $appointments = $appointmentRepository->findAllForStudyGroup($studyGroupView->getCurrentStudyGroup(), $today);
98
        } else if($teacherFilterView->getCurrentTeacher() !== null) {
99
            $appointments = $appointmentRepository->findAllForTeacher($teacherFilterView->getCurrentTeacher(), $today);
100
        } else {
101
            if($isStudent || $isParent) {
102
                $appointments = $appointmentRepository->findAllForStudents($user->getStudents()->toArray(), $today);
103
            } else {
104
                $appointments = $appointmentRepository->findAll([ ], null, $today);
105
            }
106
        }
107
108
        if(!empty($categoryFilterView->getCurrentCategories())) {
109
            $selectedCategoryIds = array_map(fn(AppointmentCategory $category) => $category->getId(), $categoryFilterView->getCurrentCategories());
110
111
            $appointments = array_filter($appointments, fn(Appointment $appointment) => in_array($appointment->getCategory()->getId(), $selectedCategoryIds));
112
        }
113
114
        $json = [ ];
115
116
        foreach($appointments as $appointment) {
117
            if($this->isGranted(AppointmentVoter::View, $appointment) !== true) {
118
                continue;
119
            }
120
121
            if($appointment->isAllDay() && $appointment->getDuration()->d === 1) {
0 ignored issues
show
Bug introduced by
The method isAllDay() does not exist on null. ( Ignorable by Annotation )

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

121
            if($appointment->/** @scrutinizer ignore-call */ isAllDay() && $appointment->getDuration()->d === 1) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
122
                $view = [
123
                    [
124
                        'label' => $translator->trans('label.date'),
125
                        'content' => $appointment->getStart()->format($translator->trans('date.format'))
126
                    ]
127
                ];
128
            } else {
129
                $view = [
130
                    [
131
                        'label' => $translator->trans('label.start'),
132
                        'content' => $appointment->getStart()->format($translator->trans($appointment->isAllDay() ? 'date.format' : 'date.with_time'))
133
                    ],
134
                    [
135
                        'label' => $translator->trans('label.end'),
136
                        'content' => $appointment->getRealEnd()->format($translator->trans($appointment->isAllDay() ? 'date.format' : 'date.with_time'))
137
                    ]
138
                ];
139
            }
140
141
            if(!empty($appointment->getLocation())) {
142
                $view[] = [
143
                    'label' => $translator->trans('label.location'),
144
                    'content' => $appointment->getLocation()
145
                ];
146
            }
147
148
            if($appointment->getStudyGroups()->count() > 0) {
149
                $view[] = [
150
                    'label' => $translator->trans('label.study_groups', ['%count%' => $appointment->getStudyGroups()->count()]),
151
                    'content' => $studyGroupsGradeStringConverter->convert($appointment->getStudyGroups())
152
                ];
153
            }
154
155
            if($appointment->getOrganizers()->count() > 0) {
156
                $view[] = [
157
                    'label' => $translator->trans('label.organizers'),
158
                    'content' => implode(', ', array_map(fn(Teacher $teacher) => $teacherStringConverter->convert($teacher), $appointment->getOrganizers()->toArray()))
159
                ];
160
            }
161
162
            if(!empty($appointment->getExternalOrganizers())) {
163
                $view[] = [
164
                    'label' => $translator->trans('label.external_organizers'),
165
                    'content' => $appointment->getExternalOrganizers()
166
                ];
167
            }
168
169
            if($appointment->getCreatedBy() !== null) {
170
                $view[] = [
171
                    'label' => $translator->trans('label.created_by'),
172
                    'content' => $userStringConverter->convert($appointment->getCreatedBy(), false)
173
                ];
174
            }
175
176
            $json[] = [
177
                'uuid' => $appointment->getUuid(),
178
                'allDay' => $appointment->isAllDay(),
179
                'title' => ($appointment->isConfirmed() === false ? '(✗) ' : '') . $appointment->getTitle(),
180
                'textColor' => $colorUtils->getForeground($appointment->getCategory()->getColor()),
181
                'backgroundColor' => $appointment->getCategory()->getColor(),
182
                'start' => $appointment->getStart()->format($appointment->isAllDay() ? 'Y-m-d' : 'Y-m-d H:i'),
183
                'end' => $appointment->getEnd()->format($appointment->isAllDay() ? 'Y-m-d' : 'Y-m-d H:i'),
184
                'extendedProps' => [
185
                    'category'=> $appointment->getCategory()->getName(),
186
                    'content' => $appointment->getContent(),
187
                    'view' => $view,
188
                    'confirmation_status' => $appointment->isConfirmed() === false ? $translator->trans('label.not_confirmed') : null
189
                ]
190
            ];
191
        }
192
193
        // Add exams
194
        if((is_countable($examGradesFilterView->getCurrentGrades()) ? count($examGradesFilterView->getCurrentGrades()) : 0) > 0) {
195
            $exams = [ ];
196
197
            foreach($examGradesFilterView->getCurrentGrades() as $grade) {
198
                $exams = array_merge($exams, $examRepository->findAllByGrade($grade));
199
            }
200
201
            $exams = ArrayUtils::createArrayWithKeys($exams, fn(Exam $exam) => $exam->getUuid()->toString());
202
203
            /** @var Exam $exam */
204
            foreach($exams as $exam) {
205
                if($this->isGranted(ExamVoter::Show, $exam)) {
206
                    $view = [ ];
207
208
                    $tuitions = implode(', ', $exam->getTuitions()->map(fn(Tuition $tuition) => $tuition->getName())->toArray());
0 ignored issues
show
Bug introduced by
The method getTuitions() does not exist on null. ( Ignorable by Annotation )

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

208
                    $tuitions = implode(', ', $exam->/** @scrutinizer ignore-call */ getTuitions()->map(fn(Tuition $tuition) => $tuition->getName())->toArray());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
209
210
                    $view[] = [
211
                        'label' => $translator->trans('label.tuitions'),
212
                        'content' => $tuitions
213
                    ];
214
215
                    $grades = [ ];
216
                    $teachers = [ ];
217
218
                    /** @var Tuition $tuition */
219
                    foreach($exam->getTuitions() as $tuition) {
220
                        /** @var Grade $grade */
221
                        foreach($tuition->getStudyGroup()->getGrades() as $grade) {
222
                            $grades[] = $grade->getName();
223
                        }
224
225
                        /** @var Teacher $teacher */
226
                        foreach($tuition->getTeachers() as $teacher) {
227
                            $teachers[] = $teacherStringConverter->convert($teacher);
228
                        }
229
230
                        $grades = array_unique($grades);
231
                        $teachers = array_unique($teachers);
232
                    }
233
234
                    $view[] = [
235
                        'label' => $translator->trans('label.teacher'),
236
                        'content' => $teachers
237
                    ];
238
239
                    $view[] = [
240
                        'label' => $translator->trans('label.grades'),
241
                        'content' => implode(', ', $grades)
242
                    ];
243
244
                    if($exam->getRoom() !== null) {
245
                        $view[] = [
246
                            'label' => $translator->trans('label.room'),
247
                            'content' => $exam->getRoom()->getName()
248
                        ];
249
                    }
250
251
                    $view[] = [
252
                        'label' => $translator->trans('plans.exams.time'),
253
                        'content' => $translator->trans('label.exam_lessons', [
254
                            '%start%' => $exam->getLessonStart(),
255
                            '%end%' => $exam->getLessonEnd(),
256
                            '%count%' => $exam->getLessonEnd() - $exam->getLessonStart()
257
                        ])
258
                    ];
259
260
                    $json[] = [
261
                        'uuid' => $exam->getUuid(),
262
                        'allDay' => false,
263
                        'title' => sprintf('%s: %s', implode(', ', $grades), $tuitions),
264
                        'textColor' => empty($appointmentsSettings->getExamColor()) ? '#000000' : $colorUtils->getForeground($appointmentsSettings->getExamColor()),
265
                        'backgroundColor' => empty($appointmentsSettings->getExamColor()) ? '#ffffff' : $appointmentsSettings->getExamColor(),
266
                        'start' => $timetableTimeHelper->getLessonStartDateTime($exam->getDate(), $exam->getLessonStart())->format('Y-m-d H:i'),
267
                        'end' => $timetableTimeHelper->getLessonEndDateTime($exam->getDate(), $exam->getLessonEnd())->format('Y-m-d H:i'),
268
                        'extendedProps' => [
269
                            'category' => $translator->trans('plans.exams.label'),
270
                            'content' => $exam->getDescription(),
271
                            'view' => $view
272
                        ]
273
                    ];
274
                }
275
            }
276
        }
277
278
        return $this->json($json);
279
    }
280
281
    #[Route(path: '/export', name: 'appointments_export')]
282
    public function export(Request $request, IcsAccessTokenManager $manager): Response {
283
        /** @var User $user */
284
        $user = $this->getUser();
285
286
        $deviceToken = (new IcsAccessToken())
287
            ->setType(IcsAccessTokenType::Calendar)
288
            ->setUser($user);
289
290
        $form = $this->createForm(DeviceTokenTypeForm::class, $deviceToken);
291
        $form->handleRequest($request);
292
293
        if($form->isSubmitted() && $form->isValid()) {
294
            $deviceToken = $manager->persistToken($deviceToken);
295
        }
296
297
        return $this->renderWithMessages('appointments/export.html.twig', [
298
            'form' => $form->createView(),
299
            'token' => $deviceToken
300
        ]);
301
    }
302
303
    #[Route(path: '/ics/download', name: 'appointments_ics')]
304
    #[Route(path: '/ics/download/{token}', name: 'appointments_ics_token')]
305
    public function ics(AppointmentIcsExporter $exporter): Response {
306
        /** @var User $user */
307
        $user = $this->getUser();
308
309
        return $exporter->getIcsResponse($user);
310
    }
311
312
    protected function getMessageScope(): MessageScope {
313
        return MessageScope::Appointments;
314
    }
315
}