Passed
Push — dependabot/npm_and_yarn/cross-... ( a16049...ad80c0 )
by
unknown
16:45 queued 07:24
created

LpProgressReminderCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 10
dl 0
loc 13
rs 10

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
            $sessionId = $checkSession ? ($user['sessionId'] ?? 0) : 0;
131
132
            if ($lpId === null) {
133
                foreach ($lpItems as $lpId => $nbDaysForLpCompletion) {
134
                    $this->sendReminderIfNeeded(
135
                        $userId,
136
                        $courseTitle,
137
                        $courseId,
138
                        $sessionId,
139
                        (int) $nbDaysForLpCompletion,
140
                        $debugMode,
141
                        $lpId,
142
                        $progress
143
                    );
144
                }
145
            } else {
146
                $nbDaysForLpCompletion = (int) ($lpItems[$lpId] ?? self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION);
147
                $this->sendReminderIfNeeded(
148
                    $userId,
149
                    $courseTitle,
150
                    $courseId,
151
                    $sessionId,
152
                    $nbDaysForLpCompletion,
153
                    $debugMode,
154
                    $lpId,
155
                    $progress
156
                );
157
            }
158
        }
159
    }
160
161
    /**
162
     * Sends a progress reminder to a user if the conditions for reminder timing are met,
163
     * based on their registration date and learning path completion criteria.
164
     */
165
    private function sendReminderIfNeeded(
166
        int $userId,
167
        string $courseTitle,
168
        int $courseId,
169
        int $sessionId,
170
        int $nbDaysForLpCompletion,
171
        bool $debugMode,
172
        ?int $lpId,
173
        int $progress
174
    ): void {
175
        $registrationDate = $this->trackEDefaultRepository->getUserCourseRegistrationAt($courseId, $userId, $sessionId);
176
        if (!$registrationDate) {
0 ignored issues
show
introduced by
$registrationDate is of type DateTime, thus it always evaluated to true.
Loading history...
177
            if ($debugMode) {
178
                echo "No registration date found for user $userId in course $courseId (session ID: $sessionId).\n";
179
            }
180
            return;
181
        }
182
183
        if ($debugMode) {
184
            $sessionInfo = $sessionId > 0 ? "in session ID $sessionId" : "without a session";
185
            echo "Registration date: {$registrationDate->format('Y-m-d H:i:s')}, Days for completion: $nbDaysForLpCompletion, LP ID: {$lpId}, $sessionInfo\n";
186
        }
187
188
        if ($this->isTimeToRemindUser($registrationDate, $nbDaysForLpCompletion)) {
189
            $nbRemind = $this->getNbReminder($registrationDate, $nbDaysForLpCompletion);
190
191
            if ($debugMode) {
192
                echo "Sending reminder to user $userId for course $courseTitle (LP ID: {$lpId})\n";
193
            }
194
            $this->logReminderSent($userId, $courseTitle, $nbRemind, $debugMode, $lpId, $sessionId);
195
            $this->sendLpReminder($userId, $courseTitle, $progress, $registrationDate, $nbRemind);
196
        }
197
    }
198
199
    /**
200
     * Logs the reminder details if debug mode is enabled.
201
     */
202
    private function logReminderSent(int $userId, string $courseTitle, int $nbRemind, bool $debugMode, int $lpId, int $sessionId = 0): void
203
    {
204
        if ($debugMode) {
205
            $sessionInfo = $sessionId > 0 ? sprintf("in session ID %d", $sessionId) : "without a session";
206
            echo sprintf(
207
                "Reminder number %d sent to user ID %d for the course %s (LP ID: %d) %s.\n",
208
                $nbRemind,
209
                $userId,
210
                $courseTitle,
211
                $lpId,
212
                $sessionInfo
213
            );
214
        }
215
    }
216
217
    /**
218
     * Calculates the number of reminders to be sent based on registration date and days for completion.
219
     */
