diff --git a/app/controllers/news.php b/app/controllers/news.php index 5377b1c7941fe2a5ce4ddca3127e0e232278b167..e60e174f634fa99c5b76e61c01bee80bdf730e55 100644 --- a/app/controllers/news.php +++ b/app/controllers/news.php @@ -53,6 +53,23 @@ class NewsController extends StudipController 'icon' => 'person', ], ]; + + $this->priorities = [ + 0 => '0 (' . _('normal') . ')', + 1 => '1 (' . _('sehr niedrig') . ')', + 2 => '2', + 3 => '3', + 4 => '4', + 5 => '5', + 6 => '6', + 7 => '7', + 8 => '8', + 9 => '9', + 10 => '10 (' . _('sehr hoch') . ')', + ]; + + $this->roles = NewsRoles::getAvailableRoles(); + $this->rolesStats = RolePersistence::getStatistics(); } /** @@ -166,6 +183,7 @@ class NewsController extends StudipController 'news_basic' => true, 'news_comments' => false, 'news_areas' => false, + 'news_visibility' => false, ]; if ($context_range) { @@ -197,6 +215,15 @@ class NewsController extends StudipController if (!$news->havePermission('edit') && !$news->isNew()) { throw new AccessDeniedException(); } + + if(!$news->isNew()){ + $this->assigned = NewsRoles::getRoles($id); + if ($this->assigned){ + $this->news_isvisible['news_visibility'] = true; + } + $this->roles = NewsRoles::getAvailableRoles($id); + } + // if form sent, get news data by post vars if (Request::get('news_isvisible')) { // visible categories, selected areas, topic, and body are utf8 encoded when sent via ajax @@ -216,6 +243,13 @@ class NewsController extends StudipController ? $this->getTimeStamp(Request::get('news_enddate'), 'end') - $news->date : ''; $news->allow_comments = Request::bool('news_allow_comments', false); + $news->prio = Request::int('news_prio', 0); + $assignedroles = Request::intArray('assignedroles',false); + + $this->assigned = NewsRoles::load($assignedroles); + if ($this->assigned){ + $this->news_isvisible['news_visibility'] = true; + } } elseif ($id) { // if news id given check for valid id and load ranges if ($news->isNew()) { @@ -439,6 +473,10 @@ class NewsController extends StudipController $news->store(); + if ($GLOBALS['perm']->have_perm('admin')) { + NewsRoles::update($news->id, $assignedroles); + } + PageLayout::postSuccess(_('Die Ankündigung wurde gespeichert.')); if (!Request::isXhr() && !$id) { // in fallback mode redirect to edit page with proper news id diff --git a/app/views/news/edit_news.php b/app/views/news/edit_news.php index f5641c157389699f04a127d22746c082f317b191..2559d065842e0111b591dc09988218424ad0ac0a 100644 --- a/app/views/news/edit_news.php +++ b/app/views/news/edit_news.php @@ -23,6 +23,7 @@ <input type="hidden" name="news_basic_js" value=""> <input type="hidden" name="news_comments_js" value=""> <input type="hidden" name="news_areas_js" value=""> + <input type="hidden" name="news_visibility_js" value=""> <input type="hidden" name="news_isvisible" value="<?=htmlReady(json_encode($news_isvisible))?>"> <input type="hidden" name="news_selectable_areas" value="<?=htmlReady(json_encode($area_options_selectable))?>"> <input type="hidden" name="news_selected_areas" value="<?=htmlReady(json_encode($area_options_selected))?>"> @@ -256,6 +257,50 @@ </div> </fieldset> + <fieldset <?= $news_isvisible['news_visibility'] ? '' : 'class="collapsed"' ?>> + <legend class="news_visibility_header" id="news_visibility"> + <?= _('Sichtbarkeitseinstellungen') ?> + </legend> + + <? if ($anker == 'news_visibility') : ?> + <a name='anker'></a> + <? endif ?> + + <label> + <?= _('Priorität') ?> + + <select name="news_prio"> + <? foreach ($priorities as $key => $label) : ?> + <option value ="<?= $key ?>"<?= $news->prio == $key ? ' selected' : '' ?>><?= $label ?></option> + <? endforeach ?> + </select> + </label> + <? if ($GLOBALS['perm']->have_perm('admin')) : ?> + <label> + <?= _('Sichtbarkeit') ?> + + <select id="assignedroles" name="assignedroles[]" multiple> + <? if ($assigned) : ?> + <? foreach ($assigned as $assignedrole) : ?> + <option value="<?= $assignedrole->getRoleid() ?>" selected> + <?= htmlReady($assignedrole->getRolename()) ?> + <? if ($assignedrole->getSystemtype()) : ?>[<?= _('Systemrolle') ?>]<? endif ?> + (<?= $rolesStats[$assignedrole->getRoleid()]['explicit'] + $rolesStats[$assignedrole->getRoleid()]['implicit'] ?>) + </option> + <? endforeach ?> + <? endif ?> + <? foreach ($roles as $role) : ?> + <option value="<?= $role->getRoleid() ?>"> + <?= htmlReady($role->getRolename()) ?> + <? if ($role->getSystemtype()) : ?>[<?= _('Systemrolle') ?>]<? endif ?> + (<?= $rolesStats[$role->getRoleid()]['explicit'] + $rolesStats[$role->getRoleid()]['implicit'] ?>) + </option> + <? endforeach ?> + </select> + </label> + <? endif ?> + </fieldset> + <footer data-dialog-button> <? if ($news->isNew()) : ?> <?= Button::createAccept(_('Ankündigung erstellen'), 'save_news') ?> @@ -280,4 +325,9 @@ event.preventDefault(); } }); + <? if ($GLOBALS['perm']->have_perm('admin')) : ?> + $("#assignedroles").select2({ + width: '100%' + }); + <? endif ?> </script> diff --git a/db/migrations/5.1.16_tic_410.php b/db/migrations/5.1.16_tic_410.php new file mode 100644 index 0000000000000000000000000000000000000000..af9774e8d6a8d96f81c74361b63353e7ef909cef --- /dev/null +++ b/db/migrations/5.1.16_tic_410.php @@ -0,0 +1,43 @@ +<?php +class tic410 extends Migration +{ + public function description() + { + return "create NewsRoles table"; + } + + public function up() + { + $db = DBManager::get(); + + $query = 'CREATE TABLE IF NOT EXISTS `news_roles` ( + `news_id` CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, + `roleid` int(10) NOT NULL, + PRIMARY KEY (`news_id`, `roleid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;'; + $db->exec($query); + + $query = 'ALTER TABLE `news` ADD COLUMN `prio` tinyint(2) NOT NULL DEFAULT 0 AFTER `allow_comments`'; + $db->exec($query); + + $query = "INSERT IGNORE INTO `config` (`field`, `value`, `type`, `range`, `mkdate`, `chdate`, `description`) + VALUES (:name, :value, :type, :range, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :description)"; + + $statement = DBManager::get()->prepare($query); + $statement->execute([ + ':name' => 'NEWS_ONLY_SYSTEM_ROLES', + ':description' => 'Über diese Option wird die Auswahl der rollenspezifischen Ankündigungen auf Systemrollen begrenzt', + ':range' => 'global', + ':type' => 'boolean', + ':value' => '1' + ]); + } + + public function down() + { + $db = DBManager::get(); + + $db->exec('DROP TABLE IF EXISTS `news_roles`'); + $db->exec('ALTER TABLE `news` DROP COLUMN `prio`'); + } +} diff --git a/lib/models/NewsRoles.class.php b/lib/models/NewsRoles.class.php new file mode 100644 index 0000000000000000000000000000000000000000..e2de5f850016db69e73fe74875bc13db9b582be4 --- /dev/null +++ b/lib/models/NewsRoles.class.php @@ -0,0 +1,140 @@ +<?php + +/** + * NewsRoles.class.php - model class for the news roles + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Sebastian Biller <s.biller@tu-braunschweig.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package admin + * @since 5.1 + * + * @property string news_id database column + * @property int roleid database column + */ + +class NewsRoles extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'news_roles'; + + $config['belongs_to']['news_ranges'] = [ + 'class_name' => StudipNews::class, + 'foreign_key' => 'news_id', + ]; + + parent::configure($config); + } + + public static function checkUserAccess($news_id, $user_id = null) + { + $user_id = $user_id ?: (isset($GLOBALS['user']) ? $GLOBALS['user']->id : null); + $news_roles = self::getRoles($news_id); + + if (!$news_roles) { + return true; + } + + if (!$user_id) { + return false; + } + + $user_roles = RolePersistence::getAssignedRoles($user_id, true); + + foreach ($news_roles as $news_role) { + foreach ($user_roles as $user_role) { + if ($news_role->getRoleid() === $user_role->getRoleid()) { + return true; + } + } + } + + return false; + } + + public static function getRoles($news_id) + { + $news_roles = self::findBynews_id($news_id); + $news_role_ids = []; + foreach ($news_roles as $news_role) { + $news_role_ids[] = $news_role['roleid']; + } + + $only_system_roles = Config::get()->NEWS_ONLY_SYSTEM_ROLES; + $roles = RolePersistence::getAllRoles(); + $re = []; + foreach ($news_role_ids as $role_id) { + if (isset($roles[$role_id])) { + if ($only_system_roles && !$roles[$role_id]->getSystemtype()) { + continue; + } + $re[$role_id] = $roles[$role_id]; + } + } + return $re; + } + + public static function getAvailableRoles($news_id = null) + { + $news_role_ids = []; + if ($news_id) { + $news_roles = self::findBynews_id($news_id); + foreach ($news_roles as $news_role) { + $news_role_ids[] = $news_role['roleid']; + } + } + + $only_system_roles = Config::get()->NEWS_ONLY_SYSTEM_ROLES; + $roles = RolePersistence::getAllRoles(); + $rolesStats = RolePersistence::getStatistics(); + $re = []; + foreach ($roles as $key => $role) { + if (!in_array($key, $news_role_ids)) { + if ($only_system_roles && !$role->getSystemtype()) { + continue; + } + if ($rolesStats[$role->getRoleid()]['explicit'] + $rolesStats[$role->getRoleid()]['implicit'] == 0) { + continue; + } + $re[$key] = $role; + } + } + + return $re; + } + + public static function update($news_id, $new_roles) + { + self::deleteBynews_id($news_id); + + if ($new_roles) { + foreach ($new_roles as $new_role) { + $NewsRoles = new self(); + $NewsRoles->news_id = $news_id; + $NewsRoles->roleid = $new_role; + $NewsRoles->store(); + } + } + } + + public static function load($new_roles) + { + $roles = RolePersistence::getAllRoles(); + + return array_filter(array_map( + function ($role_id) use ($roles) { + if (!isset($roles[$role_id])) { + return false; + } + return [$role_id, $roles[$role_id]]; + }, + $new_roles + )); + } +} diff --git a/lib/models/StudipNews.class.php b/lib/models/StudipNews.class.php index b528604bfbf8b27ca374ef0aaca1686e54cb5498..227561a125db37aaaf4ef83ef1ebda9d1244a7ca 100644 --- a/lib/models/StudipNews.class.php +++ b/lib/models/StudipNews.class.php @@ -36,6 +36,7 @@ require_once 'lib/object.inc.php'; * @property string user_id database column * @property string expire database column * @property string allow_comments database column + * @property int prio database column * @property string chdate database column * @property string chdate_uid database column * @property string mkdate database column @@ -64,6 +65,11 @@ class StudipNews extends SimpleORMap implements PrivacyObject 'class_name' => 'User', 'foreign_key' => 'user_id', ]; + $config['has_many']['news_roles'] = [ + 'class_name' => NewsRoles::class, + 'assoc_foreign_key' => 'news_id', + 'on_delete' => 'delete' + ]; $config['i18n_fields']['topic'] = true; $config['i18n_fields']['body'] = true; @@ -96,25 +102,33 @@ class StudipNews extends SimpleORMap implements PrivacyObject INNER JOIN news USING (news_id) WHERE range_id = ? {$clause} "; if (Config::get()->SORT_NEWS_BY_CHDATE) { - $query .= "ORDER BY chdate DESC, date DESC, topic ASC"; + $query .= "ORDER BY prio DESC, chdate DESC, date DESC, topic ASC"; } else { - $query .= "ORDER BY date DESC, chdate DESC, topic ASC"; + $query .= "ORDER BY prio DESC, date DESC, chdate DESC, topic ASC"; } $statement = DBManager::get()->prepare($query); $statement->execute([$range_id]); $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + if (!(isset($GLOBALS['perm']) && $GLOBALS['perm']->have_perm('root'))) { + if (!(User::find($range_id) && $GLOBALS['user']->id == $range_id)) { + foreach ($ret as $news_id => $news) { + if (!NewsRoles::checkUserAccess($news_id)) { + unset($ret[$news_id]); + } + } + } + } return $as_objects ? static::GetNewsObjects($ret) : $ret; } public static function CountUnread($range_id = 'studip', $user_id = false) { - $query = "SELECT SUM(nw.chdate > IFNULL(b.visitdate, :threshold) AND nw.user_id != :user_id) + $query = "SELECT nw.news_id as idx, nw.chdate > IFNULL(b.visitdate, :threshold) AS active FROM news_range a LEFT JOIN news nw ON (a.news_id = nw.news_id AND UNIX_TIMESTAMP() BETWEEN date AND date + expire) LEFT JOIN object_user_visits b ON (b.object_id = nw.news_id AND b.user_id = :user_id AND b.plugin_id = :plugin_id) - WHERE a.range_id = :range_id - GROUP BY a.range_id"; + WHERE a.range_id = :range_id AND nw.user_id != :user_id"; $statement = DBManager::get()->prepare($query); $statement->bindValue(':threshold', object_get_visit_threshold()); $statement->bindValue(':user_id', $user_id ?: $GLOBALS['user']->id); @@ -122,7 +136,13 @@ class StudipNews extends SimpleORMap implements PrivacyObject $plugin_id = object_type_to_id('news'); $statement->bindValue(':plugin_id', $plugin_id); $statement->execute(); - return (int) $statement->fetchColumn(); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + foreach ($ret as $news_id => $news) { + if (!NewsRoles::checkUserAccess($news_id, $user_id) && !$GLOBALS['perm']->have_perm('root') || !$news['active']) { + unset($ret[$news_id]); + } + } + return (int) count($ret); } public static function GetNewsByAuthor($user_id, $as_objects = false) @@ -131,13 +151,22 @@ class StudipNews extends SimpleORMap implements PrivacyObject FROM news WHERE user_id = ? "; if (Config::get()->SORT_NEWS_BY_CHDATE) { - $query .= "ORDER BY chdate DESC, date DESC"; + $query .= "ORDER BY prio DESC, chdate DESC, date DESC"; } else { - $query .= "ORDER BY date DESC, chdate DESC"; + $query .= "ORDER BY prio DESC, date DESC, chdate DESC"; } $statement = DBManager::get()->prepare($query); $statement->execute([$user_id]); $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + if (!(isset($GLOBALS['perm']) && $GLOBALS['perm']->have_perm('root'))) { + if ($GLOBALS['user']->id != $user_id) { + foreach ($ret as $news_id => $news) { + if (!NewsRoles::checkUserAccess($news_id)) { + unset($ret[$news_id]); + } + } + } + } return $as_objects ? static::GetNewsObjects($ret) : $ret; }