Passed
Push — master ( 419aeb...1775c7 )
by
unknown
09:16 queued 15s
created

CourseVoter::isCourseLockedForUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 11
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 20
rs 9.9
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Security\Authorization\Voter;
8
9
use Chamilo\CoreBundle\Entity\Course;
10
use Chamilo\CoreBundle\Entity\SequenceResource;
11
use Chamilo\CoreBundle\Entity\Session;
12
use Chamilo\CoreBundle\Entity\User;
13
use Chamilo\CoreBundle\Exception\NotAllowedException;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Symfony\Bundle\SecurityBundle\Security;
16
use Symfony\Component\HttpFoundation\RequestStack;
17
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
18
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
19
use Symfony\Component\Security\Core\User\UserInterface;
20
use Symfony\Contracts\Translation\TranslatorInterface;
21
22
/**
23
 * @extends Voter<'VIEW'|'EDIT'|'DELETE', Course>
24
 */
25
class CourseVoter extends Voter
26
{
27
    public const VIEW = 'VIEW';
28
    public const EDIT = 'EDIT';
29
    public const DELETE = 'DELETE';
30
31
    private RequestStack $requestStack;
32
    private EntityManagerInterface $entityManager;
33
34
    public function __construct(
35
        private readonly Security $security,
36
        private readonly TranslatorInterface $translator,
37
        RequestStack $requestStack,
38
        EntityManagerInterface $entityManager
39
    ) {
40
        $this->requestStack = $requestStack;
41
        $this->entityManager = $entityManager;
42
    }
43
44
    protected function supports(string $attribute, $subject): bool
45
    {
46
        $options = [
47
            self::VIEW,
48
            self::EDIT,
49
            self::DELETE,
50
        ];
51
52
        // if the attribute isn't one we support, return false
53
        if (!\in_array($attribute, $options, true)) {
54
            return false;
55
        }
56
57
        // only vote on Post objects inside this voter
58
        return $subject instanceof Course;
59
    }
60
61
    protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
62
    {
63
        /** @var User $user */
64
        $user = $token->getUser();
65
66
        // Admins have access to everything.
67
        if ($this->security->isGranted('ROLE_ADMIN')) {
68
            return true;
69
        }
70
71
        $request = $this->requestStack->getCurrentRequest();
72
        $sessionId = $request->query->get('sid');
73
        $sessionRepository = $this->entityManager->getRepository(Session::class);
74
75
        // Course is active?
76
        /** @var Course $course */
77
        $course = $subject;
78
79
        $session = null;
80
        if ($sessionId) {
81
            // Session is active?
82
            /** @var Session $session */
83
            $session = $sessionRepository->find($sessionId);
84
        }
85
86
        switch ($attribute) {
87
            case self::VIEW:
88
                // Course is hidden then is not visible for nobody expect admins.
89
                if ($course->isHidden()) {
90
                    return false;
91
                }
92
93
                // "Open to the world" no need to check if user is registered or if user exists.
94
                // Course::OPEN_WORLD
95
                if ($course->isPublic()) {
96
                    if ($this->isStudent($user, $course, $session)) {
97
                        if ($this->isCourseLockedForUser($user, $course, $session?->getId() ?? 0)) {
98
                            throw new NotAllowedException(
99
                                $this->translator->trans('This course is locked. You must complete the prerequisite(s) first.'),
100
                                'warning',
101
                                403
102
                            );
103
                        }
104
                    }
105
                    return true;
106
                }
107
108
                // User should be instance of UserInterface.
109
                if (!$user instanceof UserInterface) {
110
                    return false;
111
                }
112
113
                // If user is logged in and is open platform, allow access.
114
                if (Course::OPEN_PLATFORM === $course->getVisibility()) {
115
                    if ($this->isStudent($user, $course, $session)) {
116
                        if ($this->isCourseLockedForUser($user, $course, $session?->getId() ?? 0)) {
117
                            throw new NotAllowedException(
118
                                $this->translator->trans('This course is locked. You must complete the prerequisite(s) first.'),
119
                                'warning',
120
                                403
121
                            );
122
                        }
123
                    }
124
125
                    $user->addRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_STUDENT);
126
127
                    if ($course->hasUserAsTeacher($user)) {
128
                        $user->addRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER);
129
                    }
130
131
                    $token->setUser($user);
132
133
                    return true;
134
                }
135
136
                // Validation in session
137
                if ($session) {
138
                    $userIsGeneralCoach = $session->hasUserAsGeneralCoach($user);
139
                    $userIsCourseCoach = $session->hasCourseCoachInCourse($user, $course);
140
                    $userIsStudent = $session->hasUserInCourse($user, $course, Session::STUDENT);
141
142
                    if ($userIsGeneralCoach || $userIsCourseCoach) {
143
                        $user->addRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_SESSION_TEACHER);
144
                        return true;
145
                    }
146
147
                    if ($userIsStudent) {
148
                        $user->addRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_SESSION_STUDENT);
149
                        if ($this->isCourseLockedForUser($user, $course, $session->getId())) {
150
                            throw new NotAllowedException(
151
                                $this->translator->trans('This course is locked. You must complete the prerequisite(s) first.'),
152
                                'warning',
153
                                403
154
                            );
155
                        }
156
157
                        return true;
158
                    }
159
                }
160
161
                // Course::REGISTERED
162
                // User must be subscribed in the course no matter if is teacher/student
163
                if ($course->hasSubscriptionByUser($user)) {
164
                    $user->addRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_STUDENT);
165
166
                    if ($course->hasUserAsTeacher($user)) {
167
                        $user->addRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER);
168
                    }
169
170
                    if ($this->isCourseLockedForUser($user, $course)) {
171
                        throw new NotAllowedException(
172
                            $this->translator->trans('This course is locked. You must complete the prerequisite(s) first.'),
173
                            'warning',
174
                            403
175
                        );
176
                    }
177
178
                    $token->setUser($user);
179
180
                    return true;
181
                }
182
183
                break;
184
185
            case self::EDIT:
186
            case self::DELETE:
187
                // Only teacher can edit/delete stuff.
188
                if ($course->hasUserAsTeacher($user)) {
189
                    $user->addRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER);
190
                    $token->setUser($user);
191
192
                    return true;
193
                }
194
195
                break;
196
        }
197
198
        return false;
199
    }
200
201
    /**
202
     * Checks whether the given course is locked for the user
203
     * due to unmet prerequisite sequences.
204
     */
205
    private function isCourseLockedForUser(User $user, Course $course, int $sessionId = 0): bool
206
    {
207
        $sequenceRepo = $this->entityManager->getRepository(SequenceResource::class);
208
209
        $sequences = $sequenceRepo->getRequirements(
210
            $course->getId(),
211
            SequenceResource::COURSE_TYPE
212
        );
213
214
        if (empty($sequences)) {
215
            return false;
216
        }
217
218
        $statusList = $sequenceRepo->checkRequirementsForUser(
219
            $sequences,
220
            SequenceResource::COURSE_TYPE,
221
            $user->getId()
222
        );
223
224
        return !$sequenceRepo->checkSequenceAreCompleted($statusList);
225
    }
226
227
    private function isStudent(User $user, Course $course, ?Session $session): bool
228
    {
229
        if ($session) {
230
            return $session->hasUserInCourse($user, $course, Session::STUDENT);
231
        }
232
233
        return $course->hasUserAsStudent($user);
234
    }
235
}
236