Passed
Pull Request — master (#5832)
by
unknown
09:28
created

LpProgressReminderCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 9
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\Repository\CourseRelUserRepository;
10
use Chamilo\CoreBundle\Repository\ExtraFieldValuesRepository;
11
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
12
use Chamilo\CoreBundle\Repository\Node\UserRepository;
13
use Chamilo\CoreBundle\Repository\SessionRelCourseRelUserRepository;
14
use Chamilo\CoreBundle\Repository\TrackEDefaultRepository;
15
use Chamilo\CoreBundle\ServiceHelper\MessageHelper;
16
use DateTime;
17
use DateTimeZone;
18
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
19
use Symfony\Contracts\Translation\TranslatorInterface;
20
use Twig\Environment;
21
use Symfony\Component\Console\Command\Command;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
26
class LpProgressReminderCommand extends Command
27
{
28
    protected static $defaultName = 'app:lp-progress-reminder';
29
30
    private const NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION = 3;
31
32
    public function __construct(
33
        private readonly CourseRepository $courseRepository,
34
        private readonly CourseRelUserRepository $courseRelUserRepository,
35
        private readonly SessionRelCourseRelUserRepository $sessionRelCourseRelUserRepository,
36
        private readonly ExtraFieldValuesRepository $extraFieldValuesRepository,
37
        private readonly TrackEDefaultRepository $trackEDefaultRepository,
38
        private readonly UserRepository $userRepository,
39
        private readonly Environment $twig,
40
        private readonly TranslatorInterface $translator,
41
        private readonly MessageHelper $messageHelper,
42
        private readonly UrlGeneratorInterface $urlGenerator
43
    ) {
44
        parent::__construct();
45
    }
46
47
48
    protected function configure()
49
    {
50
        $this
51
            ->setDescription('Send LP progress reminders to users based on "number_of_days_for_completion".')
52
            ->addOption(
53
                'debug',
54
                null,
55
                InputOption::VALUE_NONE,
56
                'If set, will output detailed debug information'
57
            );
58
    }
59
60
    protected function execute(InputInterface $input, OutputInterface $output): int
61
    {
62
        $debugMode = $input->getOption('debug');
63
        $output->writeln('Starting the LP progress reminder process...');
64
65
        // Retrieve LPs with completion days
66
        $lpItems = $this->extraFieldValuesRepository->getLpIdWithDaysForCompletion();
67
        if ($debugMode && !empty($lpItems)) {
68
            $output->writeln('LP Items retrieved: ' . print_r($lpItems, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($lpItems, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
            $output->writeln('LP Items retrieved: ' . /** @scrutinizer ignore-type */ print_r($lpItems, true));
Loading history...
69
        }
70
71
        if (empty($lpItems)) {
72
            $output->writeln('No learning paths with days for completion found.');
73
            return Command::SUCCESS;
74
        }
75
76
        $lpMap = [];
77
        foreach ($lpItems as $lpItem) {
78
            $lpMap[$lpItem['lp_id']] = $lpItem['ndays'];
79
        }
80
        $lpIds = array_keys($lpMap);
81
82
        // Retrieve all courses from the CourseRepository
83
        $courses = $this->courseRepository->findAll();
84
        if ($debugMode && !empty($courses)) {
85
            $output->writeln('Courses retrieved: ' . count($courses));
86
        }
87
88
        foreach ($courses as $course) {
89
            $courseId = $course->getId();
90
91
            // Retrieve users for the course (without session)
92
            $courseUsers = $this->courseRelUserRepository->getCourseUsers($courseId, $lpIds);
93
            // Retrieve users for the course session
94
            $sessionCourseUsers = $this->sessionRelCourseRelUserRepository->getSessionCourseUsers($courseId, $lpIds);
95
96
            if ($debugMode && (!empty($courseUsers) || !empty($sessionCourseUsers))) {
97
                $output->writeln('Processing course ID: ' . $courseId);
98
                if (!empty($courseUsers)) {
99
                    $output->writeln('Course users retrieved: ' . count($courseUsers));
100
                    //$output->writeln('Course retrieved: ' . print_r($courseUsers, true));
101
                }
102
                if (!empty($sessionCourseUsers)) {
103
                    $output->writeln('Session users retrieved: ' . count($sessionCourseUsers));
104
                    //$output->writeln('Session retrieved: ' . print_r($sessionCourseUsers, true));
105
                }
106
            }
107
108
            // Process users from the main course (sessionId = 0 or null)
109
            $this->processCourseUsers($courseUsers, $lpMap, $courseId, $debugMode);
110
111
            // Process users from the course session (sessionId > 0)
112
            $this->processCourseUsers($sessionCourseUsers, $lpMap, $courseId, $debugMode, true);
113
        }
114
115
        $output->writeln('LP progress reminder process finished.');
116
        return Command::SUCCESS;
117
    }
118
119
    /**
120
     * Processes users from a course or session to check if a reminder needs to be sent.
121
     */
122
    private function processCourseUsers(array $users, array $lpItems, int $courseId, bool $debugMode = false, bool $checkSession = false): void
123
    {
124
        foreach ($users as $user) {
125
            $userId = $user['userId'];
126
            $courseTitle = $user['courseTitle'];
127
            $lpId = $user['lpId'];
128
            $progress = (int) $user['progress'];
129
130
            if ($lpId === null) {
131
                foreach ($lpItems as $lpId => $nbDaysForLpCompletion) {
132
                    $this->sendReminderIfNeeded(
133
                        $userId,
134
                        $courseTitle,
135
                        $courseId,
136
                        $sessionId ?? 0,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionId seems to never exist and therefore isset should always be false.
Loading history...
137
                        (int) $nbDaysForLpCompletion,
138
                        $debugMode,
139
                        $lpId,
140
                        $progress
141
                    );
142
                }
143
            } else {
144
                $nbDaysForLpCompletion = (int) ($lpItems[$lpId] ?? self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION);
145
                $this->sendReminderIfNeeded(
146
                    $userId,
147
                    $courseTitle,
148
                    $courseId,
149
                    $sessionId ?? 0,
150
                    $nbDaysForLpCompletion,
151
                    $debugMode,
152
                    $lpId,
153
                    $progress
154
                );
155
            }
156
        }
157
    }
158
159
    /**
160
     * Sends a progress reminder to a user if the conditions for reminder timing are met,
161
     * based on their registration date and learning path completion criteria.
162
     */
163
    private function sendReminderIfNeeded(
164
        int $userId,
165
        string $courseTitle,
166
        int $courseId,
167
        int $sessionId,
168
        int $nbDaysForLpCompletion,
169
        bool $debugMode,
170
        ?int $lpId,
171
        int $progress
172
    ): void {
173
        $registrationDate = $this->trackEDefaultRepository->getUserCourseRegistrationAt($courseId, $userId, $sessionId);
174
        if ($registrationDate) {
175
            if ($debugMode) {
176
                $sessionInfo = $sessionId > 0 ? "in session ID $sessionId" : "without a session";
177
                echo "Registration date: {$registrationDate->format('Y-m-d H:i:s')}, Days for completion: $nbDaysForLpCompletion, LP ID: {$lpId}, $sessionInfo\n";
178
            }
179
            if ($this->isTimeToRemindUser($registrationDate, $nbDaysForLpCompletion)) {
180
                $nbRemind = $this->getNbReminder($registrationDate, $nbDaysForLpCompletion);
181
                if ($debugMode) {
182
                    echo "Sending reminder to user $userId for course $courseTitle (LP ID: {$lpId}) $sessionInfo\n";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionInfo does not seem to be defined for all execution paths leading up to this point.
Loading history...
183
                    $this->logReminderSent($userId, $courseTitle, $nbRemind, $debugMode, $lpId, $sessionId);
184
                }
185
                $this->sendLpReminder($userId, $courseTitle, $progress, $registrationDate, $nbRemind);
186
            }
187
        }
188
    }
189
190
    /**
191
     * Logs the reminder details if debug mode is enabled.
192
     */
193
    private function logReminderSent(int $userId, string $courseTitle, int $nbRemind, bool $debugMode, int $lpId, int $sessionId = 0): void
194
    {
195
        if ($debugMode) {
196
            $sessionInfo = $sessionId > 0 ? sprintf("in session ID %d", $sessionId) : "without a session";
197
            echo sprintf(
198
                "Reminder number %d sent to user ID %d for the course %s (LP ID: %d) %s.\n",
199
                $nbRemind,
200
                $userId,
201
                $courseTitle,
202
                $lpId,
203
                $sessionInfo
204
            );
205
        }
206
    }
207
208
    /**
209
     * Calculates the number of reminders to be sent based on registration date and days for completion.
210
     */
211
    private function getNbReminder(DateTime $registrationDate, int $nbDaysForLpCompletion): int
212
    {
213
        $date1 = clone $registrationDate;
214
        $date1->modify("+$nbDaysForLpCompletion day");
215
216
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
217
218
        $interval = $date1->diff($date2);
219
        $diffDays = (int) $interval->format('%a');
220
221
        return (int) ceil($diffDays / self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION) + 1;
222
    }
223
224
    /**
225
     * Checks if it is time to remind the user based on their registration date and LP completion days.
226
     */
227
    private function isTimeToRemindUser(DateTime $registrationDate, int $nbDaysForLpCompletion): bool
228
    {
229
        $date1 = clone $registrationDate;
230
        $date1->modify("+$nbDaysForLpCompletion day");
231
        $startDate = $date1->format('Y-m-d');
232
233
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
234
        $now = $date2->format('Y-m-d');
235
236
        if ($startDate < $now) {
237
            $interval = $date1->diff($date2);
238
            $diffDays = (int) $interval->format('%a');
239
            return (0 === $diffDays % self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION);
240
        }
241
242
        return $startDate === $now;
243
    }
244
245
    /**
246
     * Sends a reminder email to the user regarding their LP progress.
247
     */
248
    private function sendLpReminder(int $toUserId, string $courseName, int $lpProgress, DateTime $registrationDate, int $nbRemind): bool
249
    {
250
        $user = $this->userRepository->find($toUserId);
251
        if (!$user) {
252
            throw new \Exception("User not found");
253
        }
254
255
        $platformUrl = $this->urlGenerator->generate('index', [], UrlGeneratorInterface::ABSOLUTE_URL);
256
        $recoverPasswordUrl = $platformUrl.'/main/auth/lostPassword.php';
257
258
        $trainingCenterName = 'Your Training Center';
259
        $trainers = 'Trainer Name';
260
261
        $hello = $this->translator->trans("Hello %s");
262
        $youAreRegCourse = $this->translator->trans("You are registered in the training %s since the %s");
263
        $thisMessageIsAbout = $this->translator->trans("You are receiving this message because you have completed a learning path with a %s progress of your training.<br/>Your progress must be 100 to consider that your training was carried out.<br/>If you have the slightest problem, you should contact with your trainer.");
264
        $stepsToRemind = $this->translator->trans("As a reminder, to access the training platform:<br/>1. Connect to the platform at the address: %s <br/>2. Then enter: <br/>Your username: %s <br/>Your password: This was emailed to you.<br/>if you forgot it and can't find it, you can retrieve it by going to %s <br/><br/>Thank you for doing what is necessary.");
265
        $lpRemindFooter = $this->translator->trans("The training center<p>%s</p>Trainers:<br/>%s");
266
267
        $hello = sprintf($hello, $user->getFullName());
268
        $youAreRegCourse = sprintf($youAreRegCourse, $courseName, $registrationDate->format('Y-m-d'));
269
        $thisMessageIsAbout = sprintf($thisMessageIsAbout, $lpProgress);
270
        $stepsToRemind = sprintf($stepsToRemind, $platformUrl, $user->getUsername(), $recoverPasswordUrl);
271
        $lpRemindFooter = sprintf($lpRemindFooter, $trainingCenterName, $trainers);
272
273
        $messageContent = $this->twig->render('@ChamiloCore/Mailer/Legacy/lp_progress_reminder_body.html.twig', [
274
            'HelloX' => $hello,
275
            'YouAreRegCourseXFromDateX' => $youAreRegCourse,
276
            'ThisMessageIsAboutX' => $thisMessageIsAbout,
277
            'StepsToRemindX' => $stepsToRemind,
278
            'LpRemindFooterX' => $lpRemindFooter,
279
        ]);
280
281
        try {
282
            $this->messageHelper->sendMessageSimple(
283
                $toUserId,
284
                sprintf("Reminder number %d for the course %s", $nbRemind, $courseName),
285
                $messageContent
286
            );
287
288
            return true;
289
        } catch (\Exception $e) {
290
            throw new \Exception('Error sending reminder: ' . $e->getMessage());
291
        }
292
    }
293
}
294