From 41b3733ff6b1dd9213c1fd610b1e2606f1294933 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Wed, 6 Mar 2024 13:15:21 +0000
Subject: [PATCH] fixes #3750

Closes #3750

Merge request studip/studip!2607
---
 app/routes/Events.php                         |  26 ++---
 .../Fix/EndTimeWeeklyRecurredEvents.php       |  40 -------
 cli/studip                                    |   1 -
 lib/classes/JsonApi/SchemaMap.php             |   2 +-
 ...arEvent.php => CalendarDateAssignment.php} |   2 +-
 lib/classes/Privacy.php                       | 106 +++++++++---------
 lib/classes/UserManagement.class.php          |  20 +++-
 lib/extern/ExternPagePersonDetails.php        |  56 ++++-----
 lib/models/User.class.php                     |   2 +-
 9 files changed, 118 insertions(+), 137 deletions(-)
 delete mode 100644 cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php
 rename lib/classes/JsonApi/Schemas/{CalendarEvent.php => CalendarDateAssignment.php} (96%)

diff --git a/app/routes/Events.php b/app/routes/Events.php
index c07c6e69ca5..3612d9a5a37 100644
--- a/app/routes/Events.php
+++ b/app/routes/Events.php
@@ -40,31 +40,31 @@ class Events extends \RESTAPI\RouteMap
             $this->error(401);
         }
 
-        $start = time();
-        $end   = strtotime('+2 weeks', $start);
-        $list = SingleCalendar::getEventList($user_id, $start, $end, null, [], [
-            'CourseEvent',
-            'CourseCancelledEvent',
-            'CourseMarkedEvent',
-        ]);
+        $start = new \DateTime();
+        $end   = clone $start;
+        $end   = $end->add(new \DateInterval('P2W'));
+
+        $list = array_merge(
+            \CalendarCourseDate::getEvents($start, $end, $user_id),
+            \CalendarCourseExDate::getEvents($start, $end, $user_id)
+        );
 
         $json = [];
         $events = array_slice($list, $this->offset, $this->limit); ;
         foreach ($events as $event) {
-            $singledate = new SingleDate($event->id);
 
-            $course_uri = $this->urlf('/course/%s', [htmlReady($event->getSeminarId())]);
+            $course_uri = $this->urlf('/course/%s', [htmlReady($event->course_id)]);
 
             $json[] = [
                 'event_id'    => $event->id,
                 'course'      => $course_uri,
-                'start'       => $event->getStart(),
-                'end'         => $event->getEnd(),
+                'start'       => $event->date,
+                'end'         => $event->end_time,
                 'title'       => $event->getTitle(),
                 'description' => $event->getDescription() ?: '',
                 'categories'  => $event->toStringCategories() ?: '',
-                'room'        => html_entity_decode(strip_tags($singledate->getRoom() ?: $singledate->getFreeRoomText() ?: '')),
-                'canceled'    => $singledate->isHoliday() ?: false,
+                'room'        => $event->getRoomName(),
+                'canceled'    => $event instanceof \CourseExDate || holiday($event->date),
             ];
         }
 
