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

LpProgressReminderCommand::sendReminderIfNeeded()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
c 0
b 0
f 0
nc 10
nop 8
dl 0
loc 23
rs 9.2222

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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