From 5f78b312c8ad82f91148d6bfe79c614651736161 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Fri, 27 Sep 2024 11:50:34 +0000
Subject: [PATCH] fix errors found through static code analysis, fixes #4562

Closes #4562

Merge request studip/studip!3375
---
 app/controllers/admin/courses.php             |  2 +-
 app/controllers/admin/tree.php                |  4 +--
 app/controllers/course/admission.php          |  4 +--
 app/controllers/course/basicdata.php          |  2 +-
 app/controllers/course/feedback.php           |  1 -
 app/controllers/debugbar.php                  |  2 +-
 app/controllers/institute/basicdata.php       |  2 +-
 app/controllers/resources/ajax.php            |  2 +-
 lib/classes/AdminCourseFilter.php             |  2 +-
 lib/classes/Avatar.php                        |  2 +-
 .../Middlewares/DangerousRouteHandler.php     |  3 +-
 lib/classes/JsonApi/RouteMap.php              |  2 +-
 .../Courseware/Rel/SolversOfTaskGroup.php     |  1 +
 .../JsonApi/Routes/Feedback/RatingHelper.php  |  1 +
 .../Routes/Files/FileRefsContentShow.php      |  3 +-
 .../JsonApi/Routes/Holidays/HolidaysShow.php  |  2 +-
 .../RangeTree/ChildrenOfRangeTreeNode.php     |  3 +-
 lib/classes/OAuth2/NegotiatesWithPsr7.php     |  5 +++
 lib/classes/SQLQuery.php                      |  2 +-
 lib/classes/admission/CourseSet.php           |  2 +-
 lib/classes/cache/Exception.php               |  2 +-
 .../cache/InvalidCacheArgumentException.php   |  2 +-
 lib/classes/calendar/ICalendarExport.php      |  3 +-
 lib/classes/calendar/ICalendarImport.php      | 34 ++++++++-----------
 lib/exTpl/IteratorNode.php                    |  3 ++
 lib/exTpl/TemplateParserException.php         |  2 +-
 lib/exceptions/FeatureDisabledException.php   |  2 +-
 lib/helpers.php                               |  2 +-
 lib/ilias_interface/StudipSoapClient.php      |  7 ++--
 lib/models/Course.php                         |  6 ++--
 lib/models/Courseware/StructuralElement.php   |  4 +--
 lib/models/ModuleManagementModel.php          | 13 ++++---
 lib/models/MvvFileRange.php                   |  2 +-
 lib/models/StudipCacheOperation.php           |  7 ++--
 lib/plugins/core/CorePlugin.php               |  1 +
 phpstan.neon.dist                             | 14 ++++----
 .../lib/flexi/PHPTemplatePartialBugTest.php   |  2 ++
 .../lib/flexi/TemplateMagicMethodsTest.php    |  1 +
 38 files changed, 84 insertions(+), 70 deletions(-)

diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php
index 562a14bcb17..8a641901e73 100644
--- a/app/controllers/admin/courses.php
+++ b/app/controllers/admin/courses.php
@@ -490,7 +490,7 @@ class Admin_CoursesController extends AuthenticatedController
                         $multimode = $plugin->useMultimode();
                         if ($multimode) {
                             $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
-                            if ($multimode instanceof Flex\Template) {
+                            if ($multimode instanceof Flexi\Template) {
                                 $data['buttons_bottom'] = $multimode->render();
                             } elseif ($multimode instanceof \Studip\Button) {
                                 $data['buttons_bottom'] = (string) $multimode;
diff --git a/app/controllers/admin/tree.php b/app/controllers/admin/tree.php
index 320f10efd9c..5d22775a385 100644
--- a/app/controllers/admin/tree.php
+++ b/app/controllers/admin/tree.php
@@ -192,9 +192,9 @@ class Admin_TreeController extends AuthenticatedController
         }
 
         if ($node->store() !== false) {
-            Pagelayout::postSuccess(_('Die Daten wurden gespeichert.'));
+            PageLayout::postSuccess(_('Die Daten wurden gespeichert.'));
         } else {
-            Pagelayout::postError(_('Die Daten konnten nicht gespeichert werden.'));
+            PageLayout::postError(_('Die Daten konnten nicht gespeichert werden.'));
         }
 
         $this->relocate(Request::get('from'));
diff --git a/app/controllers/course/admission.php b/app/controllers/course/admission.php
index 0b3755a1ea0..a2b7df4e689 100644
--- a/app/controllers/course/admission.php
+++ b/app/controllers/course/admission.php
@@ -169,10 +169,10 @@ class Course_AdmissionController extends AuthenticatedController
                                 continue;
                             }
                             $num_moved++;
-                            setTempLanguage($user_id);
+                            setTempLanguage($user->id);
                             $message_body = sprintf(_('Sie wurden in der Veranstaltung **%s** in den Status **Autor** versetzt, da das Anmeldeverfahren geändert wurde.'), $this->course->name);
                             $message_title = sprintf(_("Statusänderung %s"), $this->course->name);
-                            messaging::sendSystemMessage($user_id, $message_title, $message_body);
+                            messaging::sendSystemMessage($user, $message_title, $message_body);
                             restoreLanguage();
                         }
                         if ($num_moved) {
diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php
index 114bcc8ee9f..04bf8fc7eaf 100644
--- a/app/controllers/course/basicdata.php
+++ b/app/controllers/course/basicdata.php
@@ -782,7 +782,7 @@ class Course_BasicdataController extends AuthenticatedController
         } else {
             $this->deleteUserFromCourse(
                 Course::find($course_id),
-                User::find($teacher_id)
+                User::find($tutor_id)
             );
         }
 
diff --git a/app/controllers/course/feedback.php b/app/controllers/course/feedback.php
index df600c7a1f5..903df17300e 100644
--- a/app/controllers/course/feedback.php
+++ b/app/controllers/course/feedback.php
@@ -72,7 +72,6 @@ class Course_FeedbackController extends AuthenticatedController
             'results_visible'   => 1,
             'commentable'       => 1,
             'mode'              => FeedbackElement::MODE_5STAR_RATING,
-            'mode'              => 1,
             'anonymous_entries' => 1,
         ]);
     }
diff --git a/app/controllers/debugbar.php b/app/controllers/debugbar.php
index 59627f27964..9fc5286c693 100644
--- a/app/controllers/debugbar.php
+++ b/app/controllers/debugbar.php
@@ -1,5 +1,5 @@
 <?php
