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

SessionRepetitionCommand::getFirstAdminId()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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