Passed
Pull Request — master (#5831)
by
unknown
26:05 queued 12:07
created

SessionRepetitionCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 3
b 0
f 0
nc 1
nop 5
dl 0
loc 8
rs 10
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\AccessUrl;
10
use Chamilo\CoreBundle\Entity\GradebookCategory;
11
use Chamilo\CoreBundle\Entity\GradebookEvaluation;
12
use Chamilo\CoreBundle\Entity\GradebookLink;
13
use Chamilo\CoreBundle\Entity\Session;
14
use Chamilo\CoreBundle\Entity\User;
15
use Chamilo\CoreBundle\Repository\SessionRepository;
16
use Chamilo\CoreBundle\ServiceHelper\MessageHelper;
17
use Doctrine\ORM\EntityManagerInterface;
18
use Exception;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\HttpFoundation\RequestStack;
24
use Symfony\Component\Mailer\MailerInterface;
25
use Symfony\Contracts\Translation\TranslatorInterface;
26
27
class SessionRepetitionCommand extends Command
28
{
29
    protected static $defaultName = 'app:session-repetition';
30
31
    public function __construct(
32
        private readonly SessionRepository $sessionRepository,
33
        private readonly EntityManagerInterface $entityManager,
34
        private readonly TranslatorInterface $translator,
35
        private readonly MessageHelper $messageHelper,
36
        private readonly RequestStack $requestStack
37
    ) {
38
        parent::__construct();
39
    }
40
41
    protected function configure(): void
42
    {
43
        $this
44
            ->setDescription('Automatically duplicates sessions that meet the repetition criteria.')
45
            ->addOption('debug', null, InputOption::VALUE_NONE, 'Enable debug mode');
46
    }
47
48
    protected function execute(InputInterface $input, OutputInterface $output): int
49
    {
50
        $debug = $input->getOption('debug');
51
52
        // Find sessions that meet the repetition criteria
53
        $sessions = $this->sessionRepository->findSessionsWithoutChildAndReadyForRepetition();
54
55
        if ($debug) {
56
            $output->writeln(sprintf('Found %d session(s) ready for repetition.', count($sessions)));
57
        }
58
59
        foreach ($sessions as $session) {
60
            if ($debug) {
61
                $output->writeln(sprintf('Processing session: %d', $session->getId()));
62
            }
63
64
            // Duplicate session
65
            $newSession = $this->duplicateSession($session, $debug, $output);
66
67
            // Notify general coach of the new session
68
            $this->notifyGeneralCoach($newSession, $debug, $output);
69
70
            $output->writeln('Created new session: ' . $newSession->getId() . ' from session: ' . $session->getId());
71
        }
72
73
        return Command::SUCCESS;
74
    }
75
76
    /**
77
     * Duplicates a session and creates a new session with adjusted dates.
78
     * @throws Exception
79
     */
80
    private function duplicateSession(Session $session, bool $debug, OutputInterface $output): Session
81
    {
82
        // Calculate new session dates based on the duration of the original session
83
        $duration = $session->getAccessEndDate()->diff($session->getAccessStartDate())->days;
0 ignored issues
show
Documentation Bug introduced by
It seems like $session->getAccessEndDa...ccessStartDate())->days can also be of type boolean. However, the property $days is declared as type false|integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
84
        $newStartDate = (clone $session->getAccessEndDate())->modify('+1 day');
85
        $newEndDate = (clone $newStartDate)->modify("+{$duration} days");
86
87
        if ($debug) {
88
            $output->writeln(sprintf(
89
                'Duplicating session %d. New start date: %s, New end date: %s',
90
                $session->getId(),
91
                $newStartDate->format('Y-m-d H:i:s'),
92
                $newEndDate->format('Y-m-d H:i:s')
93
            ));
94
        }
95
96
        // Create a new session with the same details as the original session
97
        $newSession = new Session();
98
        $newSession
99
            ->setTitle($session->getTitle() . ' (Repetition ' . $session->getId() . ' - ' . time() . ')')
100
            ->setAccessStartDate($newStartDate)
101
            ->setAccessEndDate($newEndDate)
102
            ->setDisplayStartDate($newStartDate)
103
            ->setDisplayEndDate($newEndDate)
104
            ->setCoachAccessStartDate($newStartDate)
105
            ->setCoachAccessEndDate($newEndDate)
106
            ->setVisibility($session->getVisibility())
107
            ->setDuration(0)
108
            ->setDescription($session->getDescription() ?? '')
109
            ->setShowDescription($session->getShowDescription() ?? false)
110
            ->setCategory($session->getCategory())
111
            ->setPromotion($session->getPromotion())
112
            ->setDaysToReinscription($session->getDaysToReinscription())
113
            ->setDaysToNewRepetition($session->getDaysToNewRepetition())
114
            ->setParentId($session->getId())
115
            ->setLastRepetition(false);
116
117
        // Copy the AccessUrls from the original session
118
        foreach ($session->getUrls() as $accessUrl) {
119
            $newSession->addAccessUrl($accessUrl->getUrl());
120
        }
121
122
        // Save the new session
123
        $this->entityManager->persist($newSession);
124
        $this->entityManager->flush();
125
126
        if ($debug) {
127
            $output->writeln(sprintf('New session %d created successfully.', $newSession->getId()));
128
        }
129
130
        $courses = $session->getCourses()->toArray();
131
132
        if ($debug) {
133
            $output->writeln('Courses retrieved: ' . count($courses));
134
            foreach ($courses as $index => $sessionRelCourse) {
135
                $course = $sessionRelCourse->getCourse();
136
                $output->writeln(sprintf(
137
                    'Course #%d: %s (Course ID: %s)',
138
                    $index + 1,
139
                    $course ? $course->getTitle() : 'NULL',
140
                    $course ? $course->getId() : 'NULL'
141
                ));
142
            }
143
        }
144
145
        // Extract course IDs
146
        $courseList = array_map(function ($sessionRelCourse) {
147
            $course = $sessionRelCourse->getCourse();
148
            return $course?->getId();
149
        }, $courses);
150
151
        // Remove null values
152
        $courseList = array_filter($courseList);
153
154
        if ($debug) {
155
            $output->writeln(sprintf(
156
                'Extracted course IDs: %s',
157
                json_encode($courseList)
158
            ));
159
        }
160
161
        if (empty($courseList)) {
162
            $output->writeln(sprintf('Warning: No courses found in the original session %d.', $session->getId()));
163
        }
164
165
        // Add courses to the new session
166
        $courseCount = 0;
167
        foreach ($courses as $sessionRelCourse) {
168
            $course = $sessionRelCourse->getCourse();
169
            if ($course) {
170
                $newSession->addCourse($course);
171
                $this->entityManager->persist($newSession);
172
173
                if ($debug) {
174
                    $output->writeln(sprintf('Added course ID %d to session ID %d.', $course->getId(), $newSession->getId()));
175
                }
176
177
                $this->copyEvaluationsAndCategories($course->getId(), $session->getId(), $newSession->getId(), $debug, $output);
178
179
                $courseCount++;
180
            }
181
        }
182
183
        foreach ($session->getGeneralCoaches() as $coach) {
184
            $newSession->addGeneralCoach($coach);
185
        }
186
187
        $newSession->setNbrCourses($courseCount);
188
        $this->entityManager->persist($newSession);
189
190
        $this->entityManager->flush();
191
192
        return $newSession;
193
    }
194
195
    /**
196
     * Notifies the general coach of the session about the new repetition.
197
     */
198
    private function notifyGeneralCoach(Session $newSession, bool $debug, OutputInterface $output): void
199
    {
200
        $generalCoach = $newSession->getGeneralCoaches()->first();
201
        if ($generalCoach) {
202
            $messageSubject = $this->translator->trans('New Session Repetition Created');
203
            $messageContent = sprintf(
204
                'A new repetition of the session "%s" has been created. Please review the details: %s',
205
                $newSession->getTitle(),
206
                $this->generateSessionSummaryLink($newSession)
207
            );
208
209
            if ($debug) {
210
                $output->writeln(sprintf('Notifying coach (ID: %d) for session %d', $generalCoach->getId(), $newSession->getId()));
211
            }
212
213
            $senderId = $this->getFirstAdminId();
214
            $this->messageHelper->sendMessageSimple(
215
                $generalCoach->getId(),
216
                $messageSubject,
217
                $messageContent,
218
                $senderId
219
            );
220
221
            if ($debug) {
222
                $output->writeln('Notification sent using MessageHelper.');
223
            }
224
        } elseif ($debug) {
225
            $output->writeln('No general coach found for session ' . $newSession->getId());
226
        }
227
    }
228
229
    private function getFirstAdminId(): int
230
    {
231
        $admin = $this->entityManager->getRepository(User::class)->findOneBy([]);
232
233
        return $admin && ($admin->hasRole('ROLE_ADMIN') || $admin->hasRole('ROLE_SUPER_ADMIN'))
234
            ? $admin->getId()
235
            : 1;
236
    }
237
238
    /**
239
     * Generates a link to the session summary page.
240
     */
241
    private function generateSessionSummaryLink(Session $session): string
242
    {
243
        $request = $this->requestStack->getCurrentRequest();
244
        if (!$request) {
245
            return '/main/session/resume_session.php?id_session=' . $session->getId();
246
        }
247
248
        $baseUrl = $request->getSchemeAndHttpHost();
249
        return sprintf('%s/main/session/resume_session.php?id_session=%d', $baseUrl, $session->getId());
250
    }
251
252
    /**
253
     * Copies gradebook categories, evaluations, and links from the old session to the new session.
254
     */
255
    private function copyEvaluationsAndCategories(
256
        int $courseId,
257
        int $oldSessionId,
258
        int $newSessionId,
259
        bool $debug,
260
        OutputInterface $output
261
    ): void {
262
        // Get existing categories of the original course and session
263
        $categories = $this->entityManager->getRepository(GradebookCategory::class)
264
            ->findBy(['course' => $courseId, 'session' => $oldSessionId]);
265
266
        if ($debug) {
267
            $output->writeln(sprintf('Found %d category(ies) for course ID %d in session ID %d.', count($categories), $courseId, $oldSessionId));
268
        }
269
270
        foreach ($categories as $category) {
271
            // Create new category for the new session
272
            $newCategory = new GradebookCategory();
273
            $newCategory->setTitle($category->getTitle())
274
                ->setDescription($category->getDescription())
275
                ->setWeight($category->getWeight())
276
                ->setVisible($category->getVisible())
277
                ->setCertifMinScore($category->getCertifMinScore())
278
                ->setGenerateCertificates($category->getGenerateCertificates())
279
                ->setIsRequirement($category->getIsRequirement())
280
                ->setCourse($category->getCourse())
281
                ->setSession($this->entityManager->getReference(Session::class, $newSessionId))
282
                ->setParent($category->getParent());
283
284
            $this->entityManager->persist($newCategory);
285
            $this->entityManager->flush();
286
287
            if ($debug) {
288
                $output->writeln(sprintf('Created new category ID %d for session ID %d.', $newCategory->getId(), $newSessionId));
289
            }
290
291
            // Copy links
292
            $links = $this->entityManager->getRepository(GradebookLink::class)
293
                ->findBy(['category' => $category->getId()]);
294
295
            foreach ($links as $link) {
296
                $newLink = clone $link;
297
                $newLink->setCategory($newCategory);
298
                $this->entityManager->persist($newLink);
299
            }
300
301
            // Copy evaluations
302
            $evaluations = $this->entityManager->getRepository(GradebookEvaluation::class)
303
                ->findBy(['category' => $category->getId()]);
304
305
            foreach ($evaluations as $evaluation) {
306
                $newEvaluation = clone $evaluation;
307
                $newEvaluation->setCategory($newCategory);
308
                $this->entityManager->persist($newEvaluation);
309
            }
310
311
            $this->entityManager->flush();
312
313
            if ($debug) {
314
                $output->writeln(sprintf('Copied links and evaluations for category ID %d to new category ID %d.', $category->getId(), $newCategory->getId()));
315
            }
316
        }
317
    }
318
}
319