-final class DebugbarController extends Trails_Controller
+final class DebugbarController extends Trails\Controller
 {
     public function __construct(
         Trails\Dispatcher $dispatcher,
diff --git a/app/controllers/institute/basicdata.php b/app/controllers/institute/basicdata.php
index 0b7c2960201..2f211c29ea9 100644
--- a/app/controllers/institute/basicdata.php
+++ b/app/controllers/institute/basicdata.php
@@ -426,7 +426,7 @@ class Institute_BasicdataController extends AuthenticatedController
 
             // delete all configuration files for the "extern modules"
             if (Config::get()->EXTERN_ENABLE) {
-                $counts = ExternConfig::DeleteAllConfigurations($i_id);
+                $counts = ExternPageConfig::deleteBySQL('range_id = ?', [$i_id]);
                 if ($counts) {
                     $details[] = sprintf(_('%u Konfigurationsdateien für externe Seiten gelöscht.'), $counts);
                 }
diff --git a/app/controllers/resources/ajax.php b/app/controllers/resources/ajax.php
index cffd8783708..ebe0e107861 100644
--- a/app/controllers/resources/ajax.php
+++ b/app/controllers/resources/ajax.php
@@ -778,7 +778,7 @@ class Resources_AjaxController extends AuthenticatedController
         $semester = \Semester::findByTimestamp($timestamp);
         if (!$semester) {
             $this->notFound('No semester found for given timestamp');
-            throw new RecordNotFoundException();
+            return;
         }
 
         $timestamp = strtotime('today', $timestamp);
diff --git a/lib/classes/AdminCourseFilter.php b/lib/classes/AdminCourseFilter.php
index 0c0ec8deb33..bdb84d2871b 100644
--- a/lib/classes/AdminCourseFilter.php
+++ b/lib/classes/AdminCourseFilter.php
@@ -53,7 +53,7 @@ class AdminCourseFilter
     /**
      * Constructor of the singleton-object.
      */
-    public function __construct(bool $reset_settings = false)
+    final public function __construct(bool $reset_settings = false)
     {
         $this->initSettings($reset_settings);
     }
diff --git a/lib/classes/Avatar.php b/lib/classes/Avatar.php
index d2dcd9d65c7..bb3a91564e7 100644
--- a/lib/classes/Avatar.php
+++ b/lib/classes/Avatar.php
@@ -197,7 +197,7 @@ class Avatar
      * @param string $user_id  the user's id
      * @param string $username the user's username (optional)
      */
-    protected function __construct($user_id, $username = null)
+    final protected function __construct($user_id, $username = null)
     {
         $this->user_id = $user_id;
         $this->username = $username;
diff --git a/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php b/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php
index d1112f838b5..54f21d53719 100644
--- a/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php
+++ b/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php
@@ -24,8 +24,7 @@ class DangerousRouteHandler
         if (\Config::get()->getValue('JSONAPI_DANGEROUS_ROUTES_ALLOWED')) {
             return $handler->handle($request);
         }
-        $response = new Response();
 
-        return $response->withStatus(503)->write('Service Unavailable.');
+        throw new \AccessDeniedException('Service unavailable');
     }
 }
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php
index e9a0a011a88..4b61b3d2774 100644
--- a/lib/classes/JsonApi/RouteMap.php
+++ b/lib/classes/JsonApi/RouteMap.php
@@ -325,7 +325,7 @@ class RouteMap
         $group->get('/tree-node/{id}', Routes\Tree\TreeShow::class);
 
         $group->get('/tree-node/{id}/children', Routes\Tree\ChildrenOfTreeNode::class);
-        $group->get('/tree-node/{id}/courseinfo', Routes\Tree\CourseInfoOfTreeNode::class);
+        $group->get('/tree-node/{id}/courseinfo', Routes\Tree\CourseinfoOfTreeNode::class);
         $group->get('/tree-node/{id}/courses', Routes\Tree\CoursesOfTreeNode::class);
         $group->get('/tree-node/course/pathinfo/{classname}/{id}', Routes\Tree\PathinfoOfTreeNodeCourse::class);
         $group->get('/tree-node/course/details/{id}', Routes\Tree\DetailsOfTreeNodeCourse::class);
diff --git a/lib/classes/JsonApi/Routes/Courseware/Rel/SolversOfTaskGroup.php b/lib/classes/JsonApi/Routes/Courseware/Rel/SolversOfTaskGroup.php
index 2ab5ffa6da1..3d2a325b810 100644
--- a/lib/classes/JsonApi/Routes/Courseware/Rel/SolversOfTaskGroup.php
+++ b/lib/classes/JsonApi/Routes/Courseware/Rel/SolversOfTaskGroup.php
@@ -13,6 +13,7 @@ use JsonApi\Schemas\Courseware\TaskGroup as TaskGroupSchema;
 use JsonApi\Schemas\StatusGroup as StatusGroupSchema;
 use JsonApi\Schemas\User as UserSchema;
 use Psr\Http\Message\ServerRequestInterface as Request;
+use RuntimeException;
 use Statusgruppen;
 use User;
 
diff --git a/lib/classes/JsonApi/Routes/Feedback/RatingHelper.php b/lib/classes/JsonApi/Routes/Feedback/RatingHelper.php
index 849cba73a7e..c258f7e5dac 100644
--- a/lib/classes/JsonApi/Routes/Feedback/RatingHelper.php
+++ b/lib/classes/JsonApi/Routes/Feedback/RatingHelper.php
@@ -3,6 +3,7 @@
 namespace JsonApi\Routes\Feedback;
 
 use FeedbackElement;
+use InvalidArgumentException;
 
 trait RatingHelper
 {
diff --git a/lib/classes/JsonApi/Routes/Files/FileRefsContentShow.php b/lib/classes/JsonApi/Routes/Files/FileRefsContentShow.php
index 69f71ec6362..6cb6ef3a131 100644
--- a/lib/classes/JsonApi/Routes/Files/FileRefsContentShow.php
+++ b/lib/classes/JsonApi/Routes/Files/FileRefsContentShow.php
@@ -6,6 +6,7 @@ use JsonApi\Errors\AuthorizationFailedException;
 use JsonApi\Errors\InternalServerError;
 use JsonApi\Errors\RecordNotFoundException;
 use JsonApi\NonJsonApiController;
+use Psr\Container\ContainerInterface;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\StreamFactoryInterface;
@@ -72,7 +73,7 @@ class FileRefsContentShow extends NonJsonApiController
                 );
             }
 
-            list($done, $response) = $this->handleEtag($request, $response, $fileRef);
+            [$done, $response] = $this->handleEtag($request, $response, $fileRef);
             if ($done) {
                 return $response;
             }
diff --git a/lib/classes/JsonApi/Routes/Holidays/HolidaysShow.php b/lib/classes/JsonApi/Routes/Holidays/HolidaysShow.php
index a587dcae487..eb413d02d95 100644
--- a/lib/classes/JsonApi/Routes/Holidays/HolidaysShow.php
+++ b/lib/classes/JsonApi/Routes/Holidays/HolidaysShow.php
@@ -96,7 +96,7 @@ final class HolidaysShow extends NonJsonApiController
         $errors = new ErrorCollection();
 
         // Get filters
-        $filters = $this->query_parser->getFilteringParameters();
+        $filters = $this->queryParser->getFilteringParameters();
 
         // Validate allowed filters
         foreach ($filters as $key => $value) {
diff --git a/lib/classes/JsonApi/Routes/RangeTree/ChildrenOfRangeTreeNode.php b/lib/classes/JsonApi/Routes/RangeTree/ChildrenOfRangeTreeNode.php
index 8773c0a5b60..58fcbf3601b 100644
--- a/lib/classes/JsonApi/Routes/RangeTree/ChildrenOfRangeTreeNode.php
+++ b/lib/classes/JsonApi/Routes/RangeTree/ChildrenOfRangeTreeNode.php
@@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response;
 use JsonApi\Errors\AuthorizationFailedException;
 use JsonApi\Errors\RecordNotFoundException;
 use JsonApi\JsonApiController;
+use RangeTreeNode;
 
 class ChildrenOfRangeTreeNode extends JsonApiController
 {
@@ -27,7 +28,7 @@ class ChildrenOfRangeTreeNode extends JsonApiController
             throw new RecordNotFoundException();
         }
 
-        list($offset, $limit) = $this->getOffsetAndLimit();
+        [$offset, $limit] = $this->getOffsetAndLimit();
         $total = \RangeTreeNode::countByParent_id($args['id']);
         $children = \RangeTreeNode::findByParent_id(
             $args['id'],
diff --git a/lib/classes/OAuth2/NegotiatesWithPsr7.php b/lib/classes/OAuth2/NegotiatesWithPsr7.php
index 09197f4e659..1f743ad8fbb 100644
--- a/lib/classes/OAuth2/NegotiatesWithPsr7.php
+++ b/lib/classes/OAuth2/NegotiatesWithPsr7.php
@@ -5,6 +5,7 @@ namespace Studip\OAuth2;
 use Psr\Http\Message\ResponseFactoryInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
+use Trails\Controller;
 use Trails\Response as TrailsResponse;
 
 trait NegotiatesWithPsr7
@@ -33,6 +34,10 @@ trait NegotiatesWithPsr7
 
     protected function renderPsrResponse(ResponseInterface $response): void
     {
+        if (!($this instanceof Controller)) {
+            throw new \Exception('Can only render responses on trails controllers');
+        }
+
         $this->set_status($response->getStatusCode());
         $this->render_text((string) $response->getBody());
         foreach ($response->getHeaders() as $key => $values) {
diff --git a/lib/classes/SQLQuery.php b/lib/classes/SQLQuery.php
index 6c8f49ec7d3..d1e9aca5e6f 100644
--- a/lib/classes/SQLQuery.php
+++ b/lib/classes/SQLQuery.php
@@ -217,7 +217,7 @@ class SQLQuery
      * a sorm_class the result will be an array of the sorm-objects.
      *
      * @template T of SimpleORMap
-     * @param class-string<T>|string|null $sorm_class_or_column : column name, a class of SimpleORMap or null for associative array.
+     * @param T|class-string<T>|string|null $sorm_class_or_column : column name, a class of SimpleORMap or null for associative array.
      * @param int|null $max_results Maximum number of results to return
      * @return array[]|T[]|mixed[] arrays or array of objects or array of values.
      *
diff --git a/lib/classes/admission/CourseSet.php b/lib/classes/admission/CourseSet.php
index beff9acecdd..e7f3dee9d39 100644
--- a/lib/classes/admission/CourseSet.php
+++ b/lib/classes/admission/CourseSet.php
@@ -559,7 +559,7 @@ class CourseSet
     /**
      * Gets the course sets the given course belongs to.
      *
-     * @param  String courseId
+     * @param  string $courseId
      * @return CourseSet
      */
     public static function getSetForCourse($courseId)
diff --git a/lib/classes/cache/Exception.php b/lib/classes/cache/Exception.php
index 061d09019a2..d4ebe120b83 100644
--- a/lib/classes/cache/Exception.php
+++ b/lib/classes/cache/Exception.php
@@ -21,7 +21,7 @@ namespace Studip\Cache;
  * The CacheException class is an implementation of the CacheException interface
  * of PSR-6 that behaves like a StudipException.
  */
-class Exception extends \StudipException implements \Psr\Cache\CacheException
+class Exception extends \Studip\Exception implements \Psr\Cache\CacheException
 {
     //Nothing here, since there is nothing to implement.
 }
diff --git a/lib/classes/cache/InvalidCacheArgumentException.php b/lib/classes/cache/InvalidCacheArgumentException.php
index a201cad983e..5c4e25a6e3a 100644
--- a/lib/classes/cache/InvalidCacheArgumentException.php
+++ b/lib/classes/cache/InvalidCacheArgumentException.php
@@ -22,7 +22,7 @@ namespace Studip\Cache;
  * The InvalidCacheArgumentException is an implementation of the InvalidArgumentException interface
  *  of PSR-6 that behaves like a StudipException.
  */
-class InvalidCacheArgumentException extends \StudipException implements \Psr\Cache\InvalidArgumentException
+class InvalidCacheArgumentException extends \Studip\Exception implements \Psr\Cache\InvalidArgumentException
 {
     //Nothing here, since there is nothing to implement.
 }
diff --git a/lib/classes/calendar/ICalendarExport.php b/lib/classes/calendar/ICalendarExport.php
index d8d1af7d8ce..21bb47f2078 100644
--- a/lib/classes/calendar/ICalendarExport.php
+++ b/lib/classes/calendar/ICalendarExport.php
@@ -35,9 +35,10 @@ class ICalendarExport
      */
     private $time = 0;
 
+    public string $format;
+
     public function __construct()
     {
-        $this->default_filename_suffix = "ics";
         $this->format = "iCalendar";
     }
 
diff --git a/lib/classes/calendar/ICalendarImport.php b/lib/classes/calendar/ICalendarImport.php
index 6d4a2607d50..a2043181c07 100644
--- a/lib/classes/calendar/ICalendarImport.php
+++ b/lib/classes/calendar/ICalendarImport.php
@@ -2,15 +2,12 @@
 class ICalendarImport
 {
     private $range_id;
-
     private $count = 0;
-
-    private $dates = [];
-
     private $import_time;
-
     private $convert_to_private = false;
 
+    private $client_identifier = '';
+
     public function __construct($range_id)
     {
         $this->range_id = $range_id;
@@ -94,10 +91,11 @@ class ICalendarImport
                     $params = [];
 
                     // skip seminar events
-                    if ((!$this->import_sem) && $tag == 'UID') {
-                        if (mb_strpos($value, 'Stud.IP-SEM') === 0) {
-                            continue 2;
-                        }
+                    if (
+                        $tag == 'UID'
+                        && mb_strpos($value, 'Stud.IP-SEM') === 0
+                    ) {
+                        continue 2;
                     }
 
                     if (!empty($parts[2])) {
@@ -312,8 +310,7 @@ class ICalendarImport
 
                 $this->createDateFromProperties($properties);
             } else {
-                // _("Die Datei ist keine gültige iCalendar-Datei!")
-                throw new InvalidValuesException();
+                throw new InvalidValuesException(_('Die Datei ist keine gültige iCalendar-Datei!'));
             }
             $this->count++;
         }
@@ -471,7 +468,7 @@ class ICalendarImport
             }
             return $time;
         }
-        throw new InvalidValuesException();
+        throw new InvalidValuesException(_('Die Zeitangabe ist ungültig'));
     }
 
     /**
@@ -486,7 +483,7 @@ class ICalendarImport
             $date['mday'] = $matches[3];
             return $date;
         }
-        throw new InvalidValuesException();
+        throw new InvalidValuesException(_('Die Datumsangabe ist ungültig'));
     }
 
     /**
@@ -650,13 +647,12 @@ class ICalendarImport
 
     private function parseClientIdentifier(&$data)
     {
-        global $_calendar_error;
-
         if ($this->client_identifier == '') {
-            if (!preg_match('/PRODID((;[\W\w]*)*):([\W\w]+?)(\r\n|\r|\n)/', $data, $matches)
-                || !trim($matches[3])) {
-                // _("Die Datei ist keine gültige iCalendar-Datei!")
-                throw new InvalidValuesException();
+            if (
+                !preg_match('/PRODID((;[\W\w]*)*):([\W\w]+?)(\r\n|\r|\n)/', $data, $matches)
+                || !trim($matches[3])
+            ) {
+                throw new InvalidValuesException(_('Die Datei ist keine gültige iCalendar-Datei!'));
             } else {
                 $this->client_identifier = trim($matches[3]);
             }
diff --git a/lib/exTpl/IteratorNode.php b/lib/exTpl/IteratorNode.php
index 58dd593599a..17c15d072ef 100644
--- a/lib/exTpl/IteratorNode.php
+++ b/lib/exTpl/IteratorNode.php
@@ -38,6 +38,9 @@ class IteratorNode extends ArrayNode
         $result = '';
 
         if (is_array($values) && is_int(key($values))) {
+            $key = null;
+            $value = null;
+
             $bindings = [$this->key_name => &$key, $this->val_name => &$value];
             $context  = new Context($bindings, $context);
 
diff --git a/lib/exTpl/TemplateParserException.php b/lib/exTpl/TemplateParserException.php
index 2c7abf88f72..c38b3ff6105 100644
--- a/lib/exTpl/TemplateParserException.php
+++ b/lib/exTpl/TemplateParserException.php
@@ -14,6 +14,6 @@ class TemplateParserException extends Exception
         $type  = $scanner->tokenType();
         $value = is_int($type) ? $scanner->tokenValue() : $type;
 
-        return parent::__construct("$message at \"$value\"");
+        parent::__construct("$message at \"$value\"");
     }
 }
diff --git a/lib/exceptions/FeatureDisabledException.php b/lib/exceptions/FeatureDisabledException.php
index 16af0bf8f8a..52cbbdf18d3 100644
--- a/lib/exceptions/FeatureDisabledException.php
+++ b/lib/exceptions/FeatureDisabledException.php
@@ -1,5 +1,5 @@
 <?php
-class FeatureDisabledException extends StudipException
+class FeatureDisabledException extends Studip\Exception
 {
     public function __construct($message = '', $code = 0, Exception $previous = null)
     {
diff --git a/lib/helpers.php b/lib/helpers.php
index a44f10b9b97..bffed4a9acb 100644
--- a/lib/helpers.php
+++ b/lib/helpers.php
@@ -15,7 +15,7 @@ use Psr\Container\ContainerInterface;
  * $logger = app(LoggerInterface::class);
  * ```
  * @template T
- * @param class-string<T>|string|null $entryId    entry name or a class name
+ * @param T|class-string<T>|string|null $entryId    entry name or a class name
  * @param array       $parameters Optional parameters to use to build the entry.
  *                                Use this to force specific parameters to specific values.
  *                                Parameters not defined in this array will be resolved using the container.
diff --git a/lib/ilias_interface/StudipSoapClient.php b/lib/ilias_interface/StudipSoapClient.php
index 0aafd4af567..6a22f86243f 100644
--- a/lib/ilias_interface/StudipSoapClient.php
+++ b/lib/ilias_interface/StudipSoapClient.php
@@ -11,8 +11,9 @@
 */
 class StudipSoapClient
 {
-    var $soap_client;
-    var $error;
+    public $soap_client;
+    public $error;
+    public $faultstring;
 
     function __construct($path)
     {
@@ -30,7 +31,7 @@ class StudipSoapClient
             $this->faultstring = "";
             try {
                 $result = $this->soap_client->__soapCall($method, $params);
-            } catch  (SoapFault $fault) {
+            } catch (SoapFault $fault) {
                 $this->faultstring = $fault->faultstring;
                 if (!in_array(mb_strtolower($this->faultstring), ["session not valid","session invalid", "session idled"])) {
                     unset($params['password']);
diff --git a/lib/models/Course.php b/lib/models/Course.php
index 1ff49f4bb49..d7ffff7a956 100644
--- a/lib/models/Course.php
+++ b/lib/models/Course.php
@@ -306,7 +306,7 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
 
         $config['registered_callbacks']['before_store'][] = 'logStore';
         $config['registered_callbacks']['after_create'][] = 'setDefaultTools';
-        $config['registered_callbacks']['after_delete'][] = function ($course) {
+        $config['registered_callbacks']['after_delete'][] = function (Course $course) {
             CourseAvatar::getAvatar($course->id)->reset();
             FeedbackElement::deleteBySQL('course_id = ?', [$course->id]);
             // Remove subcourse relations, leaving subcourses intact.
@@ -351,7 +351,7 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
             AutoInsert::deleteSeminar($course->id);
 
             //Remove assignments to admission sets:
-            $cs = $this->getCourseSet();
+            $cs = $course->getCourseSet();
             if ($cs) {
                 CourseSet::removeCourseFromSet($cs->getId(), $course->id);
                 $cs->load();
@@ -605,7 +605,7 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
      */
     public function hasCourseSet() : bool
     {
-        return CourseSet::countBySeminar_id($this->id) > 0;
+        return $this->getCourseSet() !== null;
     }
 
     /**
diff --git a/lib/models/Courseware/StructuralElement.php b/lib/models/Courseware/StructuralElement.php
index 9b68ea2fdf5..e72cc4f34ef 100644
--- a/lib/models/Courseware/StructuralElement.php
+++ b/lib/models/Courseware/StructuralElement.php
@@ -184,7 +184,7 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac
             $this->image_id = $image->getId();
             $this->image_type = \StockImage::class;
         } else {
-            throw \BadMethodCallException('Invalid argument to method ' . __METHOD__);
+            throw new \BadMethodCallException('Invalid argument to method ' . __METHOD__);
         }
     }
 
@@ -1219,7 +1219,7 @@ SQL;
         if ($this->range_type === 'user') {
             return 'contents/courseware/courseware/' . $unit->id . '#/structural_element/' . $this->id;
         }
- 
+
         return 'course/courseware/courseware/' . $unit->id . '?cid=' . $this->range_id . '#/structural_element/' . $this->id;
     }
 
diff --git a/lib/models/ModuleManagementModel.php b/lib/models/ModuleManagementModel.php
index 786b1e358f6..3bd5eb75b4f 100644
--- a/lib/models/ModuleManagementModel.php
+++ b/lib/models/ModuleManagementModel.php
@@ -253,7 +253,6 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
      * Logs all changes of this object.
      *
      * @param string $action new, update or delete
-     * @return boolean Return true if logging was successful.
      */
     protected function logChanges ($action = null) {
 
@@ -367,7 +366,7 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
                 $num_index = 1;
                 break;
             default:
-                return false;
+                return;
         }
 
         if ($logging) {
@@ -389,7 +388,7 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
                     $debuginfo = $this->getDisplayName();
                     break;
                 default:
-                    return false;
+                    return;
             }
 
             $id_array = $this->getId();
@@ -407,7 +406,7 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
                     $debuginfo = $id_array[2];
                     break;
                 default:
-                    return false;
+                    return;
             }
 
             if ($action == 'update') {
@@ -424,9 +423,9 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
                 StudipLog::log($logging, $aff, $coaff, $this->db_table(), $debuginfo);
             }
 
-            return true;
+            return;
         }
-        return false;
+        return;
     }
 
     /**
@@ -927,7 +926,7 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
      */
     protected static function formatDisplayName(
         string $template,
-        array $placeholders, 
+        array $placeholders,
         array $replacements
     ): string {
         if (mb_strlen($template) === 0) {
diff --git a/lib/models/MvvFileRange.php b/lib/models/MvvFileRange.php
index 9155c1fd909..4c0c6956f94 100644
--- a/lib/models/MvvFileRange.php
+++ b/lib/models/MvvFileRange.php
@@ -48,7 +48,7 @@ class MvvFileRange extends ModuleManagementModel
     /**
      * Returns the rangetype of the document based on its foldertype.
      *
-     * @return bool|string Returns false on failure, otherwise the name of the range.
+     * @return string Returns false on failure, otherwise the name of the range.
      */
     public function getRangeType()
     {
diff --git a/lib/models/StudipCacheOperation.php b/lib/models/StudipCacheOperation.php
index d2287c32a5f..36d81b9fad1 100644
--- a/lib/models/StudipCacheOperation.php
+++ b/lib/models/StudipCacheOperation.php
@@ -1,4 +1,7 @@
 <?php
+
+use Studip\Cache\Cache;
+
 /**
  * Model for a stored cache operation.
  *
@@ -38,9 +41,9 @@ class StudipCacheOperation extends SimpleORMap
      * The operations are applied in chronological order and are deleted
      * from the database after they have been applied.
      *
-     * @param StudipCache $cache The cache object to apply the operations to
+     * @param Cache $cache The cache object to apply the operations to
      */
-    public static function apply(StudipCache $cache)
+    public static function apply(Cache $cache)
     {
         self::findEachBySQL(function (StudipCacheOperation $item) use ($cache): void {
             $parameters = unserialize($item->parameters);
diff --git a/lib/plugins/core/CorePlugin.php b/lib/plugins/core/CorePlugin.php
index 056efaf6ac1..9e68992047d 100644
--- a/lib/plugins/core/CorePlugin.php
+++ b/lib/plugins/core/CorePlugin.php
@@ -8,6 +8,7 @@
  */
 abstract class CorePlugin
 {
+    abstract public function getMetadata();
 
     /**
      * plugin meta data
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 5b8547f1c3b..2d66785205c 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -7,17 +7,17 @@ parameters:
         - tests/functional
         - tests/jsonapi
         - tests/unit
+        - tests/_support
     scanDirectories:
         - app/controllers
         - lib
         - vendor
     excludePaths:
         - lib/classes/ZipArchiveLegacyTrait.php
-        - lib/elearning/studip_referrer.php
-        - lib/soap/StudipSoapClient_PHP5.php
+        - lib/ilias_interface/studip_referrer_7x.php
+        - lib/ilias_interface/studip_referrer_8x.php
     tmpDir: .caches
-    earlyTerminatingMethodCalls:
-        RESTAPI\RouteMap:
-            - error
-            - halt
-            - notFound
+    ignoreErrors:
+        -
+            message: '#Unsafe usage of new static\(\)\.#'
+            path: lib/classes/SimpleORMap.php
diff --git a/tests/unit/lib/flexi/PHPTemplatePartialBugTest.php b/tests/unit/lib/flexi/PHPTemplatePartialBugTest.php
index ef265cf22c4..fd909cb1c95 100644
--- a/tests/unit/lib/flexi/PHPTemplatePartialBugTest.php
+++ b/tests/unit/lib/flexi/PHPTemplatePartialBugTest.php
@@ -4,6 +4,8 @@ use Flexi\Factory;
 
 final class PhpTemplatePartialBugTestCase extends Codeception\Test\Unit
 {
+    private Factory $factory;
+
     public function setUp(): void
     {
         $this->setUpFS();
diff --git a/tests/unit/lib/flexi/TemplateMagicMethodsTest.php b/tests/unit/lib/flexi/TemplateMagicMethodsTest.php
index ad2690a7430..fda4695d165 100644
--- a/tests/unit/lib/flexi/TemplateMagicMethodsTest.php
+++ b/tests/unit/lib/flexi/TemplateMagicMethodsTest.php
@@ -6,6 +6,7 @@ use Flexi\Template;
 final class TemplateMagicMethodsTestCase extends \Codeception\Test\Unit
 {
     private Factory $factory;
+    private Template $template;
 
     public function setUp(): void
     {
-- 
GitLab