Completed
Push — master ( 4a78a8...002ed2 )
by
unknown
01:57 queued 59s
created

findValidSessionInHierarchy()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 19
rs 9.6111
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Command;
8
9
use Chamilo\CoreBundle\Entity\Session;
10
use Chamilo\CoreBundle\Entity\SessionRelUser;
11
use Chamilo\CoreBundle\Entity\SessionRelCourse;
12
use Chamilo\CoreBundle\Entity\GradebookCertificate;
13
use Chamilo\CoreBundle\Repository\SessionRepository;
14
use Chamilo\CoreBundle\Repository\GradebookCertificateRepository;
15
use Doctrine\ORM\EntityManagerInterface;
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
21
class ReinscriptionCheckCommand extends Command
22
{
23
    protected static $defaultName = 'app:reinscription-check';
24
25
    private SessionRepository $sessionRepository;
26
    private GradebookCertificateRepository $certificateRepository;
27
    private EntityManagerInterface $entityManager;
28
29
    public function __construct(
30
        SessionRepository $sessionRepository,
31
        GradebookCertificateRepository $certificateRepository,
32
        EntityManagerInterface $entityManager
33
    ) {
34
        parent::__construct();
35
        $this->sessionRepository = $sessionRepository;
36
        $this->certificateRepository = $certificateRepository;
37
        $this->entityManager = $entityManager;
38
    }
39
40
    protected function configure(): void
41
    {
42
        $this
43
            ->setDescription('Checks for users who have validated all gradebooks and reinscribe them into new sessions if needed.')
44
            ->addOption(
45
                'debug',
46
                null,
47
                InputOption::VALUE_NONE,
48
                'If set, debug messages will be shown.'
49
            );
50
    }
51
52
    protected function execute(InputInterface $input, OutputInterface $output): int
53
    {
54
        $debug = $input->getOption('debug');
55
56
        $sessions = $this->sessionRepository->findAll();
57
58
        foreach ($sessions as $session) {
59
            if ($session->getValidityInDays() === null || $session->getValidityInDays() === 0) {
60
                continue;
61
            }
62
63
            $users = $this->getUsersForSession($session);
64
65
            foreach ($users as $user) {
66
                if ($debug) {
67
                    $output->writeln(sprintf('Processing user %d in session %d.', $user->getId(), $session->getId()));
68
                }
69
70
                if ($this->isUserReinscribed($user, $session)) {
71
                    continue;
72
                }
73
74
                if ($this->isUserAlreadyEnrolledInChildSession($user, $session)) {
75
                    if ($debug) {
76
                        $output->writeln(sprintf('User %d is already enrolled in a valid child session.', $user->getId()));
77
                    }
78
                    continue;
79
                }
80
81
                $certificates = $this->getUserCertificatesForSession($user, $session);
82
83
                if ($this->hasUserValidatedAllGradebooks($session, $certificates)) {
84
                    $latestValidationDate = $this->getLatestCertificateDate($certificates);
85
86
                    if ($latestValidationDate !== null) {
87
                        $reinscriptionDate = (clone $latestValidationDate)->modify("+{$session->getValidityInDays()} days");
88
89
                        if ($debug) {
90
                            $output->writeln(sprintf(
91
                                'User %d - Latest certificate date: %s, Reinscription date: %s',
92
                                $user->getId(),
93
                                $latestValidationDate->format('Y-m-d'),
94
                                $reinscriptionDate->format('Y-m-d')
95
                            ));
96
                        }
97
98
                        if (new \DateTime() >= $reinscriptionDate) {
99
                            $validSession = $this->findValidSessionInHierarchy($session);
100
101
                            if ($validSession) {
102
                                $this->enrollUserInSession($user, $validSession, $session);
103
                                if ($debug) {
104
                                    $output->writeln(sprintf(
105
                                        'User %d re-enrolled into session %d.',
106
                                        $user->getId(),
107
                                        $validSession->getId()
108
                                    ));
109
                                }
110
                            }
111
                        }
112
                    } else {
113
                        if ($debug) {
114
                            $output->writeln(sprintf(
115
                                'User %d has no valid certificates for session %d.',
116
                                $user->getId(),
117
                                $session->getId()
118
                            ));
119
                        }
120
                    }
121
                }
122
            }
123
        }
124
125
        return Command::SUCCESS;
126
    }
127
128
    /**
129
     * Retrieves all users associated with the session.
130
     */
131
    private function getUsersForSession(Session $session): array
132
    {
133
        $usersToNotify = [];
134
        $sessionCourses = $this->entityManager->getRepository(SessionRelCourse::class)->findBy(['session' => $session]);
135
136
        foreach ($sessionCourses as $courseRel) {
137
            $course = $courseRel->getCourse();
138
139
            $studentSubscriptions = $session->getSessionRelCourseRelUsersByStatus($course, Session::STUDENT);
140
            foreach ($studentSubscriptions as $studentSubscription) {
141
                $usersToNotify[$studentSubscription->getUser()->getId()] = $studentSubscription->getUser();
142
            }
143
144
            $coachSubscriptions = $session->getSessionRelCourseRelUsersByStatus($course, Session::COURSE_COACH);
145
            foreach ($coachSubscriptions as $coachSubscription) {
146
                $usersToNotify[$coachSubscription->getUser()->getId()] = $coachSubscription->getUser();
147
            }
148
        }
149
150
        $generalCoaches = $session->getGeneralCoaches();
151
        foreach ($generalCoaches as $generalCoach) {
152
            $usersToNotify[$generalCoach->getId()] = $generalCoach;
153
        }
154
155
        return array_values($usersToNotify);
156
    }
157
158
    /**
159
     * Checks if the user is already enrolled in a valid child session.
160
     */
161
    private function isUserAlreadyEnrolledInChildSession($user, $parentSession): bool
162
    {
163
        $childSessions = $this->sessionRepository->findChildSessions($parentSession);
164
165
        foreach ($childSessions as $childSession) {
166
            if ($this->findUserSubscriptionInSession($user, $childSession)) {
167
                return true;
168
            }
169
        }
170
171
        return false;
172
    }
173
174
    /**
175
     * Gets the user's certificates for the courses in the session.
176
     */
177
    private function getUserCertificatesForSession($user, Session $session): array
178
    {
179
        $courses = $this->entityManager->getRepository(SessionRelCourse::class)
180
            ->findBy(['session' => $session]);
181
182
        $courseIds = array_map(fn($rel) => $rel->getCourse()->getId(), $courses);
183
184
        return $this->certificateRepository->createQueryBuilder('gc')
185
            ->join('gc.category', 'cat')
186
            ->where('gc.user = :user')
187
            ->andWhere('cat.course IN (:courses)')
188
            ->setParameter('user', $user)
189
            ->setParameter('courses', $courseIds)
190
            ->getQuery()
191
            ->getResult();
192
    }
193
194
    /**
195
     * Checks if the user has validated all gradebooks in the session.
196
     */
197
    private function hasUserValidatedAllGradebooks(Session $session, array $certificates): bool
198
    {
199
        $courses = $this->entityManager->getRepository(SessionRelCourse::class)
200
            ->findBy(['session' => $session]);
201
202
        return count($certificates) === count($courses);
203
    }
204
205
    /**
206
     * Returns the latest certificate creation date.
207
     */
208
    private function getLatestCertificateDate(array $certificates): ?\DateTime
209
    {
210
        $dates = array_map(fn($cert) => $cert->getCreatedAt(), $certificates);
211
212
        if (empty($dates)) {
213
            return null;
214
        }
215
216
        return max($dates);
217
    }
218
219
    /**
220
     * Enrolls the user in a new session and updates the previous session subscription.
221
     */
222
    private function enrollUserInSession($user, $newSession, $oldSession): void
223
    {
224
        $existingSubscription = $this->findUserSubscriptionInSession($user, $newSession);
225
226
        if (!$existingSubscription) {
227
            $newSession->addUserInSession(Session::STUDENT, $user);
228
229
            foreach ($newSession->getCourses() as $sessionRelCourse) {
230
                $course = $sessionRelCourse->getCourse();
231
                if ($course) {
232
                    $newSession->addUserInCourse(Session::STUDENT, $user, $course);
233
                }
234
            }
235
236
            $subscription = $this->findUserSubscriptionInSession($user, $oldSession);
237
            if ($subscription) {
238
                $subscription->setNewSubscriptionSessionId($newSession->getId());
239
            }
240
241
            $this->entityManager->persist($newSession);
242
            $this->entityManager->flush();
243
        }
244
    }
245
246
    /**
247
     * Determines if the user has already been reinscribed.
248
     */
249
    private function isUserReinscribed($user, Session $session): bool
250
    {
251
        $subscription = $this->findUserSubscriptionInSession($user, $session);
252
        return $subscription && $subscription->getNewSubscriptionSessionId() !== null;
253
    }
254
255
    /**
256
     * Finds the user's subscription in the specified session.
257
     */
258
    private function findUserSubscriptionInSession($user, $session)
259
    {
260
        return $this->entityManager->getRepository(SessionRelUser::class)
261
            ->findOneBy([
262
                'user' => $user,
263
                'session' => $session,
264
            ]);
265
    }
266
267
    /**
268
     * Finds a valid session within the session hierarchy.
269
     */
270
    private function findValidSessionInHierarchy(Session $session): ?Session
271
    {
272
        $childSessions = $this->sessionRepository->findChildSessions($session);
273
274
        /* @var Session $child */
275
        foreach ($childSessions as $child) {
276
            $validUntil = (clone $child->getAccessEndDate())->modify("-{$child->getDaysToReinscription()} days");
277
            if (new \DateTime() <= $validUntil) {
278
                return $child;
279
            }
280
        }
281
282
        $parentSession = $this->sessionRepository->findParentSession($session);
283
284
        if ($parentSession && new \DateTime() <= $parentSession->getAccessEndDate()) {
285
            return $parentSession;
286
        }
287
288
        return null;
289
    }
290
}
291