diff --git a/cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php b/cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php
deleted file mode 100644
index 40e7a2b0e02..00000000000
--- a/cli/Commands/Fix/EndTimeWeeklyRecurredEvents.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-namespace Studip\Cli\Commands\Fix;
-
-use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Style\SymfonyStyle;
-
-class EndTimeWeeklyRecurredEvents extends Command
-{
-    protected static $defaultName = 'fix:end-time-weekly-recurred-events';
-
-    protected function configure(): void
-    {
-        $this->setDescription('Fix end time weekly recurred events');
-    }
-
-    protected function execute(InputInterface $input, OutputInterface $output): int
-    {
-        $io = new SymfonyStyle($input, $output);
-        $events = \EventData::findBySQL("rtype = 'WEEKLY' AND IFNULL(count, 0) > 0");
-        $cal_event = new \CalendarEvent();
-
-        $i = 0;
-
-        foreach ($events as $event) {
-            $id = $event->getId();
-            $cal_event->event = $event;
-            $rrule = $cal_event->getRecurrence();
-            $cal_event->setRecurrence($rrule);
-            $event->expire = $cal_event->event->expire;
-            $event->setId($id);
-            $event->store();
-            $i++;
-        }
-        $io->info('Wrong end time of recurrence fixed for ' . $i . ' events.');
-        return Command::SUCCESS;
-    }
-}
diff --git a/cli/studip b/cli/studip
index 9da3b945877..d981bd96884 100755
--- a/cli/studip
+++ b/cli/studip
@@ -36,7 +36,6 @@ $commands = [
     Commands\Fix\Biest7789::class,
     Commands\Fix\Biest7866::class,
     Commands\Fix\Biest8136::class,
-    Commands\Fix\EndTimeWeeklyRecurredEvents::class,
     Commands\Fix\IconDimensions::class,
     Commands\HelpContent\Migrate::class,
     Commands\Migrate\MigrateList::class,
diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php
index a0d21a3af04..97212bc6552 100644
--- a/lib/classes/JsonApi/SchemaMap.php
+++ b/lib/classes/JsonApi/SchemaMap.php
@@ -18,7 +18,7 @@ class SchemaMap
             \BlubberStatusgruppeThread::class => Schemas\BlubberStatusgruppeThread::class,
             \BlubberThread::class => Schemas\BlubberThread::class,
 
-            \CalendarDateAssignment::class => Schemas\CalendarEvent::class,
+            \CalendarDateAssignment::class => Schemas\CalendarDateAssignment::class,
             \ConsultationBlock::class => Schemas\ConsultationBlock::class,
             \ConsultationBooking::class => Schemas\ConsultationBooking::class,
             \ConsultationSlot::class => Schemas\ConsultationSlot::class,
diff --git a/lib/classes/JsonApi/Schemas/CalendarEvent.php b/lib/classes/JsonApi/Schemas/CalendarDateAssignment.php
similarity index 96%
rename from lib/classes/JsonApi/Schemas/CalendarEvent.php
rename to lib/classes/JsonApi/Schemas/CalendarDateAssignment.php
index fc5a1d81a47..3aec6eadb0a 100644
--- a/lib/classes/JsonApi/Schemas/CalendarEvent.php
+++ b/lib/classes/JsonApi/Schemas/CalendarDateAssignment.php
@@ -5,7 +5,7 @@ namespace JsonApi\Schemas;
 use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
 use Neomerx\JsonApi\Schema\Link;
 
-class CalendarEvent extends SchemaProvider
+class CalendarDateAssignment extends SchemaProvider
 {
     const TYPE = 'calendar-events';
     const REL_OWNER = 'owner';
diff --git a/lib/classes/Privacy.php b/lib/classes/Privacy.php
index 670f3023409..38e6e80c4af 100644
--- a/lib/classes/Privacy.php
+++ b/lib/classes/Privacy.php
@@ -18,69 +18,69 @@ class Privacy
      */
     private static $privacy_classes = [
         'core' => [
-            'User',
-            'DataField',
-            'DatafieldEntryModel',
-            'UserConfig',
-            'HelpTourUser',
-            'Grading\Instance',
-            'LogEvent',
+            User::class,
+            DataField::class,
+            DatafieldEntryModel::class,
+            UserConfig::class,
+            HelpTourUser::class,
+            Grading\Instance::class,
+            LogEvent::class,
         ],
         'date' => [
-            'CalendarEvent',
-            'CalendarDate',
-            'CourseDate',
-            'CourseExDate',
+            CalendarDateAssignment::class,
+            CalendarDate::class,
+            CourseDate::class,
+            CourseExDate::class,
         ],
         'message' => [
-            'BlubberThread',
-            'BlubberComment',
-            'StudipNews',
-            'StudipComment',
-            'Message',
-            'MessageUser',
+            BlubberThread::class,
+            BlubberComment::class,
+            StudipNews::class,
+            StudipComment::class,
+            Message::class,
+            MessageUser::class,
         ],
         'content' => [
-            'FileRef',
-            'ForumEntry',
-            'WikiPage',
-            'Courseware\Unit',
-            'Courseware\StructuralElement',
-            'Courseware\StructuralElementComment',
-            'Courseware\StructuralElementFeedback',
-            'Courseware\TaskGroup',
-            'Courseware\TaskFeedback',
-            'Courseware\Bookmark',
-            'Courseware\Container',
-            'Courseware\Block',
-            'Courseware\BlockComment',
-            'Courseware\BlockFeedback',
-            'Courseware\UserDataField',
-            'Courseware\UserProgress'
+            FileRef::class,
+            ForumEntry::class,
+            WikiPage::class,
+            Courseware\Unit::class,
+            Courseware\StructuralElement::class,
+            Courseware\StructuralElementComment::class,
+            Courseware\StructuralElementFeedback::class,
+            Courseware\TaskGroup::class,
+            Courseware\TaskFeedback::class,
+            Courseware\Bookmark::class,
+            Courseware\Container::class,
+            Courseware\Block::class,
+            Courseware\BlockComment::class,
+            Courseware\BlockFeedback::class,
+            Courseware\UserDataField::class,
+            Courseware\UserProgress::class,
         ],
         'quest' => [
-            'Evaluation',
-            'Questionnaire',
-            'QuestionnaireAnswer',
-            'QuestionnaireAnonymousAnswer',
-            'QuestionnaireAssignment',
-            'eTask\Attempt',
-            'eTask\Response',
-            'eTask\Task',
-            'eTask\Test',
+            Evaluation::class,
+            Questionnaire::class,
+            QuestionnaireAnswer::class,
+            QuestionnaireAnonymousAnswer::class,
+            QuestionnaireAssignment::class,
+            eTask\Attempt::class,
+            eTask\Response::class,
+            eTask\Task::class,
+            eTask\Test::class,
         ],
         'membership' => [
-            'Course',
-            'CourseMember',
-            'AdmissionApplication',
-            'ArchivedCourse',
-            'ArchivedCourseMember',
-            'Statusgruppen',
-            'StatusgruppeUser',
-            'InstituteMember',
-            'UserStudyCourse',
-            'Fach',
-            'Abschluss',
+            Course::class,
+            CourseMember::class,
+            AdmissionApplication::class,
+            ArchivedCourse::class,
+            ArchivedCourseMember::class,
+            Statusgruppen::class,
+            StatusgruppeUser::class,
+            InstituteMember::class,
+            UserStudyCourse::class,
+            Fach::class,
+            Abschluss::class,
         ],
         'plugins' => [
         ],
diff --git a/lib/classes/UserManagement.class.php b/lib/classes/UserManagement.class.php
index 3efae350047..c831b855a4a 100644
--- a/lib/classes/UserManagement.class.php
+++ b/lib/classes/UserManagement.class.php
@@ -1161,7 +1161,25 @@ class UserManagement
 
         // delete all private appointments of this user
         if (Config::get()->CALENDAR_ENABLE) {
-            $count = CalendarEvent::deleteBySQL('range_id = ?', [$user_id]);
+            // delete private appointments (omit group appointments)
+            $count = CalendarDate::deleteBySQL(
+                '`id` IN (
+                    SELECT `id`
+                    FROM (
+                        SELECT `id`, COUNT(*)
+                        FROM `calendar_dates`
+                        JOIN `calendar_date_assignments`
+                          ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id`
+                        WHERE `calendar_dates`.`author_id` = :user_id
+                        GROUP BY `id`
+                        HAVING COUNT(*) = 1
+                        ORDER BY NULL
+                    ) AS `cal_date_delete`
+                )',
+                [':user_id' => $user_id]
+            );
+            // delete assignments to group appointments
+            $count += CalendarDateAssignment::deleteBySQL('`range_id` = ?', [$user_id]);
             if ($count) {
                 $msg .= 'info§' . sprintf(_('%s Einträge aus den Terminen gelöscht.'), $count) . '§';
             }
diff --git a/lib/extern/ExternPagePersonDetails.php b/lib/extern/ExternPagePersonDetails.php
index a71124e0c55..49c754c16a1 100644
--- a/lib/extern/ExternPagePersonDetails.php
+++ b/lib/extern/ExternPagePersonDetails.php
@@ -360,44 +360,48 @@ class ExternPagePersonDetails extends ExternPage
             return [];
         }
 
-        $list_start = new DateTimeImmutable();
-        $list_end = $list_start->modify('+ 7 days');
-        $events = SingleCalendar::getEventList(
+        $list_start = new DateTime();
+        $list_end = clone $list_start;
+        $list_end = $list_end->add(new DateInterval('P7D'));
+
+        $assigned_events = CalendarDateAssignment::getEvents(
+            $list_start,
+            $list_end,
             $user->id,
-            $list_start->getTimestamp(),
-            $list_end->getTimestamp(),
-            null,
-            ['class' => 'PUBLIC'],
-            ['CalendarEvent']
+            ['PUBLIC']
         );
 
         $content['APPOINTMENTS_START'] = $list_start->getTimestamp();
         $content['APPOINTMENTS_END']   = $list_end->getTimestamp();
         $content_events = [];
-        if (!empty($events)) {
-            foreach ($events as $event) {
-                if ($event->isDayEvent()) {
-                    $date = date('d.m.Y', $event->getStart()) . ' (' . _('ganztägig') . ')';
+        if (!empty($assigned_events)) {
+            foreach ($assigned_events as $assigned_event) {
+                $event = $assigned_event->calendar_date;
+                if (!$event) {
+                    continue;
+                }
+                if ($event->isWholeDay()) {
+                    $date = date('d.m.Y', $event->begin) . ' (' . _('ganztägig') . ')';
                 } else {
-                    $date = date('d.m.Y G:H:s', $event->getStart());
-                    if (date('dmY', $event->getStart()) === date('dmY', $event->getEnd())) {
-                        $date .= date('d.m.Y G:H:s', $event->getEnd());
+                    $date = date('d.m.Y G:i:s', $event->begin);
+                    if (date('dmY', $event->begin) === date('dmY', $event->end)) {
+                        $date .= date('d.m.Y G:i:s', $event->end);
                     } else {
-                        $date .= ' - ' . date('d.m.Y G:H:s', $event->getEnd());
+                        $date .= ' - ' . date('d.m.Y G:i:s', $event->end);
                     }
                 }
                 $content_events[] = [
                     'DATE'            => $date,
-                    'TITLE'           => $event->getTitle(),
-                    'DESCRIPTION'     => $event->getDescription(),
-                    'LOCATION'        => $event->getLocation(),
-                    'RECURRENCE'      => $event->toStringRecurrence(),
-                    'CATEGORY'        => $event->toStringCategories(),
-                    'PRIORITY'        => $event->toStringPriority(),
-                    'START'           => date('d.m.Y G:H:s', $event->getStart()),
-                    'END'             => date('d.m.Y G:H:s', $event->getEnd()),
-                    'TIMESTAMP_START' => $event->getStart(),
-                    'TIMESTAMP_END'   => $event->getEnd(),
+                    'TITLE'           => $event->title,
+                    'DESCRIPTION'     => $event->description,
+                    'LOCATION'        => $event->location,
+                    'RECURRENCE'      => $event->getRepetitionAsString(),
+                    'CATEGORY'        => $event->getCategoryAsString(),
+                    'PRIORITY'        => _('Keine Angabe'),
+                    'START'           => date('d.m.Y G:i:s', $event->begin),
+                    'END'             => date('d.m.Y G:i:s', $event->end),
+                    'TIMESTAMP_START' => $event->begin,
+                    'TIMESTAMP_END'   => $event->end,
                 ];
             }
         }
diff --git a/lib/models/User.class.php b/lib/models/User.class.php
index 5f1d6a0e3f0..dfd4d18a890 100644
--- a/lib/models/User.class.php
+++ b/lib/models/User.class.php
@@ -780,7 +780,7 @@ class User extends AuthUserMd5 implements Range, PrivacyObject, Studip\Calendar\
 
         // Non-private dates.
         if (Config::get()->CALENDAR_ENABLE) {
-            $dates = CalendarEvent::countBySql('range_id = ?', [$this->id]);
+            $dates = CalendarDateAssignment::countBySql('range_id = ?', [$this->id]);
         } else {
             $dates = [];
         }
-- 
GitLab