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

LpProgressReminderCommand::isTimeToRemindUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 10
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 16
rs 9.9332
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) {
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
        // Retrieve all courses from the CourseRepository
74
        $courses = $this->courseRepository->findAll();
75
        if ($debugMode) {
76
            $output->writeln('Courses retrieved: ' . count($courses));
77
        }
78
79
        foreach ($courses as $course) {
80
            $courseId = $course->getId();
81
82
            if ($debugMode) {
83
                $output->writeln('Processing course ID: ' . $courseId);
84
            }
85
86
            // Retrieve users for the course (without session)
87
            $courseUsers = $this->courseRelUserRepository->getCourseUsers($courseId, array_keys($lpItems));
88
            // Retrieve users for the course session
89
            $sessionCourseUsers = $this->courseRelUserRepository->getCourseUsers($courseId, array_keys($lpItems), true);
90
91
            if ($debugMode) {
92
                $output->writeln('Course users retrieved: ' . count($courseUsers));
93
                $output->writeln('Session users retrieved: ' . count($sessionCourseUsers));
94
            }
95
96
            // Process users from the main course (sessionId = 0 or null)
97
            $this->processCourseUsers($courseUsers, $lpItems, $courseId);
98
99
            // Process users from the course session (sessionId > 0)
100
            $this->processCourseUsers($sessionCourseUsers, $lpItems, $courseId, true);
101
        }
102
103
        $output->writeln('LP progress reminder process finished.');
104
        return Command::SUCCESS;
105
    }
106
107
    /**
108
     * Processes users from a course or session to check if a reminder needs to be sent.
109
     */
110
    private function processCourseUsers(array $users, array $lpItems, int $courseId, bool $checkSession = false): void
111
    {
112
        foreach ($users as $user) {
113
            $userId = $user['userId'];
114
            $courseTitle = $user['courseTitle'];
115
            $lpId = $user['lpId'];
116
            $progress = (int) $user['progress'];
117
            $nbDaysForLpCompletion = (int) $lpItems[$lpId]['ndays'];
118
119
            if ($checkSession && isset($user['session_id']) && $user['session_id'] > 0) {
120
                $sessionId = $user['session_id'];
121
            } else {
122
                $sessionId = 0;
123
            }
124
125
            $registrationDate = $this->trackEDefaultRepository->getUserCourseRegistrationAt($courseId, $userId, $sessionId);
126
127
            if ($registrationDate && $this->isTimeToRemindUser($registrationDate, $nbDaysForLpCompletion)) {
128
                $nbRemind = $this->getNbReminder($registrationDate, $nbDaysForLpCompletion);
129
                $this->sendLpReminder($userId, $courseTitle, $progress, $registrationDate, $nbRemind);
130
            }
131
        }
132
    }
133
134
    /**
135
     * Calculates the number of reminders to be sent based on registration date and days for completion.
136
     */
137
    private function getNbReminder(DateTime $registrationDate, int $nbDaysForLpCompletion): int
138
    {
139
        $date1 = clone $registrationDate;
140
        $date1->modify("+$nbDaysForLpCompletion day");
141
142
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
143
144
        $interval = $date1->diff($date2);
145
        $diffDays = (int) $interval->format('%a');
146
147
        return (int) ceil($diffDays / self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION) + 1;
148
    }
149
150
    /**
151
     * Checks if it is time to remind the user based on their registration date and LP completion days.
152
     */
153
    private function isTimeToRemindUser(DateTime $registrationDate, int $nbDaysForLpCompletion): bool
154
    {
155
        $date1 = clone $registrationDate;
156
        $date1->modify("+$nbDaysForLpCompletion day");
157
        $startDate = $date1->format('Y-m-d');
158
159
        $date2 = new DateTime('now', new DateTimeZone('UTC'));
160
        $now = $date2->format('Y-m-d');
161
162
        if ($startDate < $now) {
163
            $interval = $date1->diff($date2);
164
            $diffDays = (int) $interval->format('%a');
165
            return (0 === $diffDays % self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION);
166
        }
167
168
        return $startDate === $now;
169
    }
170
171
    /**
172
     * Sends a reminder email to the user regarding their LP progress.
173
     */
174
    private function sendLpReminder(int $toUserId, string $courseName, int $lpProgress, DateTime $registrationDate, int $nbRemind): bool
175
    {
176
        $user = $this->userRepository->find($toUserId);
177
        if (!$user) {
178
            throw new \Exception("User not found");
179
        }
180
181
        $hello = $this->translator->trans('HelloX');
182
        $youAreRegCourse = $this->translator->trans('YouAreRegCourseXFromDateX');
183
        $thisMessageIsAbout = $this->translator->trans('ThisMessageIsAboutX');
184
        $stepsToRemind = $this->translator->trans('StepsToRemindX');
185
        $lpRemindFooter = $this->translator->trans('LpRemindFooterX');
186
187
        $hello = sprintf($hello, $user->getFullName());
188
        $youAreRegCourse = sprintf($youAreRegCourse, $courseName, $registrationDate->format('Y-m-d'));
189
        $thisMessageIsAbout = sprintf($thisMessageIsAbout, $lpProgress);
190
        $stepsToRemind = sprintf($stepsToRemind, '', $user->getUsername(), '');
191
        $lpRemindFooter = sprintf($lpRemindFooter, '', 'm');
192
193
        $body = $this->twig->render('@ChamiloCore/Mailer/Legacy/lp_progress_reminder_body.html.twig', [
194
            'HelloX' => $hello,
195
            'YouAreRegCourseXFromDateX' => $youAreRegCourse,
196
            'ThisMessageIsAboutX' => $thisMessageIsAbout,
197
            'StepsToRemindX' => $stepsToRemind,
198
            'LpRemindFooterX' => $lpRemindFooter,
199
        ]);
200
201
        $email = (new Email())
202
            ->from('[email protected]')
203
            ->to($user->getEmail())
204
            ->subject(sprintf("Reminder number %d for the course %s", $nbRemind, $courseName))
205
            ->html($body);
206
207
        try {
208
            $this->mailer->send($email);
209
            return true;
210
        } catch (\Exception $e) {
211
            throw new \Exception('Error to send email: ' . $e->getMessage());
212
        }
213
    }
214
}
215