Passed
Pull Request — master (#5831)
by
unknown
10:22
created

SessionRepetitionCommand::execute()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 26
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 11
nc 6
nop 2
dl 0
loc 26
rs 9.9
c 1
b 0
f 0
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\Mailer\MailerInterface;
24
use Symfony\Contracts\Translation\TranslatorInterface;
25
26
class SessionRepetitionCommand extends Command
27
{
28
    protected static $defaultName = 'app:session-repetition';
29
30
    public function __construct(
31
        private readonly SessionRepository $sessionRepository,
32
        private readonly EntityManagerInterface $entityManager,
33
        private readonly TranslatorInterface $translator,
34
        private readonly MessageHelper $messageHelper
35
    ) {
36
        parent::__construct();
37
    }
38
39
    protected function configure(): void
40
    {
41
        $this
42
            ->setDescription('Automatically duplicates sessions that meet the repetition criteria.')
43
            ->addOption('debug', null, InputOption::VALUE_NONE, 'Enable debug mode');
44
    }
45
46
    protected function execute(InputInterface $input, OutputInterface $output): int
47
    {
48
        $debug = $input->getOption('debug');
49
50
        // Find sessions that meet the repetition criteria
51
        $sessions = $this->sessionRepository->findSessionsWithoutChildAndReadyForRepetition();
52
53
        if ($debug) {
54
            $output->writeln(sprintf('Found %d session(s) ready for repetition.', count($sessions)));
55
        }
56
57
        foreach ($sessions as $session) {
58
            if ($debug) {
59
                $output->writeln(sprintf('Processing session: %d', $session->getId()));
60
            }
61
62
            // Duplicate session
63
            $newSession = $this->duplicateSession($session, $debug, $output);
64
65
            // Notify general coach of the new session
66
            $this->notifyGeneralCoach($newSession, $debug, $output);
67
68
            $output->writeln('Created new session: ' . $newSession->getId() . ' from session: ' . $session->getId());
69
        }
70
71
        return Command::SUCCESS;
72
    }
73
74
    /**
75
     * Duplicates a session and creates a new session with adjusted dates.
76
     * @throws Exception
77
     */
78
    private function duplicateSession(Session $session, bool $debug, OutputInterface $output): Session
79
    {
80
        // Calculate new session dates based on the duration of the original session
81
        $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...
82
        $newStartDate = (clone $session->getAccessEndDate())->modify('+1 day');
83
        $newEndDate = (clone $newStartDate)->modify("+{$duration} days");
84
85
        if ($debug) {
86
            $output->writeln(sprintf(
87
                'Duplicating session %d. New start date: %s, New end date: %s',
88
                $session->getId(),
89
                $newStartDate->format('Y-m-d H:i:s'),
90
                $newEndDate->format('Y-m-d H:i:s')
91
            ));
92
        }
93
94
        // Create a new session with the same details as the original session
95
        $newSession = new Session();
96
        $newSession
97
            ->setTitle($session->getTitle() . ' (Repetition ' . $session->getId() . ' - ' . time() . ')')
98
            ->setAccessStartDate($newStartDate)
99
            ->setAccessEndDate($newEndDate)
100
            ->setDisplayStartDate($newStartDate)
101
            ->setDisplayEndDate($newEndDate)
102
            ->setCoachAccessStartDate($newStartDate)
103
            ->setCoachAccessEndDate($newEndDate)
104
            ->setVisibility($session->getVisibility())
105
            ->setDuration(0)
106
            ->setDescription($session->getDescription() ?? '')
107
            ->setShowDescription($session->getShowDescription() ?? false)
108
            ->setCategory($session->getCategory())
109
            ->setPromotion($session->getPromotion())
110
            ->setDaysToReinscription($session->getDaysToReinscription())
111
            ->setDaysToNewRepetition($session->getDaysToNewRepetition())
112
            ->setParentId($session->getId())
113
            ->setLastRepetition(false);
114
115
        // Copy the AccessUrls from the original session
116
        foreach ($session->getUrls() as $accessUrl) {
117
            $newSession->addAccessUrl($accessUrl->getUrl());
118
        }
119
120
        // Save the new session
121
        $this->entityManager->persist($newSession);
122
        $this->entityManager->flush();
123
124
        if ($debug) {
125
            $output->writeln(sprintf('New session %d created successfully.', $newSession->getId()));
126
        }
127
128
        $courses = $session->getCourses()->toArray();
129
130
        if ($debug) {
131
            $output->writeln('Courses retrieved: ' . count($courses));
132
            foreach ($courses as $index => $sessionRelCourse) {
133
                $course = $sessionRelCourse->getCourse();
134
                $output->writeln(sprintf(
135
                    'Course #%d: %s (Course ID: %s)',
136
                    $index + 1,
137
                    $course ? $course->getTitle() : 'NULL',
138
                    $course ? $course->getId() : 'NULL'
139
                ));
140
            }
141
        }
142
143
        // Extract course IDs
144
        $courseList = array_map(function ($sessionRelCourse) {
145
            $course = $sessionRelCourse->getCourse();
146
            return $course?->getId();
147
        }, $courses);
148
149
        // Remove null values
150
        $courseList = array_filter($courseList);
151
152
        if ($debug) {
153
            $output->writeln(sprintf(
154
                'Extracted course IDs: %s',
155
                json_encode($courseList)
156
            ));
157
        }
158
159
        if (empty($courseList)) {
160
            $output->writeln(sprintf('Warning: No courses found in the original session %d.', $session->getId()));
161
        }
162
163
        // Add courses to the new session
164
        $courseCount = 0;
165
        foreach ($courses as $sessionRelCourse) {
166
            $course = $sessionRelCourse->getCourse();
167
            if ($course) {
168
                $newSession->addCourse($course);
169
                $this->entityManager->persist($newSession);
170
171
                if ($debug) {
172
                    $output->writeln(sprintf('Added course ID %d to session ID %d.', $course->getId(), $newSession->getId()));
173
                }
174
175
                $this->copyEvaluationsAndCategories($course->getId(), $session->getId(), $newSession->getId(), $debug, $output);
176
177
                $courseCount++;
178
            }
179
        }
180
181
        foreach ($session->getGeneralCoaches() as $coach) {
182
            $newSession->addGeneralCoach($coach);
183
        }
184
185
        $newSession->setNbrCourses($courseCount);
186
        $this->entityManager->persist($newSession);
187
188
        $this->entityManager->flush();
189
190
        return $newSession;
191
    }
192
193
    /**
194
     * Notifies the general coach of the session about the new repetition.
195
     */
196
    private function notifyGeneralCoach(Session $newSession, bool $debug, OutputInterface $output): void
197
    {
198
        $generalCoach = $newSession->getGeneralCoaches()->first();
199
        if ($generalCoach) {
200
            $messageSubject = $this->translator->trans('New Session Repetition Created');
201
            $messageContent = sprintf(
202
                'A new repetition of the session "%s" has been created. Please review the details: %s',
203
                $newSession->getTitle(),
204
                $this->generateSessionSummaryLink($newSession)
205
            );
206
207
            if ($debug) {
208
                $output->writeln(sprintf('Notifying coach (ID: %d) for session %d', $generalCoach->getId(), $newSession->getId()));
209
            }
210
211
            $senderId = $this->getFirstAdminId();
212
            $this->messageHelper->sendMessageSimple(
213
                $generalCoach->getId(),
214
                $messageSubject,
215
                $messageContent,
216
                $senderId
217
            );
218
219
            if ($debug) {
220
                $output->writeln('Notification sent using MessageHelper.');
221
            }
222
        } elseif ($debug) {
223
            $output->writeln('No general coach found for session ' . $newSession->getId());
224
        }
225
    }
226
227
    private function getFirstAdminId(): int
228
    {
229
        $admin = $this->entityManager->getRepository(User::class)->findOneBy([]);
230
231
        return $admin && ($admin->hasRole('ROLE_ADMIN') || $admin->hasRole('ROLE_SUPER_ADMIN'))
232
            ? $admin->getId()
233
            : 1;
234
    }
235
236
    /**
237
     * Generates a link to the session summary page.
238
     */
239
    private function generateSessionSummaryLink(Session $session): string
240
    {
241
        return '/main/session/resume_session.php?id_session=' . $session->getId();
242
    }
243
244
    /**
245
     * Copies gradebook categories, evaluations, and links from the old session to the new session.
246
     */
247
    private function copyEvaluationsAndCategories(
248
        int $courseId,
249
        int $oldSessionId,
250
        int $newSessionId,
251
        bool $debug,
252
        OutputInterface $output
253
    ): void {
254
        // Get existing categories of the original course and session
255
        $categories = $this->entityManager->getRepository(GradebookCategory::class)
256
            ->findBy(['course' => $courseId, 'session' => $oldSessionId]);
257
258
        if ($debug) {
259
            $output->writeln(sprintf('Found %d category(ies) for course ID %d in session ID %d.', count($categories), $courseId, $oldSessionId));
260
        }
261
262
        foreach ($categories as $category) {
263
            // Create new category for the new session
264
            $newCategory = new GradebookCategory();
265
            $newCategory->setTitle($category->getTitle())
266
                ->setDescription($category->getDescription())
267
                ->setWeight($category->getWeight())
268
                ->setVisible($category->getVisible())
269
                ->setCertifMinScore($category->getCertifMinScore())
270
                ->setGenerateCertificates($category->getGenerateCertificates())
271
                ->setIsRequirement($category->getIsRequirement())
272
                ->setCourse($category->getCourse())
273
                ->setSession($this->entityManager->getReference(Session::class, $newSessionId))
274
                ->setParent($category->getParent());
275
276
            $this->entityManager->persist($newCategory);
277
            $this->entityManager->flush();
278
279
            if ($debug) {
280
                $output->writeln(sprintf('Created new category ID %d for session ID %d.', $newCategory->getId(), $newSessionId));
281
            }
282
283
            // Copy links
284
            $links = $this->entityManager->getRepository(GradebookLink::class)
285
                ->findBy(['category' => $category->getId()]);
286
287
            foreach ($links as $link) {
288
                $newLink = clone $link;
289
                $newLink->setCategory($newCategory);
290
                $this->entityManager->persist($newLink);
291
            }
292
293
            // Copy evaluations
294
            $evaluations = $this->entityManager->getRepository(GradebookEvaluation::class)
295
                ->findBy(['category' => $category->getId()]);
296
297
            foreach ($evaluations as $evaluation) {
298
                $newEvaluation = clone $evaluation;
299
                $newEvaluation->setCategory($newCategory);
300
                $this->entityManager->persist($newEvaluation);
301
            }
302
303
            $this->entityManager->flush();
304
305
            if ($debug) {
306
                $output->writeln(sprintf('Copied links and evaluations for category ID %d to new category ID %d.', $category->getId(), $newCategory->getId()));
307
            }
308
        }
309
    }
310
}
311