diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d97565e9e8bc0b72717a9322759fe93a5387bea9..7f8d268dc36796637e0b837f4f516f398918b024 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -190,7 +190,7 @@ lint-css:
     - *mkdir-reports
     - npm install
       --no-save --no-audit --no-fund
-      --loglevel=error 
+      --loglevel=error
       stylelint@^14.9.1
       stylelint-config-standard@^26.0.0
       stylelint-formatter-gitlab
@@ -198,7 +198,7 @@ lint-css:
     - npx
       stylelint
       --cache --cache-location $CACHE_LOCATION
-      --custom-formatter=node_modules/stylelint-formatter-gitlab 
+      --custom-formatter=node_modules/stylelint-formatter-gitlab
       --output-file $STYLELINT_CODE_QUALITY_REPORT
       resources/assets/stylesheets
   artifacts:
@@ -223,12 +223,12 @@ phpstan:
     - *mkdir-caches
     - *mkdir-reports
     - *install-composer
-    - 'echo "includes:\n    - phpstan.neon.dist\n\nparameters:\n   tmpDir: $PHPSTAN_CACHE_PATH" > phpstan.neon'
+    - 'echo -e "includes:\n    - phpstan.neon.dist\n\nparameters:\n   tmpDir: $PHPSTAN_CACHE_PATH" > phpstan.neon'
   script:
     - php
-      composer/bin/phpstan analyse 
-      --memory-limit=1G 
-      --no-progress 
+      composer/bin/phpstan analyse
+      --memory-limit=1G
+      --no-progress
       --level=$PHPSTAN_LEVEL
       --error-format=gitlab > $PHPSTAN_CODE_QUALITY_REPORT
   after_script:
@@ -414,7 +414,7 @@ packaging:
     expire_in: never
 
 build_image:
-  image: 
+  image:
     name: gcr.io/kaniko-project/executor:debug
     entrypoint: [""]
   stage: build
