Calendar: Refactor Agenda Reminders Script - refs #5145

pull/5319/head
christianbeeznst 8 months ago
parent ece854fac4
commit 96651bee25
  1. 67
      assets/vue/components/ccalendarevent/CCalendarEventForm.vue
  2. 2
      assets/vue/store/modules/crud.js
  3. 161
      assets/vue/views/ccalendarevent/CCalendarEventList.vue
  4. 210
      public/main/cron/agenda_reminders.php
  5. 2
      public/main/inc/lib/api.lib.php
  6. 36
      src/CoreBundle/Controller/Api/DeleteRemindersByEventAction.php
  7. 2
      src/CoreBundle/Controller/PlatformConfigurationController.php
  8. 30
      src/CoreBundle/Entity/AgendaReminder.php
  9. 24
      src/CoreBundle/Migrations/Schema/V200/Version20201215072918.php
  10. 21
      src/CoreBundle/Migrations/Schema/V200/Version20230904173400.php
  11. 65
      src/CoreBundle/State/AgendaReminderProcessor.php

@ -41,18 +41,46 @@
</div>
<CalendarInvitations v-model="item" />
<div v-if="agendaRemindersEnabled" class="field mt-2">
<BaseButton
label="Add Notification"
@click="addNotification"
icon="time"
type="button"
class="mb-2"
/>
<div v-for="(notification, index) in notifications" :key="index" class="flex items-center gap-2">
<input
v-model="notification.count"
type="number"
min="0"
placeholder="Count"
/>
<select v-model="notification.period">
<option value="i">Minutes</option>
<option value="h">Hours</option>
<option value="d">Days</option>
</select>
<BaseButton
icon="delete"
@click="removeNotification(index)"
type="button"/>
</div>
</div>
<slot />
</form>
</template>
<script setup>
import { computed, ref, watch } from "vue"
import { computed, onMounted, ref, watch } from "vue"
import { useVuelidate } from "@vuelidate/core"
import { required } from "@vuelidate/validators"
import { required, minValue } from "@vuelidate/validators"
import BaseInputText from "../basecomponents/BaseInputText.vue"
import { useI18n } from "vue-i18n"
import BaseCalendar from "../basecomponents/BaseCalendar.vue"
import CalendarInvitations from "./CalendarInvitations.vue"
import BaseButton from "../basecomponents/BaseButton.vue"
import { usePlatformConfig } from "../../store/platformConfig"
const { t } = useI18n()
@ -70,9 +98,11 @@ const props = defineProps({
type: Object,
default: () => {},
},
notificationsData: Array,
})
const item = computed(() => props.initialValues || props.values)
const notifications = ref(props.notificationsData || [])
const rules = computed(() => ({
item: {
@ -89,13 +119,25 @@ const rules = computed(() => ({
required,
},
},
notifications: {
$each: {
count: { required, minVal: minValue(1) },
period: { required },
},
},
}))
const v$ = useVuelidate(rules, { item })
const v$ = useVuelidate(rules, { item, notifications })
// eslint-disable-next-line no-undef
defineExpose({
v$,
notifications
})
const platformConfigStore = usePlatformConfig()
const agendaRemindersEnabled = computed(() => {
return platformConfigStore.getSetting("agenda.agenda_reminders") === "true"
})
const dateRange = ref()
@ -108,4 +150,23 @@ watch(dateRange, (newValue) => {
item.value.startDate = newValue[0]
item.value.endDate = newValue[1]
})
onMounted(() => {
notifications.value = props.notificationsData.map(notification => ({
count: notification.count,
period: notification.period,
}))
})
watch(() => props.notificationsData, (newVal) => {
notifications.value = newVal || []
})
function addNotification() {
notifications.value.push({ count: 1, period: 'i' })
}
function removeNotification(index) {
notifications.value.splice(index, 1)
}
</script>

@ -111,6 +111,7 @@ export default function makeCrudModule({ normalizeRelations = (x) => x, resolveR
commit(ACTIONS.TOGGLE_LOADING);
commit(ACTIONS.ADD, data);
commit(ACTIONS.SET_CREATED, data);
return data;
})
.catch((e) => handleError(commit, e));
},
@ -343,6 +344,7 @@ export default function makeCrudModule({ normalizeRelations = (x) => x, resolveR
.then((data) => {
commit(ACTIONS.TOGGLE_LOADING);
commit(ACTIONS.SET_UPDATED, data);
return data;
})
.catch((e) => handleError(commit, e));
},

@ -22,6 +22,7 @@
ref="createForm"
:values="item"
:is-global="isGlobal"
:notifications-data="selectedEventNotifications"
/>
<template #footer>
<BaseButton
@ -149,6 +150,8 @@ import CalendarSectionHeader from "../../components/ccalendarevent/CalendarSecti
import { useCalendarActionButtons } from "../../composables/calendar/calendarActionButtons"
import { useCalendarEvent } from "../../composables/calendar/calendarEvent"
import resourceLinkService from "../../services/resourceLinkService"
import axios from "axios"
import { usePlatformConfig } from "../../store/platformConfig"
const store = useStore()
const confirm = useConfirm()
@ -174,6 +177,7 @@ const { t } = useI18n()
const { appLocale } = useLocale()
const route = useRoute()
const isGlobal = ref(route.query.type === 'global')
const selectedEventNotifications = ref([])
let currentEvent = null
@ -188,6 +192,40 @@ const sessionState = reactive({
showSessionDialog: false,
})
const platformConfigStore = usePlatformConfig()
const agendaRemindersEnabled = computed(() => {
return "true" === platformConfigStore.getSetting("agenda.agenda_reminders")
})
function parseDateInterval(dateInterval) {
const regex = /P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/
const matches = dateInterval.match(regex)
if (matches) {
const [, , , , hours, minutes] = matches.map(Number)
if (hours) return { count: hours, period: 'h' }
if (minutes) return { count: minutes, period: 'i' }
}
return { count: 0, period: 'i' } // Default value
}
async function loadEventNotifications(compositeEventId) {
const matches = compositeEventId.match(/\d+/)
const eventId = matches ? matches[0] : null
if (!eventId) {
console.error("Invalid event ID format:", compositeEventId)
return
}
try {
const response = await axios.get(`/api/agenda_reminders?eventId=${eventId}`)
selectedEventNotifications.value = response.data['hydra:member'].map(notification => parseDateInterval(notification.dateInterval))
} catch (error) {
console.error("Error loading event notifications:", error)
selectedEventNotifications.value = []
}
}
async function getCalendarEvents({ startStr, endStr }) {
const params = {
"startDate[after]": startStr,
@ -258,7 +296,7 @@ const calendarOptions = ref({
startParam: "startDate[after]",
endParam: "endDate[before]",
selectable: true,
eventClick(eventClickInfo) {
eventClick: async (eventClickInfo) => {
eventClickInfo.jsEvent.preventDefault()
currentEvent = eventClickInfo.event
@ -271,6 +309,7 @@ const calendarOptions = ref({
return
}
await loadEventNotifications(event.id);
item.value = { ...event.extendedProps }
item.value["title"] = event.title
@ -341,28 +380,30 @@ function confirmDelete() {
icon: "pi pi-exclamation-triangle",
acceptClass: "p-button-danger",
rejectClass: "p-button-plain p-button-outlined",
accept() {
if (item.value["parentResourceNodeId"] === currentUser.value["id"]) {
store.dispatch("ccalendarevent/del", item.value)
accept: async () => {
try {
const eventId = item.value['@id'].split('/').pop()
await axios.post(`/api/agenda_reminders/delete_by_event`, { eventId })
if (item.value["parentResourceNodeId"] === currentUser.value["id"]) {
await store.dispatch("ccalendarevent/del", item.value)
} else {
let filteredLinks = item.value["resourceLinkListFromEntity"].filter(
(resourceLinkFromEntity) => resourceLinkFromEntity["user"]["id"] === currentUser.value["id"]
)
if (filteredLinks.length > 0) {
await store.dispatch("resourcelink/del", {
"@id": `/api/resource_links/${filteredLinks[0]["id"]}`,
})
}
}
dialogShow.value = false
dialog.value = false
reFetch()
} else {
let filteredLinks = item.value["resourceLinkListFromEntity"].filter(
(resourceLinkFromEntity) => resourceLinkFromEntity["user"]["id"] === currentUser.value["id"],
)
if (filteredLinks.length > 0) {
store.dispatch("resourcelink/del", {
"@id": `/api/resource_links/${filteredLinks[0]["id"]}`,
})
currentEvent.remove()
dialogShow.value = false
dialog.value = false
reFetch()
}
toast.add({ severity: "success", detail: t("Event and its notifications deleted successfully."), life: 3500 })
} catch (error) {
console.error("Error deleting event or notifications: ", error)
toast.add({ severity: "error", detail: t("Error deleting event or notifications"), life: 3500 })
}
},
})
@ -389,7 +430,7 @@ const isLoading = computed(() => store.getters["ccalendarevent/isLoading"])
const createForm = ref(null)
function onCreateEventForm() {
async function onCreateEventForm() {
if (createForm.value.v$.$invalid) {
return
}
@ -400,27 +441,83 @@ function onCreateEventForm() {
itemModel.isGlobal = true
}
if (itemModel["@id"]) {
store.dispatch("ccalendarevent/update", itemModel)
} else {
if (course.value) {
itemModel.resourceLinkList = [
{
try {
let response;
if (itemModel["@id"]) {
// Update the existing event
response = await store.dispatch("ccalendarevent/update", itemModel)
} else {
// Create a new event
if (course.value) {
itemModel.resourceLinkList = [{
cid: course.value.id,
sid: session.value?.id ?? null,
visibility: RESOURCE_LINK_PUBLISHED,
},
]
}]
}
response = await store.dispatch("ccalendarevent/create", itemModel)
}
store.dispatch("ccalendarevent/create", itemModel)
if (response && response.iid) {
let successDetail = t("Event saved successfully")
if (agendaRemindersEnabled.value) {
if (createForm.value.notifications && createForm.value.notifications.length > 0 && !createForm.value.v$.notifications.$error) {
await sendNotifications(response.iid, createForm.value.notifications)
successDetail += t(" with notifications sent")
}
}
dialog.value = false
dialogShow.value = false
toast.add({ severity: "success", detail: successDetail, life: 3500 })
reFetch()
} else {
throw new Error("Failed to obtain event ID from the response.")
}
} catch (error) {
console.error("Error saving event or notifications: ", error)
toast.add({ severity: "error", detail: "Error saving event or notifications", life: 3500 })
}
dialog.value = false
}
const toast = useToast()
// Function to send notifications to the server
async function sendNotifications(eventId, notifications) {
try {
const response = await axios.post(`/api/agenda_reminders/delete_by_event`, { eventId })
console.log(response.data.message)
} catch (error) {
console.error(`Error deleting existing notifications for event ${eventId}:`, error)
}
const promises = notifications.map(notification => {
const notificationData = {
eventId: eventId,
count: notification.count,
period: notification.period,
}
return fetch('/api/agenda_reminders', {
method: 'POST',
headers: {
'Accept': 'application/ld+json',
'Content-Type': 'application/ld+json',
},
body: JSON.stringify(notificationData),
})
.then(response => {
if (!response.ok) {
return response.json().then(errorBody => {
throw new Error(`Error: ${response.status} ${errorBody['hydra:description'] || 'Unknown error'}`)
})
}
return response.json()
})
.catch(error => {
console.error('Failed to send a notification:', error.message)
})
})
await Promise.allSettled(promises)
}
watch(() => route.query.type, (newType) => {
isGlobal.value = newType === 'global'
reFetch()

@ -0,0 +1,210 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This script send notification messages to users that have reminders from an event in their agenda.
*/
require_once __DIR__.'/../../main/inc/global.inc.php';
exit;
if ('true' !== api_get_setting('agenda.agenda_reminders')) {
exit;
}
$batchCounter = 0;
$batchSize = 100;
$agendaCollectiveInvitations = 'true' === api_get_setting('agenda.agenda_collective_invitations');
$now = new DateTime('now', new DateTimeZone('UTC'));
$em = Database::getManager();
$remindersRepo = $em->getRepository(\Chamilo\CoreBundle\Entity\AgendaReminder::class);
$reminders = $remindersRepo->findBy(['sent' => false]);
$senderId = (int) api_get_setting('agenda.agenda_reminders_sender_id');
if (empty($senderId)) {
$firstAdmin = current(UserManager::get_all_administrators());
$senderId = $firstAdmin['user_id'];
}
foreach ($reminders as $reminder) {
if ('personal' === $reminder->getType()) {
$event = $em->getRepository(\Chamilo\CourseBundle\Entity\CCalendarEvent::class)->find($reminder->getEventId());
if (null === $event) {
continue;
}
$notificationDate = clone $event->getStartDate();
$notificationDate->sub($reminder->getDateInterval());
if ($notificationDate > $now) {
continue;
}
$eventDetails = [];
$eventDetails[] = '<p><strong>'.$event->getTitle().'</strong></p>';
if ($event->isAllDay()) {
$eventDetails[] = '<p class="small">'.get_lang('AllDay').'</p>';
} else {
$eventDetails[] = sprintf(
'<p class="small">'.get_lang('FromDateX').'</p>',
api_get_local_time($event->getStartDate(), null, null, false, true, true)
);
if (!empty($event->getEnddate())) {
$eventDetails[] = sprintf(
'<p class="small">'.get_lang('UntilDateX').'</p>',
api_get_local_time($event->getEnddate(), null, null, false, true, true)
);
}
}
if (!empty($event->getContent())) {
$eventDetails[] = $event->getContent();
}
$messageSubject = sprintf(get_lang('ReminderXEvent'), $event->getTitle());
$messageContent = implode(PHP_EOL, $eventDetails);
MessageManager::send_message_simple(
$event->getResourceNode()->getCreator()->getId(),
$messageSubject,
$messageContent,
$event->getResourceNode()->getCreator()->getId()
);
$getInviteesForEvent = function ($eventId) use ($em) {
$event = $em->find(\Chamilo\CourseBundle\Entity\CCalendarEvent::class, $eventId);
if (!$event) {
return [];
}
$resourceLinks = $event->getResourceLinkEntityList();
$inviteeList = [];
foreach ($resourceLinks as $resourceLink) {
$user = $resourceLink->getUser();
if ($user) {
$inviteeList[] = [
'id' => $user->getId(),
'name' => $user->getFullname(),
];
}
}
return $inviteeList;
};
if ($agendaCollectiveInvitations) {
$invitees = $getInviteesForEvent($reminder->getEventId());
$inviteesIdList = array_column($invitees, 'id');
foreach ($inviteesIdList as $userId) {
MessageManager::send_message_simple(
$userId,
$messageSubject,
$messageContent,
$event->getResourceNode()->getCreator()->getId()
);
}
}
}
if ('course' === $reminder->getType()) {
$event = $em->getRepository(\Chamilo\CourseBundle\Entity\CCalendarEvent::class)->find($reminder->getEventId());
if (null === $event) {
continue;
}
$notificationDate = clone $event->getStartDate();
$notificationDate->sub($reminder->getDateInterval());
if ($notificationDate > $now) {
continue;
}
$eventDetails = [
sprintf('<p><strong>%s</strong></p>', $event->getTitle()),
$event->isAllDay() ? '<p class="small">All Day</p>' : sprintf(
'<p class="small">From %s</p>',
$event->getStartDate()->format('Y-m-d H:i:s')
)
];
if ($event->getEndDate()) {
$eventDetails[] = sprintf(
'<p class="small">Until %s</p>',
$event->getEndDate()->format('Y-m-d H:i:s')
);
}
if ($event->getContent()) {
$eventDetails[] = $event->getContent();
}
if ($event->getComment()) {
$eventDetails[] = sprintf('<p class="small">%s</p>', $event->getComment());
}
$messageSubject = sprintf('Reminder: %s', $event->getTitle());
$messageContent = implode(PHP_EOL, $eventDetails);
$resourceLinks = $event->getResourceNode()->getResourceLinks();
$userIdList = [];
$groupUserIdList = [];
foreach ($resourceLinks as $resourceLink) {
if ($resourceLink->getUser()) {
$userIdList[] = $resourceLink->getUser()->getId();
} elseif ($resourceLink->getGroup()) {
$groupUsers = GroupManager::get_users($resourceLink->getGroup()->getId(), false, null, null, false, $event->getSessionId());
foreach ($groupUsers as $groupUserId) {
$groupUserIdList[] = $groupUserId;
}
}
}
$userIdList = array_unique($userIdList);
$groupUserIdList = array_unique($groupUserIdList);
foreach ($userIdList as $userId) {
MessageManager::send_message_simple(
$userId,
$messageSubject,
$messageContent,
$senderId
);
}
foreach ($groupUserIdList as $groupUserId) {
MessageManager::send_message_simple(
$groupUserId,
$messageSubject,
$messageContent,
$senderId
);
}
}
$reminder->setSent(true);
$em->persist($reminder);
$batchCounter++;
if (($batchCounter % $batchSize) === 0) {
$em->flush();
}
}
$em->flush();
$em->clear();

@ -7134,7 +7134,7 @@ function api_mail_html(
}
try {
$bus = Container::getMessengerBus();
//$bus = Container::getMessengerBus();
//$sendMessage = new \Chamilo\CoreBundle\Message\SendMessage();
//$bus->dispatch($sendMessage);

@ -0,0 +1,36 @@
<?php
/* For licensing terms, see /license.txt */
declare(strict_types=1);
namespace Chamilo\CoreBundle\Controller\Api;
use Chamilo\CoreBundle\Entity\AgendaReminder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class DeleteRemindersByEventAction extends AbstractController
{
public function __invoke(Request $request, EntityManagerInterface $em): Response
{
$data = json_decode($request->getContent(), true);
$eventId = $data['eventId'] ?? null;
if (!$eventId) {
return $this->json(['message' => 'Event ID is required.'], Response::HTTP_BAD_REQUEST);
}
$repository = $em->getRepository(AgendaReminder::class);
$reminders = $repository->findBy(['eventId' => $eventId]);
foreach ($reminders as $reminder) {
$em->remove($reminder);
}
$em->flush();
return $this->json(['message' => 'All reminders for the event have been deleted.']);
}
}

@ -58,7 +58,7 @@ class PlatformConfigurationController extends AbstractController
'agenda.allow_personal_agenda',
'agenda.personal_calendar_show_sessions_occupation',
// 'agenda.agenda_reminders',
'agenda.agenda_reminders',
'agenda.agenda_collective_invitations',
'agenda.agenda_event_subscriptions',

@ -6,12 +6,37 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\GetCollection;
use Chamilo\CoreBundle\Controller\Api\DeleteRemindersByEventAction;
use Chamilo\CoreBundle\State\AgendaReminderProcessor;
use Chamilo\CoreBundle\Traits\TimestampableTypedEntity;
use DateInterval;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity]
#[ORM\Table(name: 'agenda_reminder')]
#[ApiResource(
operations: [
new GetCollection(),
new Post(processor: AgendaReminderProcessor::class),
new Post(
uriTemplate: '/agenda_reminders/delete_by_event',
controller: DeleteRemindersByEventAction::class,
openapiContext: ['summary' => 'Deletes all reminders for a specific event'],
denormalizationContext: ['groups' => ['agenda_reminder:delete_by_event']],
security: "is_granted('ROLE_USER')"
),
],
normalizationContext: ['groups' => ['agenda_reminder:read']],
denormalizationContext: ['groups' => ['agenda_reminder:write']],
security: "is_granted('ROLE_USER')"
)]
#[ApiFilter(SearchFilter::class, properties: ['eventId' => 'exact'])]
class AgendaReminder
{
use TimestampableTypedEntity;
@ -22,20 +47,25 @@ class AgendaReminder
protected ?int $id = null;
#[ORM\Column(name: 'type', type: 'string')]
#[Groups(['agenda_reminder:write', 'agenda_reminder:read'])]
protected string $type;
#[ORM\Column(name: 'event_id', type: 'integer')]
#[Groups(['agenda_reminder:write', 'agenda_reminder:read'])]
protected int $eventId;
#[ORM\Column(name: 'date_interval', type: 'dateinterval')]
#[Groups(['agenda_reminder:write', 'agenda_reminder:read'])]
protected DateInterval $dateInterval;
#[ORM\Column(name: 'sent', type: 'boolean')]
#[Groups(['agenda_reminder:write', 'agenda_reminder:read'])]
protected bool $sent;
public function __construct()
{
$this->sent = false;
$this->type = 'personal';
}
public function getId(): ?int

@ -6,6 +6,7 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
use Chamilo\CoreBundle\Entity\AgendaReminder;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
@ -21,7 +22,7 @@ final class Version20201215072918 extends AbstractMigrationChamilo
{
public function getDescription(): string
{
return 'Migrate c_calendar_event, calendar_event_attachment';
return 'Migrate c_calendar_event, calendar_event_attachment and update agenda_reminder';
}
public function up(Schema $schema): void
@ -41,6 +42,7 @@ final class Version20201215072918 extends AbstractMigrationChamilo
$kernel = $container->get('kernel');
$rootPath = $kernel->getProjectDir();
$admin = $this->getAdmin();
$oldNewEventIdMap = [];
$q = $em->createQuery('SELECT c FROM Chamilo\CoreBundle\Entity\Course c');
@ -55,6 +57,7 @@ final class Version20201215072918 extends AbstractMigrationChamilo
$events = $result->fetchAllAssociative();
foreach ($events as $eventData) {
$id = $eventData['iid'];
$oldEventId = $id;
/** @var CCalendarEvent $event */
$event = $eventRepo->find($id);
@ -100,6 +103,9 @@ final class Version20201215072918 extends AbstractMigrationChamilo
$em->persist($event);
$em->flush();
$newEventId = $event->getId();
$oldNewEventIdMap[$oldEventId] = $newEventId;
}
$sql = "SELECT * FROM c_calendar_event_attachment WHERE c_id = {$courseId}
@ -137,6 +143,22 @@ final class Version20201215072918 extends AbstractMigrationChamilo
$em->flush();
}
}
$this->updateAgendaReminders($oldNewEventIdMap, $em);
}
private function updateAgendaReminders($oldNewEventIdMap, $em): void
{
$reminders = $em->getRepository(AgendaReminder::class)->findBy(['type' => 'course']);
foreach ($reminders as $reminder) {
$oldEventId = $reminder->getEventId();
if (array_key_exists($oldEventId, $oldNewEventIdMap)) {
$newEventId = $oldNewEventIdMap[$oldEventId];
$reminder->setEventId($newEventId);
$em->persist($reminder);
}
}
$em->flush();
}
public function down(Schema $schema): void {}

@ -6,6 +6,7 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
use Chamilo\CoreBundle\Entity\AgendaReminder;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
use Chamilo\CourseBundle\Entity\CCalendarEvent;
@ -19,7 +20,7 @@ class Version20230904173400 extends AbstractMigrationChamilo
{
public function getDescription(): string
{
return 'Migrate personal_agenda to c_calendar_event';
return 'Migrate personal_agenda to c_calendar_event and update agenda_reminder';
}
/**
@ -44,6 +45,7 @@ class Version20230904173400 extends AbstractMigrationChamilo
$personalAgendas = $this->getPersonalEvents();
$utc = new DateTimeZone('UTC');
$oldNewEventIdMap = [];
/** @var array $personalAgenda */
foreach ($personalAgendas as $personalAgenda) {
@ -70,6 +72,7 @@ class Version20230904173400 extends AbstractMigrationChamilo
$map[$personalAgenda['id']] = $calendarEvent;
$em->persist($calendarEvent);
$em->flush();
if ($collectiveInvitationsEnabled) {
$invitationsOrSubscriptionsInfo = [];
@ -116,9 +119,11 @@ class Version20230904173400 extends AbstractMigrationChamilo
}
}
}
$oldNewEventIdMap[$personalAgenda['id']] = $calendarEvent->getIid();
}
$em->flush();
$this->updateAgendaReminders($oldNewEventIdMap, $em);
}
private function getPersonalEvents(): array
@ -216,4 +221,18 @@ class Version20230904173400 extends AbstractMigrationChamilo
return [];
}
}
private function updateAgendaReminders(array $oldNewEventIdMap, $em): void
{
$reminders = $em->getRepository(AgendaReminder::class)->findBy(['type' => 'personal']);
foreach ($reminders as $reminder) {
$oldEventId = $reminder->getEventId();
if (array_key_exists($oldEventId, $oldNewEventIdMap)) {
$newEventId = $oldNewEventIdMap[$oldEventId];
$reminder->setEventId($newEventId);
$em->persist($reminder);
}
}
$em->flush();
}
}

@ -0,0 +1,65 @@
<?php
/* For licensing terms, see /license.txt */
declare(strict_types=1);
namespace Chamilo\CoreBundle\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use Chamilo\CoreBundle\Entity\AgendaReminder;
use Chamilo\CourseBundle\Repository\CCalendarEventRepository;
use Symfony\Component\HttpFoundation\RequestStack;
use DateInterval;
use Doctrine\ORM\EntityManagerInterface;
class AgendaReminderProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
private RequestStack $requestStack,
private CCalendarEventRepository $eventRepository
) {}
public function process($data, Operation $operation, array $uriVariables = [], array $context = []): AgendaReminder
{
\assert($data instanceof AgendaReminder);
$request = $this->requestStack->getCurrentRequest();
if ($request) {
$payload = json_decode($request->getContent(), true);
$eventId = $payload['eventId'] ?? null;
$event = $this->eventRepository->find($eventId);
if (!$event) {
throw new \Exception("Event not found with ID: $eventId");
}
$type = $this->eventRepository->determineEventType($event);
$data->setType($type);
$data->setEventId($eventId);
$count = $payload['count'] ?? 0;
$period = $payload['period'] ?? '';
$data->setDateInterval($this->convertToInterval((int) $count, $period));
}
$this->entityManager->persist($data);
$this->entityManager->flush();
return $data;
}
private function convertToInterval(int $count, string $period): DateInterval
{
return match ($period) {
'i' => new DateInterval("PT{$count}M"),
'h' => new DateInterval("PT{$count}H"),
'd' => new DateInterval("P{$count}D"),
default => throw new \InvalidArgumentException("Period not valid: $period"),
};
}
}
Loading…
Cancel
Save