chamilo/chamilo-lms

View on GitHub
src/CoreBundle/Service/ScheduledAnnouncementService.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

/* For licensing terms, see /license.txt */

namespace Chamilo\CoreBundle\Service;

use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Repository\Node\AccessUrlRepository;
use Chamilo\CoreBundle\Repository\ScheduledAnnouncementRepository;
use Chamilo\CoreBundle\Repository\SessionRepository;
use Chamilo\CoreBundle\ServiceHelper\MessageHelper;
use Chamilo\CoreBundle\Settings\SettingsManager;
use DateTime;
use Doctrine\ORM\EntityManager;
use Exception;
use ExtraField;
use ExtraFieldValue;
use Symfony\Contracts\Translation\TranslatorInterface;
use Tracking;

class ScheduledAnnouncementService
{
    public function __construct(
        private readonly ScheduledAnnouncementRepository $announcementRepository,
        private readonly AccessUrlRepository $accessUrlRepository,
        private readonly EntityManager $em,
        private readonly SettingsManager $settingsManager,
        private readonly SessionRepository $sessionRepository,
        private readonly MessageHelper $messageHelper,
        private readonly TranslatorInterface $translator
    ) {}

    /**
     * Sends pending announcements to users and coaches.
     */
    public function sendPendingMessages(int $urlId, bool $debug = false): int
    {
        if (!$this->allowed()) {
            if ($debug) {
                error_log('Announcements not allowed.');
            }

            return 0;
        }

        $messagesSent = 0;
        $now = new DateTime();

        if ($debug) {
            error_log('Current time: '.$now->format('Y-m-d H:i:s'));
        }

        $announcements = $this->announcementRepository->findBy(['sent' => false]);

        if ($debug) {
            error_log(\count($announcements).' pending announcements found.');
        }

        $extraField = new ExtraField('user');
        $extraFields = $extraField->get_all(['filter = ? AND visible_to_self = ?' => [1, 1]]);

        foreach ($announcements as $announcement) {
            if (!$announcement->isSent() && $announcement->getDate() < $now) {
                if ($debug) {
                    error_log('Processing announcement ID: '.$announcement->getId());
                }

                $sessionId = $announcement->getSessionId();
                $session = $this->sessionRepository->find($sessionId);

                if (!$session) {
                    if ($debug) {
                        error_log("Session not found for session ID: $sessionId");
                    }

                    continue;
                }

                $accessUrl = $this->accessUrlRepository->find($urlId);
                $sessionRelUsers = $this->sessionRepository->getUsersByAccessUrl($session, $accessUrl);
                $generalCoaches = $session->getGeneralCoaches();

                if (empty($sessionRelUsers) || 0 === $generalCoaches->count()) {
                    if ($debug) {
                        error_log("No users or general coaches found for session ID: $sessionId");
                    }

                    continue;
                }

                $coachId = $generalCoaches->first()->getId();
                if ($debug) {
                    error_log("Coach ID: $coachId");
                }

                $coachList = [];

                $sendToCoaches = $this->shouldSendToCoaches($announcement->getId());

                $courseList = $session->getCourses();

                if ($debug) {
                    error_log('Number of courses in session: '.\count($courseList));
                    foreach ($courseList as $sessionRelCourse) {
                        $course = $sessionRelCourse->getCourse();
                        error_log('Course ID: '.$course->getId().', Course Title: '.$course->getTitle());
                    }
                }

                if ($sendToCoaches) {
                    foreach ($courseList as $course) {
                        $coaches = $session->getGeneralCoaches();
                        $coachList = array_merge($coachList, $coaches->toArray());
                    }
                    $coachList = array_unique($coachList);
                }

                $announcement->setSent(true);
                $this->em->persist($announcement);
                $this->em->flush();

                $attachments = '';
                $subject = $announcement->getSubject();

                foreach ($sessionRelUsers as $sessionRelUser) {
                    $user = $sessionRelUser->getUser();

                    if ($debug) {
                        error_log('User ::: '.$user->getId());
                    }

                    if ($user->getId() !== $coachId) {
                        $courseInfo = $this->getCourseInfo($courseList);
                        $progress = $this->getUserProgress($user->getId(), $courseInfo, $session);

                        $message = $this->buildMessage(
                            $announcement,
                            $session,
                            $user,
                            $courseInfo,
                            $attachments,
                            $progress
                        );

                        if (!empty($extraFields)) {
                            $extraFieldValue = new ExtraFieldValue('user');
                            foreach ($extraFields as $extraField) {
                                $valueExtra = $extraFieldValue->get_values_by_handler_and_field_variable($user->getId(), $extraField['variable'], true);
                                $tags['(('.strtolower($extraField['variable']).'))'] = $valueExtra['value'] ?? '';
                            }
                            $message = str_replace(array_keys($tags), $tags, $message);
                        }

                        if ($debug) {
                            error_log('Sending email to user ID: '.$user->getId());
                        }

                        $this->sendEmail($user->getId(), $subject, $message, $coachId);
                    }
                }

                $coachMessage = $this->buildCoachMessage($announcement, $generalCoaches, $message);
                foreach ($coachList as $courseCoach) {
                    if ($debug) {
                        error_log('Sending email to coach ID: '.$courseCoach->getId());
                    }
                    $this->sendEmail($courseCoach->getId(), $subject, $coachMessage, $coachId);
                }

                $messagesSent++;

                if ($debug) {
                    error_log('Messages sent for announcement ID: '.$announcement->getId());
                }
            }
        }

        if ($debug) {
            error_log("Total messages sent: $messagesSent");
        }

        return $messagesSent;
    }

