ExamVoter::canViewExam()   C
last analyzed

Complexity

Conditions 14
Paths 10

Size

Total Lines 50
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
eloc 26
c 0
b 0
f 0
dl 0
loc 50
rs 6.2666
ccs 0
cts 25
cp 0
cc 14
nc 10
nop 2
crap 210

How to fix   Complexity   

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:

1
<?php
2
3
namespace App\Security\Voter;
4
5
use App\Entity\Exam;
6
use App\Entity\Student;
7
use App\Entity\Tuition;
8
use App\Entity\User;
9
use App\Entity\UserType;
10
use App\Section\SectionResolverInterface;
11
use App\Settings\ExamSettings;
12
use LogicException;
13
use SchulIT\CommonBundle\Helper\DateHelper;
14
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
16
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17
18
class ExamVoter extends Voter {
19
20
    public const Show = 'show';
21
    public const Supervisions = 'supervisions';
22
    public const Details = 'details';
23
24
    public const Manage = 'manage-exams';
25
    public const Add = 'new-exam';
26
    public const Edit = 'edit';
27
    public const Unplan = 'unplan';
28
    public const Remove = 'remove';
29
30
    public function __construct(private DateHelper $dateHelper, private ExamSettings $examSettings, private AccessDecisionManagerInterface $accessDecisionManager, private SectionResolverInterface $sectionResoler)
31
    {
32 10
    }
33 10
34 10
    /**
35 10
     * @inheritDoc
36 10
     */
37
    protected function supports($attribute, $subject): bool {
38
        $attributes = [
39
            self::Details,
40
            self::Supervisions,
41 10
            self::Show,
42
            self::Edit,
43 10
            self::Remove,
44 10
            self::Unplan
45 10
        ];
46 10
47 10
        return in_array($attribute , [ self::Add, self::Manage ]) || ($subject instanceof Exam && in_array($attribute, $attributes));
48 10
    }
49
50
    /**
51 10
     * @inheritDoc
52
     */
53
    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
54
    {
55
        return match ($attribute) {
56
            self::Show => $this->canViewExam($subject, $token),
57 4
            self::Details => $this->canViewDetails($subject, $token),
58
            self::Supervisions => $this->canViewSupervisions($subject, $token),
59 4
            self::Add => $this->canAdd($token),
60
            self::Edit, self::Unplan => $this->canEdit($subject, $token),
61
            self::Remove => $this->canRemove($subject, $token),
62 4
            self::Manage => $this->canManage($token),
63
            default => throw new LogicException('This code should not be reached.'),
64
        };
65 4
    }
66
67
    private function getUserType(TokenInterface $token): ?UserType {
68 4
        $user = $token->getUser();
69
70
        if(!$user instanceof User) {
71 4
            return null;
72 4
        }
73
74
        return $user->getUserType();
75 4
    }
76
77
    private function isStudentOrParent(TokenInterface $token): bool {
78 4
        $user = $token->getUser();
79 4
80
        if(!$user instanceof User) {
81
            return false;
82
        }
83
84
        return $user->isStudentOrParent();
85
    }
86
87
    public function canAdd(TokenInterface $token): bool {
88
        return $this->accessDecisionManager->decide($token, [ 'ROLE_EXAMS_CREATOR' ]);
89
    }
90
91
    public function canEdit(Exam $exam, TokenInterface $token): bool {
92
        if($this->accessDecisionManager->decide($token, ['ROLE_EXAMS_ADMIN']) === true) {
93
            return true;
94
        }
95
96
        if($exam->getExternalId() !== null) {
97
            // Non-Admins cannot edit external exams
98
            return false;
99
        }
100
101
        if($this->accessDecisionManager->decide($token, ['ROLE_EXAMS_CREATOR'])) {
102
            return true;
103
        }
104
105
        if($exam->isTuitionTeachersCanEditExam() !== true) {
106
            // Non-Admins cannot edit this exam
107
            return false;
108
        }
109
110
        $user = $token->getUser();
111
112
        if(!$user instanceof User) {
113
            return false;
114
        }
115
116
        $teacher = $user->getTeacher();
117
118
        if($teacher === null) {
119
            return false;
120
        }
121
122
        /** @var Tuition $tuition */
123
        foreach($exam->getTuitions() as $tuition) {
124
            foreach($tuition->getTeachers() as $tuitionTeacher) {
125
                if ($tuitionTeacher->getId() === $teacher->getId()) {
126
                    return true;
127
                }
128
            }
129
        }
130
131
        return false;
132
    }
133
134
    public function canManage(TokenInterface $token): bool {
135
        if($this->accessDecisionManager->decide($token, ['ROLE_EXAMS_ADMIN']) === true || $this->accessDecisionManager->decide($token, [ 'ROLE_EXAMS_CREATOR'])) {
136
            return true;
137
        }
138
139
        $user = $token->getUser();
140
141
        if(!$user instanceof User) {
142
            return false;
143
        }
144
145
        $teacher = $user->getTeacher();
146
147
        if($teacher === null) {
148
            return false;
149
        }
150 4
151 4
        return true;
152
    }
153
154
    public function canRemove(Exam $exam, TokenInterface $token): bool {
155 4
        if($this->accessDecisionManager->decide($token, ['ROLE_EXAMS_ADMIN']) === true) {
156
            return true;
157 4
        }
158
159
        if($exam->getExternalId() !== null) {
160
            // Non-Admins cannot edit external exams
161 4
            return false;
162
        }
163 4
164 4
        return $this->accessDecisionManager->decide($token, ['ROLE_EXAMS_CREATOR']) === true;
165
    }
166
167
    public function canViewExam(Exam $exam, TokenInterface $token): bool {
168
        if($this->accessDecisionManager->decide($token, ['ROLE_EXAMS_ADMIN']) === true || $this->accessDecisionManager->decide($token, ['ROLE_EXAMS_CREATOR']) === true) {
169
            return true;
170
        }
171
172
        $userType = $this->getUserType($token);
173
174
        if($userType === null) {
175
            return false;
176
        }
177
178
        if($this->examSettings->isVisibileFor($userType) === false) {
179
            return false;
180
        }
181
182
        $user = $token->getUser();
183
        $section = $this->sectionResoler->getSectionForDate($exam->getDate());
0 ignored issues
show
Bug introduced by
It seems like $exam->getDate() can also be of type null; however, parameter $dateTime of App\Section\SectionResol...ce::getSectionForDate() 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

183
        $section = $this->sectionResoler->getSectionForDate(/** @scrutinizer ignore-type */ $exam->getDate());
Loading history...
184
185
        if($user instanceof User && $this->isStudentOrParent($token)) {
186
            $visibleGradeIds = $this->examSettings->getVisibleGradeIds();
187
            $gradeIds = [ ];
188
189
            /** @var Student $student */
190
            foreach($user->getStudents() as $student) {
191
                $grade = $student->getGrade($section);
192
193
                if($grade !== null) {
194
                    $gradeIds[] = $grade->getId();
195
                }
196
            }
197
198
            if(count(array_intersect($visibleGradeIds, $gradeIds)) === 0) {
199
                return false;
200
            }
201
        }
202
203
        if($this->isStudentOrParent($token) && $exam->getDate() === null) {
204
            // Exam is not planned yet -> do not display
205
            return false;
206
        }
207
208
        $days = $this->examSettings->getTimeWindowForStudents();
209
        if($this->isStudentOrParent($token) && $days > 0) {
210
            $threshold = $this->dateHelper->getToday()
211
                ->modify(sprintf('+%d days', $days));
212
213
            return $exam->getDate() < $threshold;
214
        }
215
216
        return true;
217
    }
218
219
    public function canViewSupervisions(Exam $exam, TokenInterface $token): bool {
220
        $days = $this->examSettings->getTimeWindowForStudentsToSeeSupervisions();
221
        if($this->isStudentOrParent($token) && $days > 0) {
222
            $threshold = $this->dateHelper->getToday()
223
                ->modify(sprintf('+%d days', $days));
224
225
            return $exam->getDate() < $threshold;
226
        }
227
228
        return true;
229
    }
230
231
    private function canViewDetails(Exam $exam, TokenInterface $token): bool {
232
        return $this->isStudentOrParent($token) === false;
233
    }
234
}