From 7e94d278c685d68fa464f2b7c0f136165e30cc86 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Wed, 22 Mar 2023 12:34:00 +0000
Subject: [PATCH] ensure correct permissions for courses, fixes #2220

Closes #2220

Merge request studip/studip!1455
---
 lib/classes/forms/NewsRangesInput.php        |  19 +-
 lib/classes/searchtypes/NewsRangesSearch.php | 193 +++++++++++++++++++
 2 files changed, 196 insertions(+), 16 deletions(-)
 create mode 100644 lib/classes/searchtypes/NewsRangesSearch.php

diff --git a/lib/classes/forms/NewsRangesInput.php b/lib/classes/forms/NewsRangesInput.php
index 1c70117ca41..5ab58ba190a 100644
--- a/lib/classes/forms/NewsRangesInput.php
+++ b/lib/classes/forms/NewsRangesInput.php
@@ -8,20 +8,7 @@ class NewsRangesInput extends Input
     public function render()
     {
         $context = $this->getContextObject();
-        $sql = "SELECT CONCAT(`Seminar_id`, '__seminar') AS `range_id`, `name` FROM `seminare` WHERE `name` LIKE :input ";
-        if ($GLOBALS['perm']->have_perm('admin')) {
-            $sql .= "UNION SELECT CONCAT(`Institut_id`, '__institute') AS `range_id`, `Name` AS `name` FROM Institute WHERE `name` LIKE :input ";
-            if (!$GLOBALS['perm']->have_perm('root')) {
-                $sql .= "AND ";
-            }
-        }
-        if ($GLOBALS['perm']->have_perm('root')) {
-            $sql .= "UNION SELECT * FROM (SELECT CAST('studip__home' AS BINARY) AS `range_id`, '"._('Stud.IP-Startseite')."' AS `name`) as tmp_global_table WHERE `name` LIKE :input ";
-            $sql .= "UNION SELECT CONCAT(`user_id`, '__person') AS `range_id`, CONCAT(`Vorname`, ' ', `Nachname`) AS `name` FROM `auth_user_md5` WHERE CONCAT(`Vorname`, ' ', `Nachname`) LIKE :input ";
-        } else {
-            $sql .= "UNION SELECT * FROM (SELECT CAST('".\User::findCurrent()->id."__person' AS BINARY) AS `range_id`, '".\addslashes(\User::findCurrent()->getFullName()." - "._('Profilseite'))."' AS `name`) as tmp_user_table WHERE `name` LIKE :input ";
-        }
-        $searchtype = new \SQLSearch($sql, _('Bereich suchen'));
+
         $items = [];
         $icons = [
             'global' => 'home',
@@ -70,7 +57,7 @@ class NewsRangesInput extends Input
         $template = $GLOBALS['template_factory']->open('forms/news_ranges_input');
         $template->name = $this->name;
         $template->items = $items;
-        $template->searchtype = $searchtype;
+        $template->searchtype = new \NewsRangesSearch();
         $template->selectable = $selectable;
         $template->category_order = ['home', 'institute', 'seminar', 'person'];
         return $template->render();
@@ -170,7 +157,7 @@ class NewsRangesInput extends Input
         $name_format = \Config::get()->IMPORANT_SEMNUMBER ? 'number-name-semester' : 'name-semester';
 
         $options = [];
-        foreach (\Course::findByUser(\User::findCurrent()->id) as $course) {
+        foreach (\Course::findByUser(\User::findCurrent()->id, ['tutor', 'dozent']) as $course) {
             if (!\StudipNews::haveRangePermission('edit', $course->id)) {
                 continue;
             }
diff --git a/lib/classes/searchtypes/NewsRangesSearch.php b/lib/classes/searchtypes/NewsRangesSearch.php
new file mode 100644
index 00000000000..927181e8719
--- /dev/null
+++ b/lib/classes/searchtypes/NewsRangesSearch.php
@@ -0,0 +1,193 @@
+<?php
+class NewsRangesSearch extends SearchType
+{
+    /**
+     * returns the title/description of the searchfield
+     *
+     * @return string title/description
+     */
+    public function getTitle()
+    {
+        return _('Bereich suchen');
+    }
+
+    /**
+     * returns the results of a search
+     *
+     * @param string $input the search-word(s)
+     * @param array $contextual_data unused
+     * @param int $limit maximum number of results (default: all)
+     * @param int $offset return results starting from this row (default: 0)
+     *
+     * @return array  array(array(), ...)
+     */
+    public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0)
+    {
+        $sql_searches = [];
+        $parameters = [':input' => "%{$input}%"];
+
+        $user = \User::findCurrent();
+
+        // Courses
+        if ($GLOBALS['perm']->have_perm('root')) {
+            $sql_searches[] = "SELECT CONCAT(`Seminar_id`, '__seminar') AS `range_id`, `name`
+                               FROM `seminare`
+                               WHERE `name` LIKE :input";
+        } elseif ($GLOBALS['perm']->have_perm('admin')) {
+            $sem_inst = Config::get()->ALLOW_ADMIN_RELATED_INST ? 'si' : 's';
+
+            $sql_searches[] = "SELECT CONCAT(`Seminar_id`, '__seminar') AS `range_id`, `name`
+                               FROM `seminare` AS s
+                               JOIN `seminar_inst` si USING (Seminar_id)
+                               WHERE {$sem_inst}.`institut_id` IN (:institutes)
+                                 AND `name` LIKE :input";
+
+            $parameters[':institutes'] = $this->getAdminInstitutes($user);
+        } else {
+            $sql_searches[] = "SELECT CONCAT(`Seminar_id`, '__seminar') AS `range_id`, `name`
+                               FROM `seminare`
+                               JOIN `seminar_user` USING (`Seminar_id`)
+                               WHERE `name` LIKE :input
+                                 AND `seminar_user`.`user_id` = :user_id 
+                                 AND `seminar_user`.`status` IN ('tutor', 'dozent')";
+            $parameters[':user_id'] = $user->id;
+        }
+
+        // Institutes
+        if ($GLOBALS['perm']->have_perm('root')) {
+            $sql_searches[] = "SELECT CONCAT(`Institut_id`, '__institute') AS `range_id`, `Name` AS `name`
+                               FROM `Institute`
+                               WHERE `name` LIKE :input";
+        } else {
+            $sql_searches[] = "SELECT CONCAT(`Institut_id`, '__institute') AS `range_id`, `Name` AS `name`
+                               FROM `Institute`
+                               JOIN `user_inst` USING (`Institut_id`)
+                               WHERE `user_inst`.`user_id` = :user_id
+                                 AND `user_inst`.`inst_perms` IN ('tutor', 'dozent', 'admin')
+                                 AND `name` LIKE :input";
+            $parameters[':user_id'] = $user->id;
+        }
+
+        // Other (start page for root) and personal pages (only own profile for everyone except root)
+        if ($GLOBALS['perm']->have_perm('root')) {
+            $sql_searches[] = "SELECT *
+                               FROM (
+                                  SELECT CAST('studip__home' AS BINARY) AS `range_id`, :home_label AS `name`
+                               ) AS tmp_global_table
+                               WHERE `name` LIKE :input";
+            $parameters[':home_label'] = _('Stud.IP-Startseite');
+
+            $sql_searches[] = "SELECT CONCAT(`user_id`, '__person') AS `range_id`, CONCAT(`Vorname`, ' ', `Nachname`) AS `name`
+                               FROM `auth_user_md5`
+                               WHERE CONCAT(`Vorname`, ' ', `Nachname`) LIKE :input";
+        } elseif ($GLOBALS['perm']->have_perm('admin')) {
+            $sql_searches[] = "SELECT CONCAT(`user_id`, '__person') AS `range_id`, CONCAT(`Vorname`, ' ', `Nachname`) AS `name`
+                               FROM `auth_user_md5` AS aum
+                               JOIN `user_inst` AS ui USING (`user_id`)
+                               WHERE ui.`institut_id` IN (:institutes)
+                                 AND CONCAT(`Vorname`, ' ', `Nachname`) LIKE :input";
+
+            $parameters[':institutes'] = $this->getAdminInstitutes($user);
+        } else {
+            $sql_searches[] = "SELECT *
+                               FROM (
+                                 SELECT CAST(CONCAT(:user_id, '__person') AS BINARY) AS `range_id`,
+                                        CONCAT_WS(' - ', :user_name, :profile_name) AS `name`
+                               ) AS tmp_user_table
+                               WHERE `name` LIKE :input";
+            $parameters[':user_id'] = $user->id;
+            $parameters[':user_name'] = $user->getFullname();
+            $parameters[':profile_name'] = _('Profilseite');
+        }
+
+        $searches = implode(' UNION ALL ', $sql_searches);
+
+        $query = "SELECT * FROM ({$searches}) AS tmp";
+
+        if ($offset || $limit != PHP_INT_MAX) {
+            $query .= " LIMIT {$offset}, {$limit}";
+        }
+
+        $statement = DBManager::get()->prepare($query);
+        $statement->execute($parameters);
+        return $statement->fetchAll(PDO::FETCH_NUM);
+    }
+
+    /**
+     * Returns an adress of the avatar of the searched item (if avatar enabled)
+     *
+     * @param string $id id of the item which can be username, user_id, Seminar_id or Institut_id
+     * @return string url to the avatar image
+     */
+    public function getAvatar($id)
+    {
+        $avatar = $this->getAvatarObject($id);
+        return $avatar ? $avatar->getURL(Avatar::MEDIUM): '';
+    }
+
+    /**
+     * Returns an html tag of the image of the searched item (if avatar enabled)
+     *
+     * @param string $id id of the item which can be username, user_id, Seminar_id or Institut_id
+     * @param string $size enum(NORMAL, SMALL, MEDIUM): size of the avatar
+     * @param array $options
+     * @return string like "<img src="...avatar.jpg" ... >"
+     */
+    public function getAvatarImageTag($id, $size = Avatar::SMALL, $options = [])
+    {
+        $avatar = $this->getAvatarObject($id);
+        return $avatar ? $avatar->getImageTag($size, $options): '';
+    }
+
+    /**
+     * Returns an avatar object for the given combined id.
+     *
+     * @param string $id
+     * @return Avatar|null
+     */
+    protected function getAvatarObject(string $id): ?Avatar
+    {
+        [$id, $type] = explode('__', $id);
+
+        switch ($type) {
+            case 'person':
+                return Avatar::getAvatar($id);
+            case 'seminar':
+                return CourseAvatar::getAvatar($id);
+            case 'institute':
+                return InstituteAvatar::getAvatar($id);
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * A very simple overwrite of the same method from SearchType class.
+     * returns the absolute path to this class for autoincluding this class.
+     *
+     * @return string path to this class
+     */
+    public function includePath()
+    {
+        return studip_relative_path(__FILE__);
+    }
+
+    /**
+     * Returns a list of all institute ids the given user is admin for.
+     *
+     * @param User $user
+     * @return string[]
+     */
+    protected function getAdminInstitutes(User $user): array
+    {
+        $query = "SELECT DISTINCT i.`Institut_id`
+                  FROM `user_inst` AS ui
+                  JOIN `Institute` AS i 
+                    ON ui.`Institut_id` IN (i.`Institut_id`, i.`fakultaets_id`)   
+                  WHERE ui.`user_id` = :user_id
+                    AND ui.`inst_perms` = 'admin'";
+        return DBManager::get()->fetchFirst($query, [
+            ':user_id' => $user->id,
+        ]);
+    }
+}
-- 
GitLab