Passed
Pull Request — master (#5831)
by
unknown
07:44
created

SessionRepetitionCommand::duplicateSession()   F

Complexity

Conditions 14
Paths 512

Size

Total Lines 113
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 14
eloc 71
c 3
b 0
f 0
nc 512
nop 3
dl 0
loc 113
rs 2.8549

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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