Platform: Refactor global events management - refs #5270

pull/5291/head
christianbeeznst 2 years ago
parent a1f04ee3b1
commit 6096110fd0
  1. 3
      assets/vue/router/ccalendarevent.js
  2. 17
      assets/vue/views/ccalendarevent/CCalendarEventList.vue
  3. 20
      public/main/inc/ajax/agenda.ajax.php
  4. 85
      public/main/inc/lib/agenda.lib.php
  5. 2
      src/CoreBundle/Controller/Admin/IndexBlocksController.php
  6. 49
      src/CoreBundle/Entity/Listener/ResourceListener.php
  7. 166
      src/CoreBundle/Entity/SysCalendar.php
  8. 82
      src/CoreBundle/Filter/GlobalEventFilter.php
  9. 102
      src/CoreBundle/Migrations/Schema/V200/Version20240323181500.php
  10. 29
      src/CoreBundle/Migrations/Schema/V200/Version20240323222700.php
  11. 19
      src/CoreBundle/Repository/SysCalendarRepository.php
  12. 2
      src/CourseBundle/Entity/CCalendarEvent.php

@ -18,7 +18,8 @@ export default {
{
name: 'CCalendarEventList',
path: '',
component: () => import('../views/ccalendarevent/CCalendarEventList.vue')
component: () => import('../views/ccalendarevent/CCalendarEventList.vue'),
props: (route) => ({ type: route.query.type })
}
]
};

