Passed
Pull Request — master (#5832)
by
unknown
12:26
created

LpProgressReminderCommand::getNbReminder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 11
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\TrackEDefaultRepository;
14
use DateTime;
15
use DateTimeZone;
16
use Symfony\Component\Mailer\MailerInterface;
17
use Symfony\Component\Mime\Email;
18
use Symfony\Contracts\Translation\TranslatorInterface;
19
use Twig\Environment;
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
25
class LpProgressReminderCommand extends Command
26
{
27
    protected static $defaultName = 'app:lp-progress-reminder';
28
29
    private const NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION = 3;
30
31
    public function __construct(
32
        private CourseRepository $courseRepository,
33
        private CourseRelUserRepository $courseRelUserRepository,
34
        private ExtraFieldValuesRepository $extraFieldValuesRepository,
35
        private TrackEDefaultRepository $trackEDefaultRepository,
36
        private UserRepository $userRepository,
37
        private MailerInterface $mailer,
38
        private Environment $twig,
39
        private TranslatorInterface $translator
40
    ) {
41
        parent::__construct();
42
    }
43
44
45
    protected function configure()
46
    {
47
        $this
48
            ->setDescription('Send LP progress reminders to users based on "number_of_days_for_completion".')
49
            ->addOption(
50
                'debug',
51
                null,
52
                InputOption::VALUE_NONE,
53
                'If set, will output detailed debug information'
54
            );
55
    }
56
57
    protected function execute(InputInterface $input, OutputInterface $output): int
58
    {
59
        $debugMode = $input->getOption('debug');
60
        $output->writeln('Starting the LP progress reminder process...');
61
62
        // Retrieve LPs with completion days
63
        $lpItems = $this->extraFieldValuesRepository->getLpIdWithDaysForCompletion();
64
        if ($debugMode && !empty($lpItems)) {
65
            $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

65
            $output->writeln('LP Items retrieved: ' . /** @scrutinizer ignore-type */ print_r($lpItems, true));
Loading history...
66
        }
67
68
        if (empty($lpItems)) {
69
            $output->writeln('No learning paths with days for completion found.');
70
            return Command::SUCCESS;
71
        }
72
73
        $lpMap = [];
74
        foreach ($lpItems as $lpItem) {
75
            $lpMap[$lpItem['lp_id']] = $lpItem['ndays'];
76
        }
77
        $lpIds = array_keys($lpMap);
78
79
        // Retrieve all courses from the CourseRepository
80
        $courses = $this->courseRepository->findAll();
81
        if ($debugMode && !empty($courses)) {
82
            $output->writeln('Courses retrieved: ' . count($courses));
83
        }
84
85
        foreach ($courses as $course) {
86
            $courseId = $course->getId();
87
88
            // Retrieve users for the course (without session)
89
            $courseUsers = $this->courseRelUserRepository->getCourseUsers($courseId, $lpIds);
90
            // Retrieve users for the course session
91
            $sessionCourseUsers = $this->courseRelUserRepository->getCourseUsers($courseId, $lpIds, true);
92
93
            if ($debugMode && (!empty($courseUsers) || !empty($sessionCourseUsers))) {
94
                $output->writeln('Processing course ID: ' . $courseId);
95
                if (!empty($courseUsers)) {
96
                    $output->writeln('Course users retrieved: ' . count($courseUsers));
97
                    $output->writeln('Course retrieved: ' . print_r($courseUsers, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($courseUsers, 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

97
                    $output->writeln('Course retrieved: ' . /** @scrutinizer ignore-type */ print_r($courseUsers, true));
Loading history...
98
                }
99
                if (!empty($sessionCourseUsers)) {
100
                    $output->writeln('Session users retrieved: ' . count($sessionCourseUsers));
101
                    $output->writeln('Session retrieved: ' . print_r($sessionCourseUsers, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($sessionCourseUsers, 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

101
                    $output->writeln('Session retrieved: ' . /** @scrutinizer ignore-type */ print_r($sessionCourseUsers, true));
Loading history...
102
                }
103
            }
104
105
            // Process users from the main course (sessionId = 0 or null)
106
            $this->processCourseUsers($courseUsers, $lpMap, $courseId, $debugMode);
107
108
            // Process users from the course session (sessionId > 0)
109
            $this->processCourseUsers($sessionCourseUsers, $lpMap, $courseId, $debugMode, true);
110
        }
111
112
        $output->writeln('LP progress reminder process finished.');
113
        return Command::SUCCESS;
114
    }
115
116
    /**
117
     * Processes users from a course or session to check if a reminder needs to be sent.
118
     */
119
    private function processCourseUsers(array $users, array $lpItems, int $courseId, bool $debugMode = false, bool $checkSession = false): void
120
    {
121
        foreach ($users as $user) {
122
            $userId = $user['userId'];
123
            $courseTitle = $user['courseTitle'];
124
            $lpId = $user['lpId'];
125
            $progress = (int) $user['progress'];
126
127
            if (!isset($lpItems[$lpId])) {
128
                continue;
129
            }
130
131
            $sessionId = 0;
132
            if ($checkSession && isset($user['sessionId']) && $user['sessionId'] > 0) {
133
                $sessionId = $user['sessionId'];
134
            }
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, $sessionId, $lpId);
149
                    }
150
                    $this->sendLpReminder($userId, $courseTitle, $progress, $registrationDate, $nbRemind);
151
                }
152
            } elseif ($debugMode) {
153
                $sessionInfo = $sessionId > 0 ? "in session ID $sessionId" : "without a session";
154
                echo "No registration date found for user $userId in course $courseTitle (LP ID: $lpId) $sessionInfo\n";
155
            }
156
        }
157
    }
158
159
    /**
160
     * Logs the reminder details if debug mode is enabled.
161
     */
162
    private function logReminderSent(int $userId, string $courseTitle, int $nbRemind, bool $debugMode, int $sessionId = 0, int $lpId): void
163
    {
164
        if ($debugMode) {
165
            $sessionInfo = $sessionId > 0 ? sprintf("in session ID %d", $sessionId) : "without a session";
166
            echo sprintf(
167
                "Reminder number %d sent to user ID %d for the course %s (LP ID: %d) %s.\n",
168
                $nbRemind,
169
                $userId,
170
                $courseTitle,
171
                $lpId,
172
                $sessionInfo
173
            );
174
        }
175
    }
176
177
    /**
178
     * Calculates the number of reminders to be sent based on registration date and days for completion.
179
     */
180
    private function getNbReminder(DateTime $registrationDate, int $nbDaysForLpCompletion): int
181
    {
182
        $date1 = clone $registrationDate;
183
        $date1->modify("+$nbDaysForLpCompletion day");
184
185
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
186
187
        $interval = $date1->diff($date2);
188
        $diffDays = (int) $interval->format('%a');
189
190
        return (int) ceil($diffDays / self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION) + 1;
191
    }
192
193
    /**
194
     * Checks if it is time to remind the user based on their registration date and LP completion days.
195
     */
196
    private function isTimeToRemindUser(DateTime $registrationDate, int $nbDaysForLpCompletion): bool
197
    {
198
        $date1 = clone $registrationDate;
199
        $date1->modify("+$nbDaysForLpCompletion day");
200
        $startDate = $date1->format('Y-m-d');
201
202
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
203
        $now = $date2->format('Y-m-d');
204
205
        if ($startDate < $now) {
206
            $interval = $date1->diff($date2);
207
            $diffDays = (int) $interval->format('%a');
208
            return (0 === $diffDays % self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION);
209
        }
210
211
        return $startDate === $now;
212
    }
213
214
    /**
215
     * Sends a reminder email to the user regarding their LP progress.
216
     */
217
    private function sendLpReminder(int $toUserId, string $courseName, int $lpProgress, DateTime $registrationDate, int $nbRemind): bool
218
    {
219
        $user = $this->userRepository->find($toUserId);
220
        if (!$user) {
221
            throw new \Exception("User not found");
222
        }
223
224
        $hello = $this->translator->trans('HelloX');
225
        $youAreRegCourse = $this->translator->trans('YouAreRegCourseXFromDateX');
226
        $thisMessageIsAbout = $this->translator->trans('ThisMessageIsAboutX');
227
        $stepsToRemind = $this->translator->trans('StepsToRemindX');
228
        $lpRemindFooter = $this->translator->trans('LpRemindFooterX');
229
230
        $hello = sprintf($hello, $user->getFullName());
231
        $youAreRegCourse = sprintf($youAreRegCourse, $courseName, $registrationDate->format('Y-m-d'));
232
        $thisMessageIsAbout = sprintf($thisMessageIsAbout, $lpProgress);
233
        $stepsToRemind = sprintf($stepsToRemind, '', $user->getUsername(), '');
234
        $lpRemindFooter = sprintf($lpRemindFooter, '', 'm');
235
236
        $body = $this->twig->render('@ChamiloCore/Mailer/Legacy/lp_progress_reminder_body.html.twig', [
237
            'HelloX' => $hello,
238
            'YouAreRegCourseXFromDateX' => $youAreRegCourse,
239
            'ThisMessageIsAboutX' => $thisMessageIsAbout,
240
            'StepsToRemindX' => $stepsToRemind,
241
            'LpRemindFooterX' => $lpRemindFooter,
242
        ]);
243
244
        $email = (new Email())
245
            ->from('[email protected]')
246
            ->to($user->getEmail())
247
            ->subject(sprintf("Reminder number %d for the course %s", $nbRemind, $courseName))
248
            ->html($body);
249
250
        try {
251
            $this->mailer->send($email);
252
            return true;
253
        } catch (\Exception $e) {
254
            throw new \Exception('Error to send email: ' . $e->getMessage());
255
        }
256
    }
257
}
258