From 57c22c7079d12b5225c6abd622e291bab45b7bde Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Wed, 25 Sep 2024 06:59:40 +0000
Subject: [PATCH] fix export of course dates, fixes #4626

Closes #4626

Merge request studip/studip!3438
---
 app/controllers/course/dates.php  | 127 ++++++++++++++----------------
 app/views/course/dates/export.php |  20 +++--
 lib/classes/CourseDateList.php    |  42 +++++-----
 lib/models/CourseDate.php         |   2 +-
 lib/models/resources/Resource.php |   2 +-
 5 files changed, 92 insertions(+), 101 deletions(-)

diff --git a/app/controllers/course/dates.php b/app/controllers/course/dates.php
index 88a61259a1b..3d744c17ad1 100644
--- a/app/controllers/course/dates.php
+++ b/app/controllers/course/dates.php
@@ -362,52 +362,59 @@ class Course_DatesController extends AuthenticatedController
 
     public function export_action()
     {
-        $themen = CourseTopic::findBySeminar_id($this->course->id);
-
         $termine = $this->course->getAllDatesInSemester()->getSingleDates(true, true, true);
 
         $dates = [];
-
         foreach ($termine as $singledate) {
             if ($singledate instanceof CourseDate) {
-                $tmp_ids = $singledate->getIssueIDs();
-                $title = $description = '';
-                if (is_array($tmp_ids)) {
-                    $title = trim(join("\n", array_map(function ($tid) use ($themen) {return $themen[$tid]->getTitle();}, $tmp_ids)));
-                    $description = trim(join("\n\n", array_map(function ($tid) use ($themen) {return $themen[$tid]->getDescription();}, $tmp_ids)));
-                }
+                $title = trim(implode(
+                    "\n",
+                    $singledate->topics->filter(function (CourseTopic $topic): bool {
+                        return trim($topic->title) !== '';
+                    })->map(function (CourseTopic $topic): string {
+                        return trim($topic->title);
+                    })
+                ));
+                $description = trim(implode(
+                    "\n\n",
+                    $singledate->topics->filter(function (CourseTopic $topic): bool {
+                        return trim($topic->description) !== '';
+                    })->map(function (CourseTopic $topic): string {
+                        return trim($topic->description);
+                    })
+                ));
 
                 $dates[] = [
-                    'date'  => $singledate->toString(),
-                    'title' => $title,
-                    'description' => $description,
-                    'start' => $singledate->getStartTime(),
-                    'related_persons' => $singledate->getRelatedPersons(),
-                    'groups' => $singledate->getRelatedGroups(),
-                    'room' => $singledate->getRoom() ?: $singledate->raum,
-                    'type' => $GLOBALS['TERMIN_TYP'][$singledate->getDateType()]['name']
+                    'date'            => (string) $singledate,
+                    'title'           => $title,
+                    'description'     => $description,
+                    'start'           => $singledate->date,
+                    'related_persons' => $singledate->dozenten,
+                    'groups'          => $singledate->statusgruppen,
+                    'room'            => (string) ($singledate->getRoom() ?? $singledate->raum),
+                    'type'            => $GLOBALS['TERMIN_TYP'][$singledate->date_typ]['name'],
                 ];
-            } elseif ($singledate->getComment()) {
+            } elseif ($singledate instanceof CourseExDate && $singledate->content) {
                 $dates[] = [
-                    'date'  => $singledate->toString(),
-                    'title' => _('fällt aus') . ' (' . _('Kommentar:') . ' ' . $singledate->getComment() . ')',
-                    'description' => '',
-                    'start' => $singledate->getStartTime(),
+                    'date'            => (string) $singledate,
+                    'title'           => _('fällt aus') . ' (' . _('Kommentar:') . ' ' . $singledate->content . ')',
+                    'description'     => '',
+                    'start'           => $singledate->date,
                     'related_persons' => [],
-                    'groups' => [],
-                    'room' => '',
-                    'type' => $GLOBALS['TERMIN_TYP'][$singledate->getDateType()]['name']
+                    'groups'          => [],
+                    'room'            => '',
+                    'type'            => $GLOBALS['TERMIN_TYP'][$singledate->date_typ]['name'],
                 ];
             }
         }
 
         $factory = $this->get_template_factory();
         $template = $factory->open($this->get_default_template('export'));
-
-        $template->set_attribute('dates', $dates);
-        $template->lecturer_count = $this->course->countMembersWithStatus('dozent');
-        $template->group_count = count($this->course->statusgruppen);
-        $content = $template->render();
+        $content = $template->render([
+            'dates'          => $dates,
+            'lecturer_count' => $this->course->countMembersWithStatus('dozent'),
+            'group_count'    => count($this->course->statusgruppen),
+        ]);
 
         $content = mb_encode_numericentity($content, [0x80, 0xffff, 0, 0xffff], 'utf-8');
         $filename = FileManager::cleanFileName($this->course['name'] . '-' . _('Ablaufplan') . '.doc');
@@ -426,13 +433,6 @@ class Course_DatesController extends AuthenticatedController
      */
     public function export_csv_action()
     {
-        $dates = $this->course->getAllDatesInSemester(true, true, true);
-        $raw_issues = CourseTopic::findBySeminar_id($this->course->id);
-        $issues = [];
-        foreach ($raw_issues as $issue) {
-            $issues[$issue->id] = $issue;
-        }
-
         $columns = [
             _('Wochentag'),
             _('Termin'),
@@ -450,9 +450,10 @@ class Course_DatesController extends AuthenticatedController
 
         $data = [$columns];
 
+        $dates = $this->course->getAllDatesInSemester()->getSingleDates(true, true, true);
         foreach ($dates as $date) {
             // FIXME this should not be necessary, see https://develop.studip.de/trac/ticket/8101
-            if ($date->isExTermin() && $date->getComment() == '') {
+            if ($date instanceof CourseExDate && trim($date->content) === '') {
                 continue;
             }
 
@@ -463,49 +464,37 @@ class Course_DatesController extends AuthenticatedController
             $row[] = strftime('%H:%M', $date->end_time);
             $row[] = $date->getTypeName();
 
-            if ($date->isExTermin()) {
-                $row[] = $date->getComment();
+            if ($date instanceof CourseExDate) {
+                $row[] = trim($date->content);
                 $row[] = '';
             } else {
                 $issue = $descr = '';
 
-                foreach ((array) $date->getIssueIDs() as $id) {
-                    $issue .= $issues[$id]->getTitle() . "\n";
-                    $descr .= kill_format($issues[$id]->getDescription()) . "\n";
+                foreach ($date->topics as $topic) {
+                    $issue .= trim($topic->title) . "\n";
+                    $descr .= kill_format($topic->description) . "\n";
                 }
 
                 $row[] = trim($issue);
                 $row[] = trim($descr);
             }
 
-            $related_persons = '';
-
-            if ($date->related_persons) {
-                foreach ($date->related_persons as $user_id) {
-                    $related_persons .= User::find($user_id)->getFullName() . "\n";
-                }
-            }
-
-            $row[] = trim($related_persons);
-
-            $related_groups = '';
-
-            if ($date->related_groups) {
-                foreach ($date->related_groups as $group_id) {
-                    $related_groups .= Statusgruppen::find($group_id)->name . "\n";
-                }
-            }
+            $row[] = implode(
+                "\n",
+                $date->dozenten->map(function (User $user) {
+                    return $user->getFullName();
+                })
+            );
 
-            $row[] = trim($related_groups);
+            $row[] = implode(
+                "\n",
+                $date->statusgruppen->map(function (Statusgruppen $group) {
+                    return $group->name;
+                })
+            );
 
-            $room = null;
-            if ($date->resource_id) {
-                $resource_object = Resource::find($date->resource_id);
-                if ($resource_object) {
-                    $room = $resource_object->getDerivedClassInstance();
-                }
-            }
-            if ($room instanceof Room) {
+            $room = $date->getRoom();
+            if ($room) {
                 $row[] = $room->name;
                 $row[] = $room->description;
                 $row[] = $room->seats;
diff --git a/app/views/course/dates/export.php b/app/views/course/dates/export.php
index 651c4074f08..685748ded9c 100644
--- a/app/views/course/dates/export.php
+++ b/app/views/course/dates/export.php
@@ -1,5 +1,9 @@
-<?
-# Lifter010: TODO
+<?php
+/**
+ * @var array<int, array{date: string, description: string, type: string, start: int, groups: Statusgruppen[], related_persons: User[]}> $dates
+ * @var int $lecturer_count
+ * @var int $group_count
+ */
 ?>
 <html>
 <head>
@@ -7,7 +11,7 @@
 </head>
 <body>
 
-<? if (sizeof($dates)) : ?>
+<? if (count($dates) > 0) : ?>
     <table cellspacing="0" cellpadding="0" border="1" width="100%">
 
         <tr>
@@ -45,18 +49,18 @@
                 <td width="20%"><?= htmlReady($date['title']) ?></td>
                 <td width="20%">
                     <? if (count($date['related_persons']) != $lecturer_count) : ?>
-                        <? foreach ($date['related_persons'] as $key => $user_id) {
-                            echo ($key > 0 ? ", " : "").htmlReady(get_fullname($user_id));
+                        <? foreach ($date['related_persons'] as $key => $user) {
+                            echo ($key > 0 ? ", " : "").htmlReady($user->getFullName());
                         } ?>
                     <? endif ?>
                 </td>
                 <td width="20%">
                     <? if (count($date['groups']) && count($date['groups']) < $group_count) : ?>
-                        <? foreach ($date['groups'] as $key => $statusgruppe_id) {
-                            echo ($key > 0 ? ", " : "").htmlReady(Statusgruppen::find($statusgruppe_id)->name);
+                        <? foreach ($date['groups'] as $key => $statusgruppe) {
+                            echo ($key > 0 ? ", " : "").htmlReady($statusgruppe->name);
                         } ?>
                     <? else : ?>
-                        <?= _("alle") ?>
+                        <?= _('alle') ?>
                     <? endif ?>
                 </td>
                 <td width="20%"><?= htmlReady($date['room']) ?></td>
diff --git a/lib/classes/CourseDateList.php b/lib/classes/CourseDateList.php
index 881d6da90e2..5f6d4b1f10e 100644
--- a/lib/classes/CourseDateList.php
+++ b/lib/classes/CourseDateList.php
@@ -138,40 +138,38 @@ class CourseDateList implements Stringable
         return $this->regular_dates;
     }
 
+    /**
+     * @param bool $include_regular_dates
+     * @param bool $include_cancelled_dates
+     * @param bool $sorted
+     * @return CourseDate[]|CourseExDate[]
+     */
     public function getSingleDates(
         bool $include_regular_dates = false,
         bool $include_cancelled_dates = false,
         bool $sorted = false
     ): array {
+        $all_single_dates = [];
+
         if ($include_regular_dates) {
-            $all_single_dates = [];
             foreach ($this->regular_dates as $regular_date) {
                 foreach ($regular_date->dates as $date) {
                     $all_single_dates[] = $date;
                 }
             }
-            $all_single_dates = array_merge($all_single_dates, $this->single_dates);
-            if ($include_cancelled_dates) {
-                $all_single_dates = array_merge($all_single_dates, $this->cancelled_dates);
-            }
-            if ($sorted) {
-                uasort($all_single_dates, self::compareSingleDatesOrCancelledDates(...));
-            }
-            return $all_single_dates;
-        } else {
-            if ($include_cancelled_dates || $sorted) {
-                $all_single_dates = $this->single_dates;
-                if ($include_cancelled_dates) {
-                    $all_single_dates = array_merge($all_single_dates, $this->cancelled_dates);
-                }
-                if ($sorted) {
-                    uasort($all_single_dates, self::compareSingleDatesOrCancelledDates(...));
-                }
-                return $all_single_dates;
-            } else {
-                return $this->single_dates;
-            }
         }
+
+        $all_single_dates = array_merge($all_single_dates, $this->single_dates);
+
+        if ($include_cancelled_dates) {
+            $all_single_dates = array_merge($all_single_dates, $this->cancelled_dates);
+        }
+
+        if ($sorted) {
+            uasort($all_single_dates, self::compareSingleDatesOrCancelledDates(...));
+        }
+
+        return $all_single_dates;
     }
 
     public function getCancelledDates() : array
diff --git a/lib/models/CourseDate.php b/lib/models/CourseDate.php
index 943bf237855..46610d557ef 100644
--- a/lib/models/CourseDate.php
+++ b/lib/models/CourseDate.php
@@ -235,7 +235,7 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event
     /**
      * Returns the assigned room for this date as an object.
      *
-     * @return Room Either the object or null if no room is assigned
+     * @return Resource Either the object or null if no room is assigned
      */
     public function getRoom()
     {
diff --git a/lib/models/resources/Resource.php b/lib/models/resources/Resource.php
index e1d8b674296..4dae189b3f9 100644
--- a/lib/models/resources/Resource.php
+++ b/lib/models/resources/Resource.php
@@ -2817,7 +2817,7 @@ class Resource extends SimpleORMap implements StudipItem
     /**
      * Converts a Resource object to an object of a specialised resource class.
      *
-     * @return Resource|other An object of a specialised resource class
+     * @return Resource An object of a specialised resource class
      *     or a Resource object, if the resource is a standard resource
      *     with the class_name 'Resource' in its resource category.
      *     If the derived resource class is not available, an instance of
-- 
GitLab