Passed
Push — dependabot/npm_and_yarn/microm... ( e84ba6...f2f212 )
by
unknown
10:03
created

ScheduledAnnouncementService::getUserProgress()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 4
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 7
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Service;
8
9
use Chamilo\CoreBundle\Entity\Session;
10
use Chamilo\CoreBundle\Entity\User;
11
use Chamilo\CoreBundle\Repository\Node\AccessUrlRepository;
12
use Chamilo\CoreBundle\Repository\ScheduledAnnouncementRepository;
13
use Chamilo\CoreBundle\Repository\SessionRepository;
14
use Chamilo\CoreBundle\ServiceHelper\MessageHelper;
15
use Chamilo\CoreBundle\Settings\SettingsManager;
16
use Chamilo\CoreBundle\Entity\Course;
17
use Doctrine\ORM\EntityManager;
18
use ExtraField;
19
use ExtraFieldValue;
20
use Symfony\Contracts\Translation\TranslatorInterface;
21
use Tracking;
22
23
class ScheduledAnnouncementService
24
{
25
    public function __construct(
26
        private readonly ScheduledAnnouncementRepository $announcementRepository,
27
        private readonly AccessUrlRepository $accessUrlRepository,
28
        private readonly EntityManager $em,
29
        private readonly SettingsManager $settingsManager,
30
        private readonly SessionRepository $sessionRepository,
31
        private readonly MessageHelper $messageHelper,
32
        private readonly TranslatorInterface $translator
33
    ) {}
34
35
    /**
36
     * Sends pending announcements to users and coaches.
37
     */
38
    public function sendPendingMessages(int $urlId, bool $debug = false): int
39
    {
40
        if (!$this->allowed()) {
41
            if ($debug) {
42
                error_log("Announcements not allowed.");
43
            }
44
            return 0;
45
        }
46
47
        $messagesSent = 0;
48
        $now = new \DateTime();
49
50
        if ($debug) {
51
            error_log("Current time: " . $now->format('Y-m-d H:i:s'));
52
        }
53
54
        $announcements = $this->announcementRepository->findBy(['sent' => false]);
55
56
        if ($debug) {
57
            error_log(count($announcements) . " pending announcements found.");
58
        }
59
60
        $extraField = new ExtraField('user');
61
        $extraFields = $extraField->get_all(['filter = ? AND visible_to_self = ?' => [1, 1]]);
62
63
        foreach ($announcements as $announcement) {
64
            if (!$announcement->isSent() && $announcement->getDate() < $now) {
65
                if ($debug) {
66
                    error_log("Processing announcement ID: " . $announcement->getId());
67
                }
68
69
                $sessionId = $announcement->getSessionId();
70
                $session = $this->sessionRepository->find($sessionId);
71
72
                if (!$session) {
73
                    if ($debug) {
74
                        error_log("Session not found for session ID: $sessionId");
75
                    }
76
                    continue;
77
                }
78
79
                $accessUrl = $this->accessUrlRepository->find($urlId);
80
                $sessionRelUsers = $this->sessionRepository->getUsersByAccessUrl($session, $accessUrl);
81
                $generalCoaches = $session->getGeneralCoaches();
82
83
                if (empty($sessionRelUsers) || $generalCoaches->count() === 0) {
84
                    if ($debug) {
85
                        error_log("No users or general coaches found for session ID: $sessionId");
86
                    }
87
                    continue;
88
                }
89
90
                $coachId = $generalCoaches->first()->getId();
91
                if ($debug) {
92
                    error_log("Coach ID: $coachId");
93
                }
94
95
                $coachList = [];
96
97
                $sendToCoaches = $this->shouldSendToCoaches($announcement->getId());
98
99
                $courseList = $session->getCourses();
100
101
                if ($debug) {
102
                    error_log("Number of courses in session: " . count($courseList));
103
                    foreach ($courseList as $sessionRelCourse) {
104
                        $course = $sessionRelCourse->getCourse();
105
                        error_log("Course ID: " . $course->getId() . ", Course Title: " . $course->getTitle());
106
                    }
107
                }
108
109
                if ($sendToCoaches) {
110
                    foreach ($courseList as $course) {
111
                        $coaches = $session->getGeneralCoaches();
112
                        $coachList = array_merge($coachList, $coaches->toArray());
113
                    }
114
                    $coachList = array_unique($coachList);
115
                }
116
117
                $announcement->setSent(true);
118
                $this->em->persist($announcement);
119
                $this->em->flush();
120
121
                $attachments = '';
122
                $subject = $announcement->getSubject();
123
124
                foreach ($sessionRelUsers as $sessionRelUser) {
125
                    $user = $sessionRelUser->getUser();
126
127
                    if ($debug) {
128
                        error_log('User ::: ' . $user->getId());
129
                    }
130
131
                    if ($user->getId() !== $coachId) {
132
                        $courseInfo = $this->getCourseInfo($courseList);
133
                        $progress = $this->getUserProgress($user->getId(), $courseInfo, $session);
134
135
                        $message = $this->buildMessage(
136
                            $announcement,
137
                            $session,
138
                            $user,
139
                            $courseInfo,
140
                            $attachments,
141
                            $progress
142
                        );
143
144
                        if (!empty($extraFields)) {
145
                            $extraFieldValue = new ExtraFieldValue('user');
146
                            foreach ($extraFields as $extraField) {
147
                                $valueExtra = $extraFieldValue->get_values_by_handler_and_field_variable($user->getId(), $extraField['variable'], true);
148
                                $tags['(('.strtolower($extraField['variable']).'))'] = $valueExtra['value'] ?? '';
149
                            }
150
                            $message = str_replace(array_keys($tags), $tags, $message);
151
                        }
152
153
                        if ($debug) {
154
                            error_log("Sending email to user ID: " . $user->getId());
155
                        }
156
157
                        $this->sendEmail($user->getId(), $subject, $message, $coachId);
158
                    }
159
                }
160
161
                $coachMessage = $this->buildCoachMessage($announcement, $generalCoaches, $message);
162
                foreach ($coachList as $courseCoach) {
163
                    if ($debug) {
164
                        error_log("Sending email to coach ID: " . $courseCoach->getId());
165
                    }
166
                    $this->sendEmail($courseCoach->getId(), $subject, $coachMessage, $coachId);
167
                }
168
169
                $messagesSent++;
170
171
                if ($debug) {
172
                    error_log("Messages sent for announcement ID: " . $announcement->getId());
173
                }
174
            }
175
        }
176
177
        if ($debug) {
178
            error_log("Total messages sent: $messagesSent");
179
        }
180
181
        return $messagesSent;
182
    }
183
184
    /**
185
     * Checks if the announcement should be sent to coaches.
186
     */
187
    private function shouldSendToCoaches(int $announcementId): bool
188
    {
189
        $extraFieldValue = new ExtraFieldValue('scheduled_announcement');
190
        $sendToCoaches = $extraFieldValue->get_values_by_handler_and_field_variable($announcementId, 'send_to_coaches');
191
        return !empty($sendToCoaches) && !empty($sendToCoaches['value']) && (int)$sendToCoaches['value'] === 1;
192
    }
193
194
    /**
195
     * Verifies if sending scheduled announcements is allowed.
196
     */
197
    private function allowed(): bool
198
    {
199
        return 'true' === $this->settingsManager->getSetting('announcement.allow_scheduled_announcements');
200
    }
201
202
    /**
203
     * Builds the announcement message for the user.
204
     */
205
    private function buildMessage($announcement, Session $session, User $user, $courseInfo, $attachments, $progress): string
206
    {
207
        $generalCoaches = $session->getGeneralCoaches();
208
        $generalCoachName = [];
209
        $generalCoachEmail = [];
210
211
        foreach ($generalCoaches as $coach) {
212
            $generalCoachName[] = $coach->getFullname();
213
            $generalCoachEmail[] = $coach->getEmail();
214
        }
215
216
        $startTime = $this->getLocalTime($session->getAccessStartDate());
217
        $endTime = $this->getLocalTime($session->getAccessEndDate());
218
219
        $tags = [
220
            '((session_name))' => $session->getTitle(),
221
            '((session_start_date))' => $startTime,
222
            '((general_coach))' => implode(' - ', $generalCoachName),
223
            '((general_coach_email))' => implode(' - ', $generalCoachEmail),
224
            '((session_end_date))' => $endTime,
225
            '((user_complete_name))' => $user->getFirstname() . ' ' . $user->getLastname(),
226
            '((user_firstname))' => $user->getFirstname(),
227
            '((user_lastname))' => $user->getLastname(),
228
            '((lp_progress))' => $progress,
229
        ];
230
231
        $message = str_replace(array_keys($tags), $tags, $announcement->getMessage());
232
233
        return $message . $attachments;
234
    }
235
236
    /**
237
     * Builds the announcement message for the coach.
238
     */
239
    private function buildCoachMessage($announcement, $generalCoaches, $message): string
240
    {
241
        $coachNames = [];
242
        foreach ($generalCoaches as $coach) {
243
            $coachNames[] = $coach->getFullname();
244
        }
245
246
        $coachMessageIntro = count($generalCoaches) > 1
247
            ? $this->translator->trans('You are receiving a copy because you are one of the course coaches')
248
            : $this->translator->trans('You are receiving a copy because you are the course coach');
249
250
        $coachMessage = $coachMessageIntro . ': ' . implode(', ', $coachNames);
251
252
        return $coachMessage . '<br /><br />' . $message;
253
    }
254
255
    /**
256
     * Sends an email with the announcement to a user or coach.
257
     */
258
    private function sendEmail(int $userId, string $subject, string $message, int $coachId): void
259
    {
260
        $user = $this->em->getRepository(User::class)->find($userId);
261
        $coach = $this->em->getRepository(User::class)->find($coachId);
262
263
        if (!$user || !$coach) {
264
            throw new \Exception("User or coach not found.");
265
        }
266
267
        $this->messageHelper->sendMessageSimple(
268
            $userId,
269
            $subject,
270
            $message,
271
            $coachId
272
        );
273
    }
274
275
    /**
276
     * Retrieves course information from a list of courses.
277
     */
278
    private function getCourseInfo($courseList)
279
    {
280
        if (!empty($courseList) && current($courseList) instanceof Course) {
281
            $courseId = current($courseList)->getId();
282
            return $this->getCourseInfoById($courseId);
283
        } else {
284
            return [];
285
        }
286
    }
287
288
    /**
289
     * Retrieves course information by course ID.
290
     */
291
    private function getCourseInfoById(int $courseId)
292
    {
293
        $course = $this->em->getRepository(Course::class)->find($courseId);
294
295
        if ($course) {
296
            return [
297
                'real_id' => $course->getId(),
298
                'title' => $course->getTitle(),
299
                'code' => $course->getCode(),
300
                'description' => $course->getDescription(),
301
                'tutor_name' => $course->getTutorName(),
302
            ];
303
        }
304
305
        return [];
306
    }
307
308
    /**
309
     * Gets the user's progress for a specific course and session.
310
     */
311
    private function getUserProgress(int $userId, $courseInfo, Session $session): string
312
    {
313
        if (!empty($courseInfo) && $session) {
314
            $progress = Tracking::get_avg_student_progress($userId, $courseInfo['real_id'], [], $session->getId());
315
            return is_numeric($progress) ? $progress . '%' : '0%';
316
        }
317
        return '0%';
318
    }
319
320
    /**
321
     * Formats a DateTime object to a string.
322
     */
323
    private function getLocalTime(?\DateTime $datetime): string
324
    {
325
        return $datetime ? $datetime->format('Y-m-d H:i:s') : '';
326
    }
327
}
328