diff --git a/ChangeLog.md b/ChangeLog.md
index 7e15f471fc0121deea5c70592c69943ee4554b1a..78d4b2d8329c76d4a881cbcf5e86aca99b4b59ea 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,110 @@
+# 03.09.2024 v 5.5.2
+
+https://gitlab.studip.de/studip/studip/-/issues?milestone_title=Stud.IP+5.5.2&state=all
+- Courseware: Fokusmodus bietet zwar Bearbeiten an, bricht dann aber ab/beendet bearbeiten [#1461]
+- Wiki: Das automatische Speichern ist standardmäßig angeschaltet [#4166]
+- Courseware: TypeError beim Weiterschalten der Seite [#4192]
+- „Anmerkungen aktivieren“ funktioniert nicht [#4400]
+- Courseware: in der neuen Block-Auswahl-Liste ist für mich unten der letzte Eintrag nicht richtig sichtbar [#4401]
+- Courseware: Anzeige einer Sperre beim Löschen einer Seite [#4406]
+- Courseware: Fehler in der JSON-API durch Warnungen in PHP 8 [#4408]
+- Wiki: Neue Seiten zeigt falsche Autorenzuordnung an [#4410]
+- Wiki: InvalidArgumentException beim Anklicken eines Links [#4411]
+- Blubber: Eingabefeld wächst bei jedem eingegeben Zeichen [#4413]
+- Blubber-Thread: Das Lade-Icon dreht sich dauerhaft [#4414]
+- Courseware: Quelltext-Block zeigt kein Highlighting mehr an [#4439]
+- Probleme beim Eintragen von Terminen [#4484]
+- Fehler beim Import von Terminen [#4485]
+- Wiki ermöglicht, alte Zwischenversionen der Seite zu löschen [#4486]
+- Suche nach Matrikelnummer in der Nutzerverwaltung nicht möglich [#4497]
+- Update verändert erste Seite einer Courseware [#4499]
+- Externe Seiten: Fatal error: Allowed memory size exhausted [#4518]
+- PHP8: Warnungen in JSON-API Route des Dateibereichs [#4519]
+- Wiki: Keine Warnung beim Verlassen der Seite bei ungespeicherten Änderungen [#4522]
+- PHP8 - Warnungen im WIKI [#4535]
+- PHP8 - Warnungen im CourseMember [#4536]
+- CalendarDate::garbageCollect() wird nicht verwendet [#4539]
+- Wiki: Warnung beim Speichern der Seite [#4543]
+
+# 03.09.2024 v 5.4.5
+
+https://gitlab.studip.de/studip/studip/-/issues?milestone_title=Stud.IP+5.4.5&state=all
+- Verwaltung von Veranstaltungen: Spalte "Inhalte" bricht um [#3078]
+- Galerie Block Gitter Layout defekt [#3184]
+- Courseware: Export des Lernmaterials ist kaputt, wenn ein Bild aus dem Bilderpool gewählt wurde [#3743]
+- Admin-VA CSV-Export ist unsortiert [#3994]
+- Bei einem leeren Blubber-Thread dreht sich dauerhaft das Lade-Icon am linken Rand des Hauptbereiches [#4191]
+- JSON-API controller RangeTreeIndex StudyAreasIndex haben eine irreführende und zu kurze Beschreibung im DocBlock [#4396]
+- JSON-API Controller RangeTreeIndex ist nicht eingebunden [#4397]
+- Veranstaltungsverwaltung: ursprünglicher Werkzeugname zeigt nichts an [#4419]
+- Werkzeuge: Einstellung für Sichtbarkeit wird auch in Einrichtungen angeboten [#4420]
+- kompakte/mobile Navigation: Verwaltung ist ganz unten im Menü [#4422]
+- "Mehrere Gruppen anlegen" wirft Fehler bei Einzelterminen wegen fehlender Description [#4449]
+- Adminverwaltungsseite für Veranstaltungen: Zurücksetzen der Suche setzt diese nicht unmittelbar zurück [#4460]
+- Werkzeuge: Zusätzliche Kategorie "Sonstige" [#4469]
+- Das Feedback-Modul ist kaputt [#4475]
+- Hauptordner in Veranstaltung gehört Studierendem statt Dozierendem [#4479]
+- Veranstaltungs-Stundenplan zeigt keine Treffer, wenn auf der Veranstaltungsverwaltungsseite ein Suchbegriff eingegeben wurde [#4515]
+- PHP8 - Warnungen auf der Teilnehmerseite [#4533]
+- PHP8 - Warnungen in den Raumberechtigungen [#4538]
+
+# 03.09.2023 v 5.3.8
+
+https://gitlab.studip.de/studip/studip/-/issues?milestone_title=Stud.IP+5.3.8&state=all
+- Courseware: Falsche Berechtigungen für nicht im Kurs eingetragene Personen [#889]
+- Globale Suche/Schnellsuche: Graue Texte entfernen [#1382]
+- WYSIWYG: Unterschiedliche Darstellung Editor/Lesemodus [#1607]
+- Zusammenführen von Accounts nimmt keine Courseware-Inhalte mit [#2492]
+- Beschreibung des Lernmaterials nur beim Import nicht zwingend erforderlich. [#2776]
+- Aktion "Inhalt kopieren" bei einer abgegebenen Aufgabe funktioniert nicht [#2805]
+- Courseware Dateiordner Block Darstellungsfehler [#2823]
+- Courseware: Kopieren von Abschnitten generiert `null`-Einträge in der Payload [#2842]
+- Verschieben von Blöcken direkt nach dem Anlegen funktioniert nicht [#3000]
+- Exportierter Kalender kann nicht in einem anderen Account importiert werden [#3103]
+- Beschreibung im Block Karriere wird nicht angeziegt [#3144]
+- Courseware: "Lerninhalte kopieren"-Wizard kann verschoben werden, Dropdownmenü bleibt sticky [#3174]
+- Courseware: "Error: Undefined data type" im Block "Ziele" [#3181]
+- DOMDocument::loadHTML(): Empty string supplied as input [#3194]
+- Courseware: verlängerte Aufgabe kann von Studi nicht mehr abgegeben werden (Aktionsmenü fehlt) [#3269]
+- Shibboleth: Logout beendet Session nicht [#3624]
+- Courseware: PDF-Export exportiert "unsichtbare" Blöcke [#3726]
+- Implementierungen des Serializable-Interfaces erzeugen unter PHP 8.1 Deprecation Warnings [#4135]
+- PHP 8: Warnungen in JSON-API Route der Courseware [#4268]
+- Memcached-Cache: Änderung der Einstellungen wird nicht aktiv [#4284]
+- Memcached-Cache: Eintragen von mehr als einem Server führt zu einem nicht funktionieren System [#4286]
+- Suche findet eigene Veranstaltungen nicht immer [#4384]
+- PHP8 Warning bei Export von Ablaufplan [#4399]
+- Courseware: Quelltext-Block rendert HTML statt es mit Syntaxhervorhebung darzustellen [#4437]
+- Evaluationsblock trotz global deaktivierter Evaluationsfunktion in Veranstaltungskurzinfo sichtbar [#4440]
+- PHP8 Fehler in AuthPlugin: method_exists(): Argument #2 ($method) must be of type string, Closure given [#4442]
+- Raumverwaltung: Dokumente zu Räumen haben "Größe" als Standardsortierung [#4450]
+- Fragebogen: Nicht-Pflicht Antwort wird auf erste Option gesetzt [#4452]
+- Verwaltungsfunktionen von Einrichtungen sind auch für nicht berechtigte Nutzer sichtbar [#4458]
+- LTI-Schnittstelle: Parameter lis_course_section_sourcedid ergänzen [#4461]
+- Courseware: keine Anzeige bei Lernmaterialien [#4466]
+- Regelmäßige Termine lassen sich nicht bearbeiten [#4471]
+- Fehler "Only variables should be passed by reference" in der Übersicht der Module [#4472]
+- Bearbeiten von vielen Modulen ist kompliziert [#4473]
+- PHP8-Warning beim Login via SSO [#4480]
+- „Neue Nachricht schreiben“: Pflichtfelder werden nicht barrierearm ausgezeichnet [#4488]
+- Studiengruppen: Liste der teilnehmenden Personen als Gallerie ist zu starr [#4489]
+- PHP-Warnungen in den Umfragen [#4492]
+- Ausgabe der Ankündigungen auf externen Seiten ist defekt [#4496]
+- Zugriffsbeschränkung der SOAP/XML-RPC Web-Services funktioniert nicht mit IPv6 [#4500]
+- Veranstaltung: Übersichtsseite hat Textstrings mit Positionsangaben und bei Evaluationen fehlt die Icon-Bezeichnung [#4505]
+- Standardeinrichtung beim Anlegen von LV ist für Dozenten nicht vorgewählt [#4506]
+- JSUpdater leert den Flash [#4507]
+- CoreScm: Entfer nicht verwendete Methode [#4508]
+- PHP8-Warnungen bei Raumzeit [#4521]
+- Eintragen von IPv6 Bereich bei den Webservices nicht möglich [#4524]
+- Links in formatiertem Text haben falsche vertikale Position [#4531]
+- PHP8 - Warnungen in den Institute-Klasse [#4534]
+- StudipArrayObject unserialize wirft fehler [#4537]
+- Fehlende Schriftarten ergänzen [#4542]
+- PHP8 - Warnungen bei widget-layout [#4544]
+- CSRF-Protection wird an diversen Stellen falsch verwendet [#4545]
+- CSRF-Protection wird an noch mehr Stellen falsch verwendet [#4548]
+
 # 25.07.2024 v 5.5.1
 
 https://gitlab.studip.de/studip/studip/-/issues?milestone_title=Stud.IP+5.5.1&state=all
diff --git a/VERSION b/VERSION
index afd3faff57349c97db42f7ae932a9f511e466e56..c7fd932f1ac4cbd84693d66c51c61f9883fa3491 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-RELEASE 5.5.1
+RELEASE 5.5.2
diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php
index 3d920c3d8c9f3caa8dc945facd73bcdb3d982a2e..d71bcece7b8d60738741a728afb67fd7ad451df2 100644
--- a/app/controllers/course/basicdata.php
+++ b/app/controllers/course/basicdata.php
@@ -594,7 +594,8 @@ class Course_BasicdataController extends AuthenticatedController
 
     public function add_member_action($course_id, $status = 'dozent')
     {
-        CSRFProtection::verifyUnsafeRequest();
+        // We don't need to check the csrf protection at this point since it
+        // is already checked by the multiperson search endpoint
 
         // load MultiPersonSearch object
         $mp = MultiPersonSearch::load("add_member_{$status}{$course_id}");
diff --git a/app/views/admin/user/_course_files.php b/app/views/admin/user/_course_files.php
index af623963da5658b2d285d0e916e1630e908501d6..caac81ad8d3e2d95a6718675d1356154495a26ad 100644
--- a/app/views/admin/user/_course_files.php
+++ b/app/views/admin/user/_course_files.php
@@ -1,7 +1,7 @@
 <?php
 /**
  * @var Admin_UserController $controller
- * @var array $course_files
+ * @var array<string, array<string, array{course: Course, files: int}>> $course_files
  * @var array $params
  * @var User $user
  */
@@ -12,8 +12,8 @@
             <?= _('Dateiübersicht Veranstaltungen') ?>
         </h1>
     </header>
-    <? foreach ($course_files as $semester_name => $file_date) : ?>
-        <article id="<?= $semester_name ?>" class="<?= ContentBoxHelper::classes($semester_name) ?>">
+    <? foreach ($course_files as $semester_name => $file_data) : ?>
+        <article id="<?= htmlReady($semester_name) ?>" class="<?= ContentBoxHelper::classes($semester_name) ?>">
             <header>
                 <h1>
                     <a href="<?= ContentBoxHelper::href($semester_name) ?>">
@@ -40,11 +40,17 @@
                         </tr>
                     </thead>
                     <tbody>
-                        <? foreach ($file_date as $data): ?>
+                        <? foreach ($file_data as $data): ?>
                             <tr>
-                                <td><?= htmlReady($data['course']->veranstaltungsnummer) ?></td>
                                 <td>
-                                    <?= htmlReady($data['course']->name) ?>
+                                    <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $data['course']->id]) ?>">
+                                        <?= htmlReady($data['course']->veranstaltungsnummer) ?>
+                                    </a>
+                                </td>
+                                <td>
+                                    <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $data['course']->id]) ?>">
+                                        <?= htmlReady($data['course']->name) ?>
+                                    </a>
                                 </td>
                                 <td>
                                     <?= htmlReady($data['course']->getSemType()['name'])?>
@@ -57,23 +63,23 @@
                                     <? endif ?>
                                 </td>
                                 <td class="actions">
-                                    <? if ($data['files']) : ?>
-                                        <?
-                                        $actionMenu = ActionMenu::get()->setContext($data['course']->name);
-                                        $actionMenu->addLink($controller->url_for('admin/user/list_files/' . $user['user_id'] . '/' . $data['course']->id, $params),
-                                                _('Dateien auflisten'),
-                                                Icon::create('folder-full', 'clickable'),
-                                                ['data-dialog' => 'size=50%']);
-                                        $actionMenu->addLink($controller->url_for('admin/user/download_user_files/' . $user['user_id'] . '/' . $data['course']->id),
-                                                _('Dateien als ZIP herunterladen'),
-                                                Icon::create('download', 'clickable'));
-
-                                        ?>
-
-                                        <?= $actionMenu->render() ?>
-                                    <? endif ?>
+                                <? if ($data['files']) : ?>
+                                    <?= ActionMenu::get()
+                                        ->setContext($data['course']->name)
+                                        ->addLink(
+                                            $controller->list_filesURL($user->id, $data['course']->id, $params),
+                                            _('Dateien auflisten'),
+                                            Icon::create('folder-full'),
+                                            ['data-dialog' => 'size=50%']
+                                        )
+                                        ->addLink(
+                                            $controller->download_user_filesURL($user->id, $data['course']->id),
+                                            _('Dateien als ZIP herunterladen'),
+                                            Icon::create('download')
+                                        )
+                                    ?>
+                                <? endif ?>
                                 </td>
-
                             </tr>
                         <? endforeach; ?>
                     </tbody>
diff --git a/app/views/admin/user/_institute_files.php b/app/views/admin/user/_institute_files.php
index 78085dfb855fca019825d58c3da8993a300b3cfd..665e3c6ff483e7c0ae1661c407aa37b3e5be4005 100644
--- a/app/views/admin/user/_institute_files.php
+++ b/app/views/admin/user/_institute_files.php
@@ -1,7 +1,7 @@
 <?php
 /**
  * @var Admin_UserController $controller
- * @var Institute[] $institutes
+ * @var array<int, array{Institut_id: string, Name: string, files: int}> $institutes
  * @var User $user
  * @var array $params
  */
@@ -32,30 +32,34 @@
                 <? foreach ($institutes as $institute): ?>
                     <tr>
                         <td>
-                            <?= htmlReady($institute['Name']) ?>
+                            <a href="<?= URLHelper::getLink('dispatch.php/institute/overview', ['auswahl' => $institute['Institut_id']]) ?>">
+                                <?= htmlReady($institute['Name']) ?>
+                            </a>
                         </td>
                         <td>
-                            <? if ((int)$institute['files']) : ?>
+                            <? if ($institute['files']) : ?>
                                 <?= sprintf('%u %s', $institute['files'], _('Dokumente')) ?>
                             <? else : ?>
                                 -
                             <? endif ?>
                         </td>
                         <td class="actions">
-                            <? if ($institute['files']) : ?>
-                                <?
-                                $actionMenu = ActionMenu::get()->setContext($institute['Name']);
-                                $actionMenu->addLink($controller->url_for('admin/user/list_files/' . $user['user_id'] . '/' . $institute['Institut_id'] , $params),
+                        <? if ($institute['files']) : ?>
+                            <?= ActionMenu::get()
+                                ->setContext($institute['Name'])
+                                ->addLink(
+                                    $controller->list_filesURL($user->id, $institute['Institut_id'], $params),
                                     _('Dateien auflisten'),
                                     Icon::create('folder-full'),
-                                    ['data-dialog' => 'size=50%']);
-                                $actionMenu->addLink($controller->url_for('admin/user/download_user_files/' . $user['user_id'] . '/' . $institute['Institut_id']),
+                                    ['data-dialog' => 'size=50%']
+                                )
+                                ->addLink(
+                                    $controller->download_user_filesURL($user->id, $institute['Institut_id']),
                                     _('Dateien als ZIP herunterladen'),
-                                    Icon::create('download'));
-
-                                ?>
-                                <?= $actionMenu->render() ?>
-                            <? endif ?>
+                                    Icon::create('download')
+                                )
+                            ?>
+                        <? endif ?>
                         </td>
                     </tr>
                 <? endforeach; ?>
diff --git a/app/views/contact/editGroup.php b/app/views/contact/editGroup.php
index 914b2032fe032156d44aa11bfc2d1030532d2547..988a80ab245800f10fd21c451bb5b0e6f6b0724a 100644
--- a/app/views/contact/editGroup.php
+++ b/app/views/contact/editGroup.php
@@ -1,5 +1,5 @@
 <form class="default" method="post" action="<?= $controller->link_for('contact/editGroup/' . $group->id) ?>">
-    <? CSRFProtection::tokenTag() ?>
+    <?= CSRFProtection::tokenTag() ?>
     <fieldset>
         <legend class="hide-in-dialog">
             <? if ($group->isNew()) : ?>
diff --git a/app/views/resources/admin/user_permissions.php b/app/views/resources/admin/user_permissions.php
index ec7af93876e571e582244442ccd7354970949553..5dedeb9b52cb01ec864cf0687eb7f947d6ab564e 100644
--- a/app/views/resources/admin/user_permissions.php
+++ b/app/views/resources/admin/user_permissions.php
@@ -200,6 +200,9 @@
                 </thead>
                 <tbody>
                     <? foreach ($permissions as $permission): ?>
+                        <?
+                        $resource = $permission->resource->getDerivedClassInstance();
+                        ?>
                         <tr>
                             <td>
                                 <input type="checkbox" name="resource_ids[]"
@@ -207,7 +210,7 @@
                                        title="<?= htmlReady(sprintf(_('Berechtigung für %s auswählen'), $resource)) ?>">
                             </td>
                             <td>
-                                <?= htmlReady($permission->resource->getDerivedClassInstance()) ?>
+                                <?= htmlReady($resource) ?>
                             </td>
                             <td>
                                 <?= htmlReady($permission->perms) ?>
diff --git a/config/config_defaults.inc.php b/config/config_defaults.inc.php
index f29f385980038c937f52a634b10f8ff5c51974ee..d7caea7725a287d2a4f1f51a4acd55a99aeaf811 100644
--- a/config/config_defaults.inc.php
+++ b/config/config_defaults.inc.php
@@ -366,15 +366,19 @@ $STUDIP_AUTH_CONFIG_LTI = [
     ]
 ];
 
-$STUDIP_AUTH_CONFIG_SHIB = array("session_initiator" => "https://sp.studip.de/Shibboleth.sso/WAYF/DEMO",
-                                        "validate_url" => "https://sp.studip.de/auth/studip-sp.php",
-                                        "local_domain" => "studip.de",
-                                        "user_data_mapping" =>
-                                                array(  "auth_user_md5.username" => array("callback" => "dummy", "map_args" => ""),
-                                                        "auth_user_md5.password" => array("callback" => "dummy", "map_args" => ""),
-                                                        "auth_user_md5.Vorname" => array("callback" => "getUserData", "map_args" => "givenname"),
-                                                        "auth_user_md5.Nachname" => array("callback" => "getUserData", "map_args" => "surname"),
-                                                        "auth_user_md5.Email" => array("callback" => "getUserData", "map_args" => "email")));
+$STUDIP_AUTH_CONFIG_SHIB = [
+    'session_initiator' => 'https://sp.studip.de/Shibboleth.sso/WAYF/DEMO',
+    'validate_url'      => 'https://sp.studip.de/auth/studip-sp.php',
+    'logout_url'        => 'https://sp.studip.de/Shibboleth.sso/Logout',
+    'local_domain'      => 'studip.de',
+    'user_data_mapping' => [
+        'auth_user_md5.username' => ['callback' => 'dummy', 'map_args' => ''],
+        'auth_user_md5.password' => ['callback' => 'dummy', 'map_args' => ''],
+        'auth_user_md5.Vorname'  => ['callback' => 'getUserData', 'map_args' => 'givenname'],
+        'auth_user_md5.Nachname' => ['callback' => 'getUserData', 'map_args' => 'surname'],
+        'auth_user_md5.Email'    => ['callback' => 'getUserData', 'map_args' => 'email']
+    ],
+];
 
 $STUDIP_AUTH_CONFIG_IP = array('allowed_users' =>
     array ('root' => array('127.0.0.1', '::1')));
diff --git a/lib/bootstrap.php b/lib/bootstrap.php
index 9ad9f60179d371758d07306e39df6d52c2dabb56..2f2578735eda36ed2f6e1e0fa271726c1f2fbaa8 100644
--- a/lib/bootstrap.php
+++ b/lib/bootstrap.php
@@ -13,7 +13,7 @@
 const DEFAULT_ENV = 'production';
 
 //software version - please leave it as it is!
-$SOFTWARE_VERSION = '5.5.1';
+$SOFTWARE_VERSION = '5.5.2';
 
 // Store startup time
 $STUDIP_STARTUP_TIME = microtime(true);
diff --git a/lib/classes/JsonApi/Routes/Blubber/ThreadsUpdate.php b/lib/classes/JsonApi/Routes/Blubber/ThreadsUpdate.php
index c93afb6c4020935e4e0c6b071a7859c5cf724cfe..3770d2bf96d32c2197efe0286cdcf42d977a62c0 100644
--- a/lib/classes/JsonApi/Routes/Blubber/ThreadsUpdate.php
+++ b/lib/classes/JsonApi/Routes/Blubber/ThreadsUpdate.php
@@ -68,7 +68,7 @@ class ThreadsUpdate extends JsonApiController
         return $this->getContentResponse($thread);
     }
 
-    protected function validateResourceDocument($json)
+    protected function validateResourceDocument($json, $data)
     {
         if (self::arrayHas($json, 'data.attributes.visited-at')) {
             $visitedAt = self::arrayGet($json, 'data.attributes.visited-at');
diff --git a/lib/classes/StudipFileCache.class.php b/lib/classes/StudipFileCache.class.php
index 2037b6ee1f61727a6a15763a5265319bc16e2362..13fce5b8a9297c2513ebd658c61a80385514fc88 100644
--- a/lib/classes/StudipFileCache.class.php
+++ b/lib/classes/StudipFileCache.class.php
@@ -148,10 +148,11 @@ class StudipFileCache implements StudipCache
      */
     public function write($arg, $content, $expire = self::DEFAULT_EXPIRATION)
     {
-        $key = $this->getCacheKey($arg);
+        $this->expire($arg);
 
-        $this->expire($key);
+        $key = $this->getCacheKey($arg);
         $file = $this->getPathAndFile($key, $expire);
+
         return @file_put_contents($file, serialize($content), LOCK_EX);
     }
 
diff --git a/lib/classes/auth_plugins/StudipAuthCAS.class.php b/lib/classes/auth_plugins/StudipAuthCAS.class.php
index e7ceeb3d1b0986a2ea2cad30c4c359f5a104d28c..0ce2a65dbeca4448d0a2ff6bbd8d0183a2268a5a 100644
--- a/lib/classes/auth_plugins/StudipAuthCAS.class.php
+++ b/lib/classes/auth_plugins/StudipAuthCAS.class.php
@@ -80,7 +80,7 @@ class StudipAuthCAS extends StudipAuthSSO
         return $this->userdata->getUserData($key, phpCAS::getUser());
     }
 
-    function logout()
+    public function logout(): void
     {
         // do a global cas logout
         phpCAS::client(CAS_VERSION_2_0, $this->host, $this->port, $this->uri, false);
diff --git a/lib/classes/auth_plugins/StudipAuthOIDC.class.php b/lib/classes/auth_plugins/StudipAuthOIDC.class.php
index 50f70a93c179a6567b1640540a89c35cc620b6bd..726553690afd67ca40f3f8a48ec9755b12cd7820 100644
--- a/lib/classes/auth_plugins/StudipAuthOIDC.class.php
+++ b/lib/classes/auth_plugins/StudipAuthOIDC.class.php
@@ -68,7 +68,6 @@ class StudipAuthOIDC extends StudipAuthSSO
      */
     public function verifyUsername($username)
     {
-
         $this->oidc->authenticate();
         $this->userdata = (array)$this->oidc->requestUserInfo();
         if (isset($this->userdata['sub'])) {
@@ -109,4 +108,9 @@ class StudipAuthOIDC extends StudipAuthSSO
     {
         return $this->userdata[$key];
     }
+
+    public function logout(): void
+    {
+        $this->oidc->signOut($this->oidc->getIdToken(), null);
+    }
 }
diff --git a/lib/classes/auth_plugins/StudipAuthSSO.class.php b/lib/classes/auth_plugins/StudipAuthSSO.class.php
index 752fa59a1b45511d14ddf5c47f3bde2b7e5ba8fe..84d92639a32a0a6ee7ed60c46405c8874d8e8457 100644
--- a/lib/classes/auth_plugins/StudipAuthSSO.class.php
+++ b/lib/classes/auth_plugins/StudipAuthSSO.class.php
@@ -36,7 +36,7 @@ abstract class StudipAuthSSO extends StudipAuthAbstract
      * Check whether this user can be authenticated. The default
      * implementation just checks whether $username is not empty.
      */
-    function isAuthenticated ($username, $password)
+    public function isAuthenticated ($username, $password)
     {
         return !empty($username);
     }
@@ -44,8 +44,15 @@ abstract class StudipAuthSSO extends StudipAuthAbstract
     /**
      * SSO auth plugins cannot determine if a username is used.
      */
-    function isUsedUsername ($username)
+    public function isUsedUsername ($username)
     {
         return false;
     }
+
+    /**
+     * Use this to log out the user
+     */
+    public function logout(): void
+    {
+    }
 }
diff --git a/lib/classes/auth_plugins/StudipAuthShib.class.php b/lib/classes/auth_plugins/StudipAuthShib.class.php
index 4a19cf5626bdd789462f8a1863e9379bd17b2af1..5cf5fa14625e12897ac54765e6f8449319b46ab9 100644
--- a/lib/classes/auth_plugins/StudipAuthShib.class.php
+++ b/lib/classes/auth_plugins/StudipAuthShib.class.php
@@ -18,6 +18,7 @@ class StudipAuthShib extends StudipAuthSSO
     public $local_domain;
     public $session_initiator;
     public $validate_url;
+    public ?string $logout_url = null;
     public $userdata;
     public $username_attribute = 'username';
 
@@ -139,4 +140,12 @@ class StudipAuthShib extends StudipAuthSSO
 
         return $data[0];
     }
+
+    public function logout(): void
+    {
+        if (!empty($this->logout_url)) {
+            header('Location: ' . URLHelper::getURL($this->logout_url, ['return' => Request::url()]));
+            exit();
+        }
+    }
 }
diff --git a/lib/models/ConsultationEvent.php b/lib/models/ConsultationEvent.php
index eac2d286187cc033b07f00cc51c35529b95c8a19..4f51f0bbdee719ee705db5ab387fc9a042f2562a 100644
--- a/lib/models/ConsultationEvent.php
+++ b/lib/models/ConsultationEvent.php
@@ -29,6 +29,24 @@ class ConsultationEvent extends SimpleORMap
             'on_delete'         => 'delete',
         ];
 
+        $config['registered_callbacks'] = [
+            'before_delete' => [
+                function (ConsultationEvent $event) {
+                    // Suppress all mails from calendar for users that do not
+                    // want to receive emails about consultation bookings
+                    $event->event->calendars->each(function (CalendarDateAssignment $assignment) {
+                        if (
+                            $assignment->user
+                            && !$assignment->user->getConfiguration()->CONSULTATION_SEND_MESSAGES
+                        ) {
+                            $assignment->suppress_mails = true;
+                            $assignment->delete();
+                        }
+                    });
+                },
+            ],
+        ];
+
         parent::configure($config);
     }
 }
diff --git a/lib/models/ConsultationSlot.php b/lib/models/ConsultationSlot.php
index 8356a34bac07005c8cfefdaf9815a93e14b8c924..708670445155dcc5450f64b3ca21b7280548934b 100644
--- a/lib/models/ConsultationSlot.php
+++ b/lib/models/ConsultationSlot.php
@@ -194,6 +194,10 @@ class ConsultationSlot extends SimpleORMap
         $calendar_event = new CalendarDateAssignment();
         $calendar_event->range_id         = $user->id;
         $calendar_event->calendar_date_id = $event->id;
+
+        // Suppress mails for users that do not want mails from the consultations
+        $calendar_event->suppress_mails = !$user->getConfiguration()->CONSULTATION_SEND_MESSAGES;
+
         $calendar_event->store();
 
         return $event;
diff --git a/lib/models/MvvFile.php b/lib/models/MvvFile.php
index e3c0e3cd8d515879d217492bd64dc0a94f5d9343..4b0be717fcc4b84ede4508743a52fd308e789776 100644
--- a/lib/models/MvvFile.php
+++ b/lib/models/MvvFile.php
@@ -79,8 +79,8 @@ class MvvFile extends ModuleManagementModel
      */
     public function getDisplayName()
     {
-        if ($this->file_refs) {
-            return $this->file_refs[0]->name;
+        if (count($this->file_refs) > 0) {
+            return $this->file_refs->first()->name;
         }
         return '';
     }
diff --git a/lib/models/calendar/CalendarDate.class.php b/lib/models/calendar/CalendarDate.class.php
index 4755c26952a6378e5c75d78b89f7029b746eab62..7ddc802f07edbc7f693a902c518bb3dab38885f0 100644
--- a/lib/models/calendar/CalendarDate.class.php
+++ b/lib/models/calendar/CalendarDate.class.php
@@ -38,6 +38,11 @@
  * @property string mkdate database column
  * @property string chdate database column
  * @property string import_date database column
+ *
+ * @property User $author
+ * @property User $editor
+ * @property CalendarDateAssignment[]|SimpleORMapCollection $calendars
+ * @property CalendarDateException[]|SimpleORMapCollection $exceptions
  */
 class CalendarDate extends SimpleORMap implements PrivacyObject
 {
diff --git a/lib/models/calendar/CalendarDateAssignment.class.php b/lib/models/calendar/CalendarDateAssignment.class.php
index 44dded0293e42a3d2053b694fe2a71f5c6f455da..dd3ef88dfd95ade4fca08c79da7ff9f36d23a295 100644
--- a/lib/models/calendar/CalendarDateAssignment.class.php
+++ b/lib/models/calendar/CalendarDateAssignment.class.php
@@ -29,6 +29,8 @@
  * @property string mkdate The creation date of the assignment.
  * @property string chdate The modification date of the assignment.
  * @property CalendarDate|null calendar_date The associated calendar date object.
+ * @property User|null $user
+ * @property Course|null $course
  */
 class CalendarDateAssignment extends SimpleORMap implements Event
 {
diff --git a/public/logout.php b/public/logout.php
index 2f8fcd8c58eaca7526caf21dfdaaac902e94bb75..c2722a24fcf8165df4356f28b6f32446f8e770ee 100644
--- a/public/logout.php
+++ b/public/logout.php
@@ -42,12 +42,10 @@ if ($auth->auth['uid'] !== 'nobody') {
     $_language = $_SESSION['_language'];
     $contrast = UserConfig::get($GLOBALS['user']->id)->USER_HIGH_CONTRAST;
 
-    // TODO this needs to be generalized or removed
-    //erweiterung cas
-    if ($auth->auth['auth_plugin'] === 'cas') {
-        $casauth = StudipAuthAbstract::GetInstance('cas');
-        $docaslogout = true;
-    }
+    // Get auth plugin of user before logging out since the $auth object will
+    // be modified by the logout
+    $auth_plugin = StudipAuthAbstract::getInstance($auth->auth['auth_plugin']);
+
     //Logout aus dem Sessionmanagement
     $auth->logout();
     $sess->delete();
@@ -58,10 +56,11 @@ if ($auth->auth['uid'] !== 'nobody') {
     $timeout=(time()-(15 * 60));
     $user->set_last_action($timeout);
 
-    //der logout() Aufruf fuer CAS (dadurch wird das Cookie (Ticket) im Browser zerstoert)
-    if (!empty($docaslogout)) {
-        $casauth->logout();
+    // Perform logout from auth plugin (if possible)
+    if ($auth_plugin instanceof StudipAuthSSO) {
+        $auth_plugin->logout();
     }
+
     $sess->start();
     $_SESSION['_language'] = $_language;
     if ($contrast) {
diff --git a/resources/assets/stylesheets/scss/contentbar.scss b/resources/assets/stylesheets/scss/contentbar.scss
index a7df7f734d07fe03e5b85c51c23847d25b62e3a8..01e586ffe56c27c1614df3de9b0afba115028fbe 100644
--- a/resources/assets/stylesheets/scss/contentbar.scss
+++ b/resources/assets/stylesheets/scss/contentbar.scss
@@ -16,10 +16,9 @@
 
     .contentbar-wrapper-left {
         display: flex;
-        flex: auto;
+        max-width: calc(100% - 130px);
 
         .contentbar-breadcrumb {
-            display: flex;
             font-size: 1.25em;
             line-height: 1.5em;
             margin-right: 1em;
diff --git a/templates/contentbar/contentbar.php b/templates/contentbar/contentbar.php
index fef7fd8c0f4bc46fc0bc563d6956ce148757fdda..59dc8a7b1a59bdbf6dedc9327f8d859d065919b5 100644
--- a/templates/contentbar/contentbar.php
+++ b/templates/contentbar/contentbar.php
@@ -13,11 +13,11 @@
         <nav class="contentbar-nav"></nav>
         <div class="contentbar-wrapper-left">
             <nav class="contentbar-breadcrumb">
-            <? if (!$toc->isActive()) : ?>
+            <? if ($toc->isActive()): ?>
+                <?= $icon->asImg(24, ['class' => 'text-bottom contentbar-icon']) ?>
+            <? else: ?>
                 <a href="<?= $toc->getUrl() ?>" title="<?= htmlReady($toc->getTitle()) ?>" class="contentbar-icon">
-            <? endif ?>
                     <?= $icon->asImg(24, ['class' => 'text-bottom']) ?>
-            <? if (!$toc->isActive()) : ?>
                 </a>
             <? endif ?>
                 <?= $breadcrumbs->render() ?>
diff --git a/templates/sidebar/widget-layout.php b/templates/sidebar/widget-layout.php
index 60defb80b3a3c14b212cdfeb79565af1601a89df..4a8e8a54dbafb1ebf3dc72490636acbf007ec88b 100644
--- a/templates/sidebar/widget-layout.php
+++ b/templates/sidebar/widget-layout.php
@@ -25,7 +25,7 @@ $additional_attributes['class'] = implode(' ', $css_classes);
     <div class="<?= $base_class ?>-widget-content">
         <?= $content_for_layout ?>
     </div>
-<? if ($title && isset($extra)): ?>
+<? if (!empty($title) && isset($extra)): ?>
     <div class="<?= $base_class ?>-widget-extra"><?= $extra ?></div>
 <? endif; ?>
 </div>
diff --git a/templates/toc/_toc-item-breadcrumb.php b/templates/toc/_toc-item-breadcrumb.php
index 4ea48c4a49010b784310ab5478348b9918a01201..d87419dd135ee43eac51b3feb3886300091288d3 100644
--- a/templates/toc/_toc-item-breadcrumb.php
+++ b/templates/toc/_toc-item-breadcrumb.php
@@ -1,7 +1,7 @@
 <? if (!$item->isRoot()) : ?>
     <?= $this->render_partial('toc/_toc-item-breadcrumb', ['item' => $item->getParent()]) ?>
 <? endif ?>
-<li>
+<li class="contentbar-breadcrumb-item <? if ($item->isActive()) echo 'contentbar-breadcrumb-item-current'; ?>">
     <? if (!$item->isActive()) : ?>
         <a class="navigate" href="<?= htmlReady($item->getURL()) ?>">
     <? endif ?>