    /**
     * Checks if the announcement should be sent to coaches.
     */
    private function shouldSendToCoaches(int $announcementId): bool
    {
        $extraFieldValue = new ExtraFieldValue('scheduled_announcement');
        $sendToCoaches = $extraFieldValue->get_values_by_handler_and_field_variable($announcementId, 'send_to_coaches');

        return !empty($sendToCoaches) && !empty($sendToCoaches['value']) && 1 === (int) $sendToCoaches['value'];
    }

    /**
     * Verifies if sending scheduled announcements is allowed.
     */
    private function allowed(): bool
    {
        return 'true' === $this->settingsManager->getSetting('announcement.allow_scheduled_announcements');
    }

    /**
     * Builds the announcement message for the user.
     *
     * @param mixed $announcement
     * @param mixed $courseInfo
     * @param mixed $attachments
     * @param mixed $progress
     */
    private function buildMessage($announcement, Session $session, User $user, $courseInfo, $attachments, $progress): string
    {
        $generalCoaches = $session->getGeneralCoaches();
        $generalCoachName = [];
        $generalCoachEmail = [];

        foreach ($generalCoaches as $coach) {
            $generalCoachName[] = $coach->getFullname();
            $generalCoachEmail[] = $coach->getEmail();
        }

        $startTime = $this->getLocalTime($session->getAccessStartDate());
        $endTime = $this->getLocalTime($session->getAccessEndDate());

        $tags = [
            '((session_name))' => $session->getTitle(),
            '((session_start_date))' => $startTime,
            '((general_coach))' => implode(' - ', $generalCoachName),
            '((general_coach_email))' => implode(' - ', $generalCoachEmail),
            '((session_end_date))' => $endTime,
            '((user_complete_name))' => $user->getFirstname().' '.$user->getLastname(),
            '((user_firstname))' => $user->getFirstname(),
            '((user_lastname))' => $user->getLastname(),
            '((lp_progress))' => $progress,
        ];

        $message = str_replace(array_keys($tags), $tags, $announcement->getMessage());

        return $message.$attachments;
    }

    /**
     * Builds the announcement message for the coach.
     *
     * @param mixed $announcement
     * @param mixed $generalCoaches
     * @param mixed $message
     */
    private function buildCoachMessage($announcement, $generalCoaches, $message): string
    {
        $coachNames = [];
        foreach ($generalCoaches as $coach) {
            $coachNames[] = $coach->getFullname();
        }

        $coachMessageIntro = \count($generalCoaches) > 1
            ? $this->translator->trans('You are receiving a copy because you are one of the course coaches')
            : $this->translator->trans('You are receiving a copy because you are the course coach');

        $coachMessage = $coachMessageIntro.': '.implode(', ', $coachNames);

        return $coachMessage.'<br /><br />'.$message;
    }

    /**
     * Sends an email with the announcement to a user or coach.
     */
    private function sendEmail(int $userId, string $subject, string $message, int $coachId): void
    {
        $user = $this->em->getRepository(User::class)->find($userId);
        $coach = $this->em->getRepository(User::class)->find($coachId);

        if (!$user || !$coach) {
            throw new Exception('User or coach not found.');
        }

        $this->messageHelper->sendMessageSimple(
            $userId,
            $subject,
            $message,
            $coachId
        );
    }

    /**
     * Retrieves course information from a list of courses.
     *
     * @param mixed $courseList
     */
    private function getCourseInfo($courseList)
    {
        if (!empty($courseList) && current($courseList) instanceof Course) {
            $courseId = current($courseList)->getId();

            return $this->getCourseInfoById($courseId);
        }

        return [];
    }

    /**
     * Retrieves course information by course ID.
     */
    private function getCourseInfoById(int $courseId)
    {
        $course = $this->em->getRepository(Course::class)->find($courseId);

        if ($course) {
            return [
                'real_id' => $course->getId(),
                'title' => $course->getTitle(),
                'code' => $course->getCode(),
                'description' => $course->getDescription(),
                'tutor_name' => $course->getTutorName(),
            ];
        }

        return [];
    }

    /**
     * Gets the user's progress for a specific course and session.
     *
     * @param mixed $courseInfo
     */
    private function getUserProgress(int $userId, $courseInfo, Session $session): string
    {
        if (!empty($courseInfo) && $session) {
            $progress = Tracking::get_avg_student_progress($userId, $courseInfo['real_id'], [], $session->getId());

            return is_numeric($progress) ? $progress.'%' : '0%';
        }

        return '0%';
    }

    /**
     * Formats a DateTime object to a string.
     */
    private function getLocalTime(?DateTime $datetime): string
    {
        return $datetime ? $datetime->format('Y-m-d H:i:s') : '';
    }
}