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

LpProgressReminderCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 11
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\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