@ -23,6 +23,7 @@
v-if="dialog"
ref="createForm"
:values="item"
:is-global="isGlobal"
/>
<template #footer>
<BaseButton
@ -128,6 +129,7 @@ import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
import { useConfirm } from "primevue/useconfirm"
import { useFormatDate } from "../../composables/formatDate"
import { useRoute } from "vue-router"
import Loading from "../../components/Loading.vue"
import FullCalendar from "@fullcalendar/vue3"
@ -171,6 +173,8 @@ const allowToUnsubscribe = ref(false)
const currentUser = computed(() => store.getters["security/getUser"])
const { t } = useI18n()
const { appLocale } = useLocale()
const route = useRoute()
const isGlobal = ref(route.query.type === 'global')
let currentEvent = null
@ -203,6 +207,10 @@ async function getCalendarEvents({ startStr, endStr }) {
params.gid = group.value.id
}
if (route.query?.type === 'global') {
params.type = 'global'
}
const calendarEvents = await cCalendarEventService.findAll({ params }).then((response) => response.json())
return calendarEvents["hydra:member"].map((event) => ({
@ -359,6 +367,10 @@ function onCreateEventForm() {
let itemModel = createForm.value.v$.item.$model
if (isGlobal.value) {
itemModel.isGlobal = true
}
if (itemModel["@id"]) {
store.dispatch("ccalendarevent/update", itemModel)
} else {
@ -380,6 +392,11 @@ function onCreateEventForm() {
const toast = useToast()
watch(() => route.query.type, (newType) => {
isGlobal.value = newType === 'global'
reFetch()
})
watch(
() => store.state.ccalendarevent.created,
(created) => {

@ -177,26 +177,6 @@ switch ($action) {
$month,
$year
);
$agendaitems = Agenda::get_global_agenda_items(
$agendaitems,
$day,
$month,
$year,
$week,
"month_view"
);
if ('true' === api_get_setting('allow_personal_agenda')) {
/*$agendaitems = Agenda::get_personal_agenda_items(
$user_id,
$agendaitems,
$day,
$month,
$year,
$week,
"month_view"
);*/
}
Agenda::display_mymonthcalendar(
$user_id,
$agendaitems,

@ -3,8 +3,6 @@
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Course;
//use Chamilo\CoreBundle\Entity\PersonalAgenda;
use Chamilo\CoreBundle\Entity\SysCalendar;
use Chamilo\CoreBundle\Framework\Container;
use Chamilo\CourseBundle\Entity\CCalendarEvent;
use Chamilo\CourseBundle\Entity\CCalendarEventAttachment;
@ -57,8 +55,6 @@ class Agenda
$sessionId = 0
) {
// Table definitions
$this->tbl_global_agenda = Database::get_main_table(TABLE_MAIN_SYSTEM_CALENDAR);
//$this->tbl_personal_agenda = Database::get_main_table(TABLE_PERSONAL_AGENDA);
$this->tbl_course_agenda = Database::get_course_table(TABLE_AGENDA);
$this->table_repeat = Database::get_course_table(TABLE_AGENDA_REPEAT);
@ -365,24 +361,6 @@ class Agenda
}
}
break;
case 'admin':
if (api_is_platform_admin()) {
$event = new SysCalendar();
$color = SysCalendar::COLOR_SYSTEM_EVENT;
$event
->setTitle($title)
->setContent($content)
->setStartDate($start)
->setEndDate($end)
->setAllDay($allDay)
->setUrl(api_get_url_entity())
->setColor($color)
;
$em->persist($event);
$em->flush();
$id = $event->getId();
}
break;
}
return $id;
@ -811,26 +789,6 @@ class Agenda
return false;
break;
case 'admin':
case 'platform':
if (api_is_platform_admin()) {
$attributes = [
'title' => $title,
'start_date' => $start,
'end_date' => $end,
'all_day' => $allDay,
];
if ($updateContent) {
$attributes['content'] = $content;
}
Database::update(
$this->tbl_global_agenda,
$attributes,
['id = ?' => $id]
);
}
break;
}
}
@ -925,14 +883,6 @@ class Agenda
}
}
break;
case 'admin':
if (api_is_platform_admin()) {
Database::delete(
$this->tbl_global_agenda,
['id = ?' => $id]
);
}
break;
}
}
@ -957,9 +907,6 @@ class Agenda
$format = 'json'
) {
switch ($this->type) {
case 'admin':
$this->getPlatformEvents($start, $end);
break;
case 'course':
$course = api_get_course_entity($courseId);
@ -1001,14 +948,6 @@ class Agenda
$sessionFilterActive = true;
}
if (false == $sessionFilterActive) {
// Getting personal events
//$this->getPersonalEvents($start, $end);
// Getting platform/admin events
$this->getPlatformEvents($start, $end);
}
$ignoreVisibility = ('true' === api_get_setting('agenda.personal_agenda_show_all_session_events'));
// Getting course events
@ -1171,12 +1110,6 @@ class Agenda
id = ".$id;
Database::query($sql);
break;
case 'admin':
$sql = "UPDATE $this->tbl_global_agenda SET
end_date = DATE_ADD(end_date, INTERVAL $delta MINUTE)
WHERE id = ".$id;
Database::query($sql);
break;
}
}
@ -1222,14 +1155,6 @@ class Agenda
id=".$id;
Database::query($sql);
break;
case 'admin':
$sql = "UPDATE $this->tbl_global_agenda SET
all_day = $allDay,
start_date = DATE_ADD(start_date,INTERVAL $delta MINUTE),
end_date = DATE_ADD(end_date, INTERVAL $delta MINUTE)
WHERE id=".$id;
Database::query($sql);
break;
}
}
@ -1291,16 +1216,6 @@ class Agenda
}
}
break;
case 'admin':
case 'platform':
$sql = "SELECT * FROM ".$this->tbl_global_agenda."
WHERE id = $id";
$result = Database::query($sql);
if (Database::num_rows($result)) {
$event = Database::fetch_array($result, 'ASSOC');
$event['description'] = $event['content'];
}
break;
}
return $event;

@ -339,7 +339,7 @@ class IndexBlocksController extends BaseController
];
$items[] = [
'class' => 'item-global-agenda',
'url' => $this->generateUrl('legacy_main', ['name' => 'calendar/agenda_js.php', 'type' => 'admin']),
'url' => '/resources/ccalendarevent?type=global',
'label' => $this->translator->trans('Global agenda'),
];

