Passed
Pull Request — master (#5832)
by
unknown
08:39
created

LpProgressReminderCommand::sendLpReminder()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 43
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 31
c 2
b 0
f 0
nc 3
nop 5
dl 0
loc 43
rs 9.424
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 (!isset($lpItems[$lpId])) {
131
                continue;
132
            }
133
134
            $sessionId = $checkSession && isset($user['sessionId']) && $user['sessionId'] > 0 ? $user['sessionId'] : 0;
135
136
            $registrationDate = $this->trackEDefaultRepository->getUserCourseRegistrationAt($courseId, $userId, $sessionId);
137
            $nbDaysForLpCompletion = (int) $lpItems[$lpId];
138
139
            if ($registrationDate) {
140
                if ($debugMode) {
141
                    $sessionInfo = $sessionId > 0 ? "in session ID $sessionId" : "without a session";
142
                    echo "Registration date: {$registrationDate->format('Y-m-d H:i:s')}, Days for completion: $nbDaysForLpCompletion, LP ID: $lpId, $sessionInfo\n";
143
                }
144
                if ($this->isTimeToRemindUser($registrationDate, $nbDaysForLpCompletion)) {
145
                    $nbRemind = $this->getNbReminder($registrationDate, $nbDaysForLpCompletion);
146
                    if ($debugMode) {
147
                        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...
148
                        $this->logReminderSent($userId, $courseTitle, $nbRemind, $debugMode, $lpId, $sessionId);
149
                    }
150
                    $this->sendLpReminder($userId, $courseTitle, $progress, $registrationDate, $nbRemind);
151
                }
152
            }
153
        }
154
    }
155
156
    /**
157
     * Logs the reminder details if debug mode is enabled.
158
     */
159
    private function logReminderSent(int $userId, string $courseTitle, int $nbRemind, bool $debugMode, int $lpId, int $sessionId = 0): void
160
    {
161
        if ($debugMode) {
162
            $sessionInfo = $sessionId > 0 ? sprintf("in session ID %d", $sessionId) : "without a session";
163
            echo sprintf(
164
                "Reminder number %d sent to user ID %d for the course %s (LP ID: %d) %s.\n",
165
                $nbRemind,
166
                $userId,
167
                $courseTitle,
168
                $lpId,
169
                $sessionInfo
170
            );
171
        }
172
    }
173
174
    /**
175
     * Calculates the number of reminders to be sent based on registration date and days for completion.
176
     */
177
    private function getNbReminder(DateTime $registrationDate, int $nbDaysForLpCompletion): int
178
    {
179
        $date1 = clone $registrationDate;
180
        $date1->modify("+$nbDaysForLpCompletion day");
181
182
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
183
184
        $interval = $date1->diff($date2);
185
        $diffDays = (int) $interval->format('%a');
186
187
        return (int) ceil($diffDays / self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION) + 1;
188
    }
189
190
    /**
191
     * Checks if it is time to remind the user based on their registration date and LP completion days.
192
     */
193
    private function isTimeToRemindUser(DateTime $registrationDate, int $nbDaysForLpCompletion): bool
194
    {
195
        $date1 = clone $registrationDate;
196
        $date1->modify("+$nbDaysForLpCompletion day");
197
        $startDate = $date1->format('Y-m-d');
198
199
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
200
        $now = $date2->format('Y-m-d');
201
202
        if ($startDate < $now) {
203
            $interval = $date1->diff($date2);
204
            $diffDays = (int) $interval->format('%a');
205
            return (0 === $diffDays % self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION);
206
        }
207
208
        return $startDate === $now;
209
    }
210
211
    /**
212
     * Sends a reminder email to the user regarding their LP progress.
213
     */
214
    private function sendLpReminder(int $toUserId, string $courseName, int $lpProgress, DateTime $registrationDate, int $nbRemind): bool
215
    {
216
        $user = $this->userRepository->find($toUserId);
217
        if (!$user) {
218
            throw new \Exception("User not found");
219
        }
220
221
        $platformUrl = $this->urlGenerator->generate('index', [], UrlGeneratorInterface::ABSOLUTE_URL);
222
        $recoverPasswordUrl = $platformUrl.'/main/auth/lostPassword.php';
223
224
        $trainingCenterName = 'Your Training Center';
225
        $trainers = 'Trainer Name';
226
227
        $hello = $this->translator->trans("Hello %s");
228
        $youAreRegCourse = $this->translator->trans("You are registered in the training %s since the %s");
229
        $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.");
230
        $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.");
231
        $lpRemindFooter = $this->translator->trans("The training center<p>%s</p>Trainers:<br/>%s");
232
233
        $hello = sprintf($hello, $user->getFullName());
234
        $youAreRegCourse = sprintf($youAreRegCourse, $courseName, $registrationDate->format('Y-m-d'));
235
        $thisMessageIsAbout = sprintf($thisMessageIsAbout, $lpProgress);
236
        $stepsToRemind = sprintf($stepsToRemind, $platformUrl, $user->getUsername(), $recoverPasswordUrl);
237
        $lpRemindFooter = sprintf($lpRemindFooter, $trainingCenterName, $trainers);
238
239
        $messageContent = $this->twig->render('@ChamiloCore/Mailer/Legacy/lp_progress_reminder_body.html.twig', [
240
            'HelloX' => $hello,
241
            'YouAreRegCourseXFromDateX' => $youAreRegCourse,
242
            'ThisMessageIsAboutX' => $thisMessageIsAbout,
243
            'StepsToRemindX' => $stepsToRemind,
244
            'LpRemindFooterX' => $lpRemindFooter,
245
        ]);
246
247
        try {
248
            $this->messageHelper->sendMessageSimple(
249
                $toUserId,
250
                sprintf("Reminder number %d for the course %s", $nbRemind, $courseName),
251
                $messageContent
252
            );
253
254
            return true;
255
        } catch (\Exception $e) {
256
            throw new \Exception('Error sending reminder: ' . $e->getMessage());
257
        }
258
    }
259
}
260