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

copyEvaluationsAndCategories()   B

Complexity

Conditions 7
Paths 34

Size

Total Lines 60
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 35
c 0
b 0
f 0
nc 34
nop 5
dl 0
loc 60
rs 8.4266

How to fix   Long Method   

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
        foreach ($courses as $sessionRelCourse) {
164
            $course = $sessionRelCourse->getCourse();
165
            if ($course) {
166
                $newSession->addCourse($course);
167
168
                if ($debug) {
169
                    $output->writeln(sprintf('Added course ID %d to session ID %d.', $course->getId(), $newSession->getId()));
170
                }
171
172
                $this->copyEvaluationsAndCategories($course->getId(), $session->getId(), $newSession->getId(), $debug, $output);
173
            }
174
        }
175
176
        $this->entityManager->flush();
177
178
        return $newSession;
179
    }
180
181
    /**
182
     * Notifies the general coach of the session about the new repetition.
183
     */
184
    private function notifyGeneralCoach(Session $newSession, bool $debug, OutputInterface $output): void
185
    {
186
        $generalCoach = $newSession->getGeneralCoaches()->first();
187
        if ($generalCoach) {
188
            $message = sprintf(
189
                'A new repetition of the session "%s" has been created. Please review the details: %s',
190
                $newSession->getTitle(),
191
                $this->generateSessionSummaryLink($newSession)
192
            );
193
194
            if ($debug) {
195
                $output->writeln(sprintf('Notifying coach (ID: %d) for session %d', $generalCoach->getId(), $newSession->getId()));
196
            }
197
198
            // Send message to the general coach
199
            $this->sendMessage($generalCoach->getEmail(), $message);
200
201
            if ($debug) {
202
                $output->writeln('Notification sent.');
203
            }
204
        } else {
205
            if ($debug) {
206
                $output->writeln('No general coach found for session ' . $newSession->getId());
207
            }
208
        }
209
    }
210
211
    /**
212
     * Sends an email message to a user.
213
     */
214
    private function sendMessage(string $recipientEmail, string $message): void
215
    {
216
        $subject = $this->translator->trans('New Session Repetition Created');
217
218
        $email = (new Email())
219
            ->from('[email protected]')
220
            ->to($recipientEmail)
221
            ->subject($subject)
222
            ->html('<p>' . $message . '</p>');
223
224
        $this->mailer->send($email);
225
    }
226
227
    /**
228
     * Generates a link to the session summary page.
229
     */
230
    private function generateSessionSummaryLink(Session $session): string
231
    {
232
        return '/main/session/resume_session.php?id_session=' . $session->getId();
233
    }
234
235
    /**
236
     * Copies gradebook categories, evaluations, and links from the old session to the new session.
237
     */
238
    private function copyEvaluationsAndCategories(
239
        int $courseId,
240
        int $oldSessionId,
241
        int $newSessionId,
242
        bool $debug,
243
        OutputInterface $output
244
    ): void {
245
        // Get existing categories of the original course and session
246
        $categories = $this->entityManager->getRepository(GradebookCategory::class)
247
            ->findBy(['course' => $courseId, 'session' => $oldSessionId]);
248
249
        if ($debug) {
250
            $output->writeln(sprintf('Found %d category(ies) for course ID %d in session ID %d.', count($categories), $courseId, $oldSessionId));
251
        }
252
253
        foreach ($categories as $category) {
254
            // Create new category for the new session
255
            $newCategory = new GradebookCategory();
256
            $newCategory->setTitle($category->getTitle())
257
                ->setDescription($category->getDescription())
258
                ->setWeight($category->getWeight())
259
                ->setVisible($category->getVisible())
260
                ->setCertifMinScore($category->getCertifMinScore())
261
                ->setGenerateCertificates($category->getGenerateCertificates())
262
                ->setIsRequirement($category->getIsRequirement())
263
                ->setCourse($category->getCourse())
264
                ->setSession($this->entityManager->getReference(Session::class, $newSessionId))
265
                ->setParent($category->getParent());
266
267
            $this->entityManager->persist($newCategory);
268
            $this->entityManager->flush();
269
270
            if ($debug) {
271
                $output->writeln(sprintf('Created new category ID %d for session ID %d.', $newCategory->getId(), $newSessionId));
272
            }
273
274
            // Copy links
275
            $links = $this->entityManager->getRepository(GradebookLink::class)
276
                ->findBy(['category' => $category->getId()]);
277
278
            foreach ($links as $link) {
279
                $newLink = clone $link;
280
                $newLink->setCategory($newCategory);
281
                $this->entityManager->persist($newLink);
282
            }
283
284
            // Copy evaluations
285
            $evaluations = $this->entityManager->getRepository(GradebookEvaluation::class)
286
                ->findBy(['category' => $category->getId()]);
287
288
            foreach ($evaluations as $evaluation) {
289
                $newEvaluation = clone $evaluation;
290
                $newEvaluation->setCategory($newCategory);
291
                $this->entityManager->persist($newEvaluation);
292
            }
293
294
            $this->entityManager->flush();
295
296
            if ($debug) {
297
                $output->writeln(sprintf('Copied links and evaluations for category ID %d to new category ID %d.', $category->getId(), $newCategory->getId()));
298
            }
299
        }
300
    }
301
}
302