@ -13,6 +13,7 @@ use Chamilo\CoreBundle\Entity\EntityAccessUrlInterface;
use Chamilo\CoreBundle\Entity\PersonalFile;
use Chamilo\CoreBundle\Entity\ResourceFile;
use Chamilo\CoreBundle\Entity\ResourceFormat;
use Chamilo\CoreBundle\Entity\ResourceLink;
use Chamilo\CoreBundle\Entity\ResourceNode;
use Chamilo\CoreBundle\Entity\ResourceToRootInterface;
use Chamilo\CoreBundle\Entity\ResourceType;
@ -20,6 +21,7 @@ use Chamilo\CoreBundle\Entity\ResourceWithAccessUrlInterface;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Tool\ToolChain;
use Chamilo\CoreBundle\Traits\AccessUrlListenerTrait;
use Chamilo\CourseBundle\Entity\CCalendarEvent;
use Cocur\Slugify\SlugifyInterface;
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
@ -257,6 +259,10 @@ class ResourceListener
throw new InvalidArgumentException($message);
}
if ($resource instanceof CCalendarEvent) {
$this->addCCalendarEventGlobalLink($resource, $eventArgs);
}
}
/**
@ -299,4 +305,47 @@ class ResourceListener
$slug = sprintf('%s.%s', $this->slugify->slugify($originalBasename), $originalExtension);*/
$resource->getResourceNode()->setTitle($resourceName);
}
private function addCCalendarEventGlobalLink(CCalendarEvent $event, PrePersistEventArgs $eventArgs): void
{
$currentRequest = $this->request->getCurrentRequest();
if (null === $currentRequest) {
return;
}
$type = $currentRequest->query->get('type');
if (null === $type) {
$content = $currentRequest->getContent();
$params = json_decode($content, true);
if (isset($params['isGlobal']) && 1 === (int) $params['isGlobal']) {
$type = 'global';
}
}
if ('global' === $type) {
$em = $eventArgs->getObjectManager();
$resourceNode = $event->getResourceNode();
$globalLink = new ResourceLink();
$globalLink->setCourse(null)
->setSession(null)
->setGroup(null)
->setUser(null);
$alreadyHasGlobalLink = false;
foreach ($resourceNode->getResourceLinks() as $existingLink) {
if (null === $existingLink->getCourse() && null === $existingLink->getSession() &&
null === $existingLink->getGroup() && null === $existingLink->getUser()) {
$alreadyHasGlobalLink = true;
break;
}
}
if (!$alreadyHasGlobalLink) {
$resourceNode->addResourceLink($globalLink);
$em->persist($globalLink);
}
}
}
}