220
    private function getNbReminder(DateTime $registrationDate, int $nbDaysForLpCompletion): int
221
    {
222
        $reminderStartDate = (clone $registrationDate)->modify("+$nbDaysForLpCompletion days");
223
        $currentDate = new DateTime('now', new DateTimeZone('UTC'));
224
        $reminderStartDate->setTime(0, 0, 0);
225
        $currentDate->setTime(0, 0, 0);
226
227
        $interval = $reminderStartDate->diff($currentDate);
228
        $diffDays = (int) $interval->format('%a');
229
230
        return (int) floor($diffDays / self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION) + 1;
231
    }
232
233
    /**
234
     * Checks if it is time to remind the user based on their registration date and LP completion days.
235
     */
236
    private function isTimeToRemindUser(DateTime $registrationDate, int $nbDaysForLpCompletion): bool
237
    {
238
        $reminderStartDate = (clone $registrationDate)->modify("+$nbDaysForLpCompletion days");
239
        $reminderStartDate->setTime(0, 0, 0);
240
241
        $currentDate = new DateTime('now', new DateTimeZone('UTC'));
242
        $currentDate->setTime(0, 0, 0);
243
244
        $interval = $reminderStartDate->diff($currentDate);
245
        $diffDays = (int) $interval->format('%a');
246
247
        return ($diffDays >= self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION &&
248
            $diffDays % self::NUMBER_OF_DAYS_TO_RESEND_NOTIFICATION === 0) || $diffDays === 0;
249
    }
250
251
252
    /**
253
     * Sends a reminder email to the user regarding their LP progress.
254
     */
255
    private function sendLpReminder(int $toUserId, string $courseName, int $lpProgress, DateTime $registrationDate, int $nbRemind): bool
256
    {
257
        $user = $this->userRepository->find($toUserId);
258
        if (!$user) {
259
            throw new \Exception("User not found");
260
        }
261
262
        $platformUrl = $this->urlGenerator->generate('index', [], UrlGeneratorInterface::ABSOLUTE_URL);
263
        $recoverPasswordUrl = $platformUrl.'/main/auth/lostPassword.php';
264
265
        $trainingCenterName = 'Your Training Center';
266
        $trainers = 'Trainer Name';
267
268
        $hello = $this->translator->trans("Hello %s");
269
        $youAreRegCourse = $this->translator->trans("You are registered in the training %s since the %s");
270
        $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.");
271
        $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.");
272
        $lpRemindFooter = $this->translator->trans("The training center<p>%s</p>Trainers:<br/>%s");
273
274
        $hello = sprintf($hello, $user->getFullName());
275
        $youAreRegCourse = sprintf($youAreRegCourse, $courseName, $registrationDate->format('Y-m-d'));
276
        $thisMessageIsAbout = sprintf($thisMessageIsAbout, $lpProgress);
277
        $stepsToRemind = sprintf($stepsToRemind, $platformUrl, $user->getUsername(), $recoverPasswordUrl);
278
        $lpRemindFooter = sprintf($lpRemindFooter, $trainingCenterName, $trainers);
279
280
        $messageContent = $this->twig->render('@ChamiloCore/Mailer/Legacy/lp_progress_reminder_body.html.twig', [
281
            'HelloX' => $hello,
282
            'YouAreRegCourseXFromDateX' => $youAreRegCourse,
283
            'ThisMessageIsAboutX' => $thisMessageIsAbout,
284
            'StepsToRemindX' => $stepsToRemind,
285
            'LpRemindFooterX' => $lpRemindFooter,
286
        ]);
287
288
        try {
289
            $this->messageHelper->sendMessageSimple(
290
                $toUserId,
291
                sprintf("Reminder number %d for the course %s", $nbRemind, $courseName),
292
                $messageContent
293
            );
294
295
            return true;
296
        } catch (\Exception $e) {
297
            throw new \Exception('Error sending reminder: ' . $e->getMessage());
298
        }
299
    }
300
}
301