@ -1,166 +0,0 @@
<?php
declare(strict_types=1);
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
/**
* SysCalendar.
*/
#[ORM\Table(name: 'sys_calendar')]
#[ORM\Entity]
class SysCalendar
{
public const COLOR_SYSTEM_EVENT = '#FF0000';
#[ORM\Column(name: 'id', type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
protected ?int $id = null;
#[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)]
protected string $title;
#[ORM\Column(name: 'content', type: 'text', nullable: true)]
protected ?string $content = null;
#[ORM\Column(name: 'start_date', type: 'datetime', nullable: true)]
protected ?DateTime $startDate = null;
#[ORM\Column(name: 'end_date', type: 'datetime', nullable: true)]
protected ?DateTime $endDate = null;
#[ORM\ManyToOne(targetEntity: AccessUrl::class)]
#[ORM\JoinColumn(name: 'access_url_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
protected AccessUrl $url;
#[ORM\Column(name: 'all_day', type: 'integer', nullable: false)]
protected int $allDay;
#[ORM\Column(name: 'color', type: 'string', length: 20, nullable: true)]
protected ?string $color = null;
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
/**
* Get title.
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
/**
* Get content.
*
* @return string
*/
public function getContent()
{
return $this->content;
}
public function setStartDate(DateTime $startDate): self
{
$this->startDate = $startDate;
return $this;
}
/**
* Get startDate.
*
* @return DateTime
*/
public function getStartDate()
{
return $this->startDate;
}
public function setEndDate(DateTime $endDate): self
{
$this->endDate = $endDate;
return $this;
}
/**
* Get endDate.
*
* @return DateTime
*/
public function getEndDate()
{
return $this->endDate;
}
public function setAllDay(int $allDay): self
{
$this->allDay = $allDay;
return $this;
}
/**
* Get allDay.
*
* @return int
*/
public function getAllDay()
{
return $this->allDay;
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
public function getColor(): ?string
{
return $this->color;
}
public function setColor(string $color): self
{
$this->color = $color;
return $this;
}
public function getUrl(): AccessUrl
{
return $this->url;
}
public function setUrl(AccessUrl $url): self
{
$this->url = $url;
return $this;
}
}

@ -0,0 +1,82 @@
<?php
/* For licensing terms, see /license.txt */
declare(strict_types=1);
namespace Chamilo\CoreBundle\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Chamilo\CoreBundle\Entity\ResourceNode;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
class GlobalEventFilter extends AbstractFilter
{
public function __construct(
ManagerRegistry $managerRegistry,
?LoggerInterface $logger = null,
?array $properties = null,
?NameConverterInterface $nameConverter = null
) {
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
}
protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
$isGlobalType = isset($context['filters']['type']) && $context['filters']['type'] === 'global';
$rootAlias = $queryBuilder->getRootAliases()[0];
$resourceNodeAlias = $queryNameGenerator->generateJoinAlias('resourceNode');
$resourceLinkAlias = $queryNameGenerator->generateJoinAlias('resourceLink');
if ($isGlobalType) {
$queryBuilder
->innerJoin("$rootAlias.resourceNode", $resourceNodeAlias)
->innerJoin("$resourceNodeAlias.resourceLinks", $resourceLinkAlias)
->andWhere("$resourceLinkAlias.course IS NULL")
->andWhere("$resourceLinkAlias.session IS NULL")
->andWhere("$resourceLinkAlias.group IS NULL")
->andWhere("$resourceLinkAlias.user IS NULL");
return;
}
if (!$isGlobalType) {
$subQueryBuilder = $queryBuilder->getEntityManager()->createQueryBuilder();
$subRN = $queryNameGenerator->generateJoinAlias('subResourceNode');
$subRL = $queryNameGenerator->generateJoinAlias('subResourceLink');
$subQueryBuilder->select('1')
->from(ResourceNode::class, $subRN)
->innerJoin("$subRN.resourceLinks", $subRL)
->where("$subRL.course IS NULL")
->andWhere("$subRL.session IS NULL")
->andWhere("$subRL.group IS NULL")
->andWhere("$subRL.user IS NULL")
->andWhere("$subRN.id = $rootAlias.resourceNode");
$queryBuilder->andWhere($queryBuilder->expr()->not($queryBuilder->expr()->exists($subQueryBuilder->getDQL())));
}
}
public function getDescription(string $resourceClass): array
{
return [
'type' => [
'property' => 'type',
'type' => 'string',
'required' => false,
'description' => 'Filter events by type. Use "global" to get global events.',
],
];
}
}

@ -0,0 +1,102 @@
<?php
/* For licensing terms, see /license.txt */
declare(strict_types=1);
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
use Chamilo\CoreBundle\Entity\ResourceLink;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
use Chamilo\CourseBundle\Entity\CCalendarEvent;
use DateTime;
use DateTimeZone;
use Doctrine\DBAL\Schema\Schema;
class Version20240323181500 extends AbstractMigrationChamilo
{
public function getDescription(): string
{
return 'Migrate sys_calendar to c_calendar_event';
}
public function up(Schema $schema): void
{
$em = $this->getEntityManager();
$sysCalendars = $this->connection->fetchAllAssociative('SELECT * FROM sys_calendar');
$utc = new DateTimeZone('UTC');
$admin = $this->getAdmin();
foreach ($sysCalendars as $sysCalendar) {
$calendarEvent = $this->createCCalendarEvent(
$sysCalendar['title'] ?: '-',
$sysCalendar['content'],
$sysCalendar['start_date'] ? new DateTime($sysCalendar['start_date'], $utc) : null,
$sysCalendar['end_date'] ? new DateTime($sysCalendar['end_date'], $utc) : null,
(bool) $sysCalendar['all_day'],
$sysCalendar['color'] ?? '',
$admin
);
$em->persist($calendarEvent);
$this->addGlobalResourceLinkToNode($em, $calendarEvent->getResourceNode());
}
$em->flush();
}
private function createCCalendarEvent(
string $title,
string $content,
?DateTime $startDate,
?DateTime $endDate,
bool $allDay,
string $color,
User $creator
): CCalendarEvent {
$calendarEvent = new CCalendarEvent();
$calendarEvent
->setTitle($title)
->setContent($content)
->setStartDate($startDate)
->setEndDate($endDate)
->setAllDay($allDay)
->setColor($color)
->setCreator($creator)
->setResourceName($title)
->setParentResourceNode($creator->getResourceNode()->getId())
;
return $calendarEvent;
}
private function addGlobalResourceLinkToNode($em, $resourceNode): void
{
$globalLink = new ResourceLink();
$globalLink->setCourse(null)
->setSession(null)
->setGroup(null)
->setUser(null);
$alreadyHasGlobalLink = false;
foreach ($resourceNode->getResourceLinks() as $existingLink) {
if (null === $existingLink->getCourse() && null === $existingLink->getSession() &&
null === $existingLink->getGroup() && null === $existingLink->getUser()) {
$alreadyHasGlobalLink = true;
break;
}
}
if (!$alreadyHasGlobalLink) {
$resourceNode->addResourceLink($globalLink);
$em->persist($globalLink);
}
}
public function down(Schema $schema): void
{
// Down migration is not defined, as data migration cannot be easily reverted
}
}

@ -0,0 +1,29 @@
<?php
/* For licensing terms, see /license.txt */
declare(strict_types=1);
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
use Doctrine\DBAL\Schema\Schema;
final class Version20240323222700 extends AbstractMigrationChamilo
{
public function getDescription(): string
{
return 'Remove the sys_calendar table';
}
public function up(Schema $schema): void
{
$this->addSql('DROP TABLE IF EXISTS sys_calendar');
}
public function down(Schema $schema): void
{
if (!$schema->hasTable('sys_calendar')) {
$this->addSql('CREATE TABLE sys_calendar (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, content LONGTEXT DEFAULT NULL, start_date DATETIME DEFAULT NULL, end_date DATETIME DEFAULT NULL, access_url_id INT DEFAULT NULL, all_day INT NOT NULL, color VARCHAR(20) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
}
}
}

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Repository;
use Chamilo\CoreBundle\Entity\SysCalendar;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class SysCalendarRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, SysCalendar::class);
}
}

@ -21,6 +21,7 @@ use Chamilo\CoreBundle\Entity\AbstractResource;
use Chamilo\CoreBundle\Entity\ResourceInterface;
use Chamilo\CoreBundle\Entity\Room;
use Chamilo\CoreBundle\Filter\CidFilter;
use Chamilo\CoreBundle\Filter\GlobalEventFilter;
use Chamilo\CoreBundle\Filter\SidFilter;
use Chamilo\CoreBundle\State\CalendarEventProvider;
use Chamilo\CoreBundle\State\CCalendarEventProcessor;
@ -67,6 +68,7 @@ use Symfony\Component\Validator\Constraints as Assert;
#[ApiFilter(filterClass: DateFilter::class, strategy: 'exclude_null')]
#[ApiFilter(filterClass: CidFilter::class)]
#[ApiFilter(filterClass: SidFilter::class)]
#[ApiFilter(GlobalEventFilter::class, properties: ["type"])]
class CCalendarEvent extends AbstractResource implements ResourceInterface, Stringable
{
public const COLOR_STUDENT_PUBLICATION = '#FF8C00';

Loading…
Cancel
Save