diff --git a/app/controllers/course/topics.php b/app/controllers/course/topics.php index fbae33d704bd5d84aa7d03fb98f9210722c7918c..f40c4db78221fe558b73cf1f75b59e53234f7e71 100644 --- a/app/controllers/course/topics.php +++ b/app/controllers/course/topics.php @@ -3,6 +3,7 @@ class Course_TopicsController extends AuthenticatedController { protected $allow_nobody = true; + protected $_autobind = true; public function before_filter(&$action, &$args) { @@ -13,136 +14,123 @@ class Course_TopicsController extends AuthenticatedController checkObject(); checkObjectModule("schedule"); + Navigation::activateItem('/course/schedule/topics'); PageLayout::setTitle(sprintf('%s - %s', Course::findCurrent()->getFullname(), _("Themen"))); $seminar = new Seminar(Course::findCurrent()); $this->forum_activated = $seminar->getSlotModule('forum'); $this->documents_activated = $seminar->getSlotModule('documents'); + if ($action !== 'index' && !$GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) { + throw new AccessDeniedException(); + } + $this->setupSidebar($action); } public function index_action() { - if (Request::isPost() && Request::get("edit") && $GLOBALS['perm']->have_studip_perm("tutor", Context::getId())) { - $topic = new CourseTopic(Request::option("issue_id")); - if ($topic['seminar_id'] && ($topic['seminar_id'] !== Context::getId())) { - throw new AccessDeniedException(); - } + $this->topics = CourseTopic::findBySeminar_id(Context::getId()); + $this->cancelled_dates_locked = LockRules::Check(Context::getId(), 'cancelled_dates'); + } - $topic['title'] = Request::get("title"); - $topic['description'] = Studip\Markup::purifyHtml(Request::get("description")); - $topic['paper_related'] = (bool) Request::int('paper_related'); - if ($topic->isNew()) { - $topic['seminar_id'] = Context::getId(); - } - $topic->store(); + public function delete_action(CourseTopic $topic) + { + if (!Request::isPost()) { + throw new MethodNotAllowedException(); + } - //change dates for this topic - $former_date_ids = $topic->dates->pluck("termin_id"); - $new_date_ids = array_keys(Request::getArray("date")); - foreach (array_diff($former_date_ids, $new_date_ids) as $delete_termin_id) { - $topic->dates->unsetByPk($delete_termin_id); - } - foreach (array_diff($new_date_ids, $former_date_ids) as $add_termin_id) { - $date = CourseDate::find($add_termin_id); - if ($date) { - $topic->dates[] = $date; - } - } - $topic->store(); + if ($topic->seminar_id && ($topic->seminar_id !== Context::getId())) { + throw new AccessDeniedException(); + } - if (Request::get("folder")) { - $topic->connectWithDocumentFolder(); - } + if ($topic->delete()) { + PageLayout::postSuccess(_('Thema gelöscht.')); + } - // create a connection to the module forum (can be anything) - // will update title and description automagically - if (Request::get("forumthread")) { - $topic->connectWithForumThread(); - } + $this->redirect('course/topics'); + } - if (Request::option("issue_id") === "new") { - Request::set("open", $topic->getId()); - } - PageLayout::postMessage(MessageBox::success(_("Thema gespeichert."))); - $this->redirect("course/topics/index"); + public function edit_action(CourseTopic $topic = null) + { + PageLayout::setTitle($topic->isNew() ? _('Neues Thema erstellen') : sprintf(_('Bearbeiten: %s'), $topic->title)); + + $this->dates = CourseDate::findBySeminar_id(Context::getId()); + } + + public function store_action(CourseTopic $topic = null) + { + if (!Request::isPost()) { + throw new MethodNotAllowedException(); } - if (Request::isPost() && Request::option("move_down")) { - $topics = CourseTopic::findBySeminar_id(Context::getId()); - $mainkey = null; - foreach ($topics as $key => $topic) { - if ($topic->getId() === Request::option("move_down")) { - $mainkey = $key; - } - $topic['priority'] = $key + 1; - } - if ($mainkey !== null && $mainkey < count($topics)) { - $topics[$mainkey]->priority++; - $topics[$mainkey + 1]->priority--; - } - foreach ($topics as $key => $topic) { - $topic->store(); - } + if ($topic->seminar_id && ($topic->seminar_id !== Context::getId())) { + throw new AccessDeniedException(); } - if (Request::isPost() && Request::option("move_up")) { - $topics = CourseTopic::findBySeminar_id(Context::getId()); - foreach ($topics as $key => $topic) { - if (($topic->getId() === Request::option("move_up")) && $key > 0) { - $topic['priority'] = $key; - $topics[$key - 1]->priority = $key + 1; - $topics[$key - 1]->store(); - } else { - $topic['priority'] = $key + 1; - } - $topic->store(); + + $topic->title = Request::i18n("title"); + $topic->description = Request::i18n('description', null, function ($string) { + return Studip\Markup::purifyHtml($string); + + }); + $topic->paper_related = Request::bool('paper_related', false); + if ($topic->isNew()) { + $topic->seminar_id = Context::getId(); + } + $topic->store(); + + //change dates for this topic + $former_date_ids = $topic->dates->pluck('termin_id'); + $new_date_ids = array_keys(Request::getArray('date')); + foreach (array_diff($former_date_ids, $new_date_ids) as $delete_termin_id) { + $topic->dates->unsetByPk($delete_termin_id); + } + foreach (array_diff($new_date_ids, $former_date_ids) as $add_termin_id) { + $date = CourseDate::find($add_termin_id); + if ($date) { + $topic->dates[] = $date; } } + $topic->store(); - Navigation::activateItem('/course/schedule/topics'); - $this->topics = CourseTopic::findBySeminar_id(Context::getId()); - $this->cancelled_dates_locked = LockRules::Check(Context::getId(), 'cancelled_dates'); - } + if (Request::bool('folder')) { + $topic->connectWithDocumentFolder(); + } - public function delete_action($topic_id) - { - if (!$GLOBALS['perm']->have_studip_perm("tutor", Context::getId())) { - throw new AccessDeniedException(); + // create a connection to the module forum (can be anything) + // will update title and description automagically + if (Request::bool('forumthread')) { + $topic->connectWithForumThread(); } - $topic = new CourseTopic($topic_id); + PageLayout::postSuccess(_('Thema gespeichert.')); + $this->redirect($this->indexURL(['open' => $topic->id])); + } - if ($topic['seminar_id'] && ($topic['seminar_id'] !== Context::getId())) { - throw new AccessDeniedException(); + public function move_up_action(CourseTopic $topic) + { + if (!Request::isPost()) { + throw new MethodNotAllowedException(); } - $topic->delete(); - PageLayout::postSuccess(_('Thema gelöscht.')); + $topic->increasePriority(); - $this->redirect('course/topics'); + $this->redirect($this->indexURL(['open' => $topic->id])); } - public function edit_action($topic_id = null) + public function move_down_action(CourseTopic $topic) { - if (!$GLOBALS['perm']->have_studip_perm("tutor", Context::getId())) { - throw new AccessDeniedException(); + if (!Request::isPost()) { + throw new MethodNotAllowedException(); } - $this->topic = new CourseTopic($topic_id); - $this->dates = CourseDate::findBySeminar_id(Context::getId()); - if (Request::isXhr()) { - PageLayout::setTitle($topic_id ? sprintf(_('Bearbeiten: %s'), $this->topic['title']) : _("Neues Thema erstellen")); - } else { - Navigation::activateItem('/course/schedule/topics'); - } + $topic->decreasePriority(); + + $this->redirect($this->indexURL(['open' => $topic->id])); } public function allow_public_action() { - if (!$GLOBALS['perm']->have_studip_perm("tutor", Context::getId())) { - throw new AccessDeniedException(); - } $config = CourseConfig::get(Context::getId()); $config->store('COURSE_PUBLIC_TOPICS', !$config->COURSE_PUBLIC_TOPICS); $this->redirect("course/topics"); @@ -150,9 +138,6 @@ class Course_TopicsController extends AuthenticatedController public function copy_action() { - if (!$GLOBALS['perm']->have_studip_perm("tutor", Context::getId())) { - throw new AccessDeniedException(); - } if (Request::submitted("copy")) { $prio = 1; foreach (Course::find(Context::getId())->topics as $topic) { @@ -227,9 +212,6 @@ class Course_TopicsController extends AuthenticatedController public function fetch_topics_action() { - if (!$GLOBALS['perm']->have_studip_perm("tutor", Request::option("seminar_id"))) { - throw new AccessDeniedException(); - } $this->topics = CourseTopic::findBySeminar_id(Request::option("seminar_id")); $output = [ 'html' => $this->render_template_as_string("course/topics/_topiclist.php") @@ -272,8 +254,8 @@ class Course_TopicsController extends AuthenticatedController if ($GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) { $options = $sidebar->addWidget(new OptionsWidget()); $options->addCheckbox( - _("Themen öffentlich einsehbar"), - CourseConfig::get(Context::getId())->COURSE_PUBLIC_TOPICS, + _('Themen öffentlich einsehbar'), + (bool) CourseConfig::get(Context::getId())->COURSE_PUBLIC_TOPICS, $this->url_for('course/topics/allow_public') ); } diff --git a/app/views/course/topics/edit.php b/app/views/course/topics/edit.php index 43a5c74feb1529878f8e4a7e046e464822140628..9d04927fa3ec3a90e88da090cc13a51907cb5dcc 100644 --- a/app/views/course/topics/edit.php +++ b/app/views/course/topics/edit.php @@ -1,67 +1,66 @@ <? $date_ids = $topic->dates->pluck("termin_id") ?> -<form action="<?= URLHelper::getLink("dispatch.php/course/topics") ?>" method="post" class="default"> +<form action="<?= $controller->store($topic) ?>" method="post" class="default"> <?= CSRFProtection::tokenTag() ?> - <input type="hidden" name="issue_id" value="<?=htmlReady($topic->getId()) ?>"> - <input type="hidden" name="open" value="<?=htmlReady($topic->getId()) ?>"> + <input type="hidden" name="open" value="<?= htmlReady($topic->getId()) ?>"> <input type="hidden" name="edit" value="1"> <fieldset> <legend><?= _('Thema bearbeiten') ?></legend> - <label for="topic_title"> - <span class="required"><?= _("Titel") ?></span> - <input type="text" name="title" id="topic_title" value="<?= htmlReady($topic['title']) ?>" required> + <label> + <span class="required"><?= _('Titel') ?></span> + <?= I18N::input('title', $topic->title, ['required' => '']) ?> </label> - <label for="topic_description"> + <label> <?= _("Beschreibung") ?> - <textarea class="add_toolbar wysiwyg size-l" name="description" id="topic_description"><?= wysiwygReady($topic['description']) ?></textarea> - <? if (Request::isAjax()) : ?> - <script>jQuery('.add_toolbar').addToolbar();</script> - <? endif ?> + <?= I18N::textarea('description', $topic->description, [ + 'class' => 'add_toolbar wysiwyg size-l', + ]) ?> </label> - <? if ($documents_activated) : ?> - <label> - <? $folder = $topic->folders->first() ?> - <? if ($folder) : ?> - <?= Icon::create('accept', 'accept')->asImg(['class' => "text-bottom"]) ?> - <?= _("Dateiordner vorhanden ") ?> - <? else : ?> - <input type="checkbox" name="folder" id="topic_folder"> - <?= _("Dateiordner anlegen") ?> - <? endif ?> - </label> + <? if ($documents_activated) : ?> + <label> + <? $folder = $topic->folders->first() ?> + <? if ($folder) : ?> + <?= Icon::create('accept', Icon::ROLE_ACCEPT)->asImg(['class' => 'text-bottom']) ?> + <?= _('Dateiordner vorhanden ') ?> + <? else : ?> + <input type="checkbox" name="folder" id="topic_folder" value="1"> + <?= _('Dateiordner anlegen') ?> <? endif ?> + </label> + <? endif ?> - <? if ($forum_activated) : ?> - <label> - <? if ($topic->forum_thread_url) : ?> - <?= Icon::create('accept', 'accept')->asImg(['class' => "text-bottom"]) ?> - <?= _("Forenthema vorhanden ") ?> - <? else : ?> - <input type="checkbox" name="forumthread" id="topic_forumthread"> - <?= _("Forenthema anlegen") ?> - <? endif ?> - </label> + <? if ($forum_activated) : ?> + <label> + <? if ($topic->forum_thread_url) : ?> + <?= Icon::create('accept', Icon::ROLE_ACCEPT)->asImg(['class' => 'text-bottom']) ?> + <?= _('Forenthema vorhanden ') ?> + <? else : ?> + <input type="checkbox" name="forumthread" id="topic_forumthread" value="1"> + <?= _('Forenthema anlegen') ?> <? endif ?> + </label> + <? endif ?> - <h2><?= _("Termine") ?></h2> + <h2><?= _('Termine') ?></h2> <? foreach ($dates as $date) : ?> <label> - <input type="checkbox" name="date[<?= $date->getId() ?>]" value="1" class="text-bottom"<?= in_array($date->getId(), $date_ids) ? " checked" : "" ?>> - <?= Icon::create('date', 'info')->asImg(['class' => "text-bottom"]) ?> - <?= (floor($date['date'] / 86400) !== floor($date['end_time'] / 86400)) ? date("d.m.Y, H:i", $date['date'])." - ".date("d.m.Y, H:i", $date['end_time']) : date("d.m.Y, H:i", $date['date'])." - ".date("H:i", $date['end_time']) ?> - <? $localtopics = $date->topics ?> - <? if (count($localtopics)) : ?> + <input type="checkbox" name="date[<?= htmlReady($date->id) ?>]" value="1" class="text-bottom" + <? if (in_array($date->id, $date_ids)) echo 'checked'; ?>> + <?= Icon::create('date', Icon::ROLE_INFO)->asImg(['class' => 'text-bottom']) ?> + <?= floor($date['date'] / 86400) !== floor($date['end_time'] / 86400) ? date("d.m.Y, H:i", $date['date'])." - ".date("d.m.Y, H:i", $date['end_time']) : date("d.m.Y, H:i", $date['date'])." - ".date("H:i", $date['end_time']) ?> + + <? if (count($date->topics) > 0) : ?> ( - <? foreach ($localtopics as $key => $localtopic) : ?> - <a href="<?= URLHelper:: getLink("dispatch.php/course/topics/index", ['open' => $localtopic->getId()]) ?>"> - <?= Icon::create('topic', 'clickable')->asImg(['class' => "text-bottom"]) ?> - <?= htmlReady($localtopic['title']) ?> + <? foreach ($date->topics as $key => $localtopic) : ?> + <a href="<?= $controller->index(['open' => $localtopic->id]) ?>"> + <?= Icon::create('topic')->asImg(['class' => 'text-bottom']) ?> + <?= htmlReady($localtopic->title) ?> </a> <? endforeach ?> ) @@ -90,5 +89,3 @@ </div> </footer> </form> - -<br> diff --git a/app/views/course/topics/index.php b/app/views/course/topics/index.php index 79b74a252cd987326367aaac5723b4c6104c02d2..39635d17586422698a3fe913452ab907669f354c 100644 --- a/app/views/course/topics/index.php +++ b/app/views/course/topics/index.php @@ -83,34 +83,36 @@ </table> <div style="text-align: center;"> <? if ($GLOBALS['perm']->have_studip_perm("tutor", Context::getId())) : ?> - <?= \Studip\LinkButton::createEdit(_('Bearbeiten'), - $controller->url_for('course/topics/edit/' . $topic->getId()), [ - 'data-dialog' => '' - ]) ?> - - <?= Studip\LinkButton::create( - _('Löschen'), - $controller->url_for('course/topics/delete/' . $topic->getId()), - ['data-confirm' => _('Wirklich löschen?')] + <?= Studip\LinkButton::createEdit( + _('Bearbeiten'), + $controller->editURL($topic), + ['data-dialog' => ''] ) ?> + <form action="<?= $controller->delete($topic) ?>" method="post" style="display: inline"> + <?= Studip\Button::create( + _('Löschen'), + 'delete', + ['data-confirm' => _('Wirklich löschen?')] + ) ?> + </form> + <? if (!$cancelled_dates_locked && $topic->dates->count()) : ?> <?= \Studip\LinkButton::create(_("Alle Termine ausfallen lassen"), URLHelper::getURL("dispatch.php/course/cancel_dates", ['issue_id' => $topic->getId()]), ['data-dialog' => '']) ?> <? endif ?> + + <span class="button-group"> <? if ($key > 0) : ?> - <form action="<?=$controller->link_for()?>" method="post" style="display: inline;"> - <input type="hidden" name="move_up" value="<?= $topic->getId() ?>"> - <input type="hidden" name="open" value="<?= $topic->getId() ?>"> - <?= \Studip\Button::createMoveUp(_("nach oben verschieben")) ?> + <form action="<?= $controller->move_up($topic) ?>" method="post" style="display: inline;"> + <?= Studip\Button::createMoveUp(_('nach oben verschieben')) ?> </form> <? endif ?> <? if ($key < count($topics) - 1) : ?> - <form action="<?=$controller->link_for()?>" method="post" style="display: inline;"> - <input type="hidden" name="move_down" value="<?= $topic->getId() ?>"> - <input type="hidden" name="open" value="<?= $topic->getId() ?>"> - <?= \Studip\Button::createMoveDown(_("nach unten verschieben")) ?> - </form> + <form action="<?=$controller->move_down($topic)?>" method="post" style="display: inline;"> + <?= Studip\Button::createMoveDown(_('nach unten verschieben')) ?> + </form> <? endif ?> + </span> <? endif ?> </div> </div> diff --git a/lib/models/CourseTopic.class.php b/lib/models/CourseTopic.class.php index adad4d167e976ed13c950923eb90d0d61159d16d..56a060d16df369aced98064e050d9e0b144fb03b 100644 --- a/lib/models/CourseTopic.class.php +++ b/lib/models/CourseTopic.class.php @@ -19,57 +19,33 @@ * @property string priority database column * @property string mkdate database column * @property string chdate database column - * @property DocumentFolder folder belongs_to DocumentFolder + * @property Folder folder belongs_to DocumentFolder * @property Course course belongs_to Course * @property User author belongs_to User * @property SimpleORMapCollection dates has_and_belongs_to_many CourseDate */ class CourseTopic extends SimpleORMap { - public static function findByTermin_id($termin_id) - { - return self::findBySQL("INNER JOIN themen_termine USING (issue_id) - WHERE themen_termine.termin_id = ? - ORDER BY priority ASC", - [$termin_id] - ); - } - - public static function findBySeminar_id($seminar_id, $order_by = 'ORDER BY priority') - { - return parent::findBySeminar_id($seminar_id, $order_by); - } - - public static function findByTitle($seminar_id, $name) - { - return self::findOneBySQL("seminar_id = ? AND title = ?", [$seminar_id, $name]); - } - - public static function getMaxPriority($seminar_id) - { - return DBManager::get()->fetchColumn("SELECT MAX(priority) FROM themen WHERE seminar_id=?", [$seminar_id]); - } - protected static function configure($config = []) { $config['db_table'] = 'themen'; $config['has_and_belongs_to_many']['dates'] = [ - 'class_name' => 'CourseDate', + 'class_name' => CourseDate::class, 'thru_table' => 'themen_termine', 'order_by' => 'ORDER BY date', 'on_delete' => 'delete', 'on_store' => 'store' ]; $config['has_many']['folders'] = [ - 'class_name' => 'Folder', + 'class_name' => Folder::class, 'assoc_func' => 'findByTopic_id' ]; $config['belongs_to']['course'] = [ - 'class_name' => 'Course', + 'class_name' => Course::class, 'foreign_key' => 'seminar_id' ]; $config['belongs_to']['author'] = [ - 'class_name' => 'User', + 'class_name' => User::class, 'foreign_key' => 'author_id' ]; @@ -79,9 +55,36 @@ class CourseTopic extends SimpleORMap $config['registered_callbacks']['after_store'][] = 'cbUpdateConnectedContentModules'; $config['registered_callbacks']['before_delete'][] = 'cbUnlinkConnectedContentModules'; + $config['i18n_fields']['title'] = true; + $config['i18n_fields']['description'] = true; + parent::configure($config); } + public static function findByTermin_id($termin_id) + { + return self::findBySQL("INNER JOIN themen_termine USING (issue_id) + WHERE themen_termine.termin_id = ? + ORDER BY priority ASC", + [$termin_id] + ); + } + + public static function findBySeminar_id($seminar_id, $order_by = 'ORDER BY priority') + { + return parent::findBySeminar_id($seminar_id, $order_by); + } + + public static function findByTitle($seminar_id, $name) + { + return self::findOneBySQL("seminar_id = ? AND title = ?", [$seminar_id, $name]); + } + + public static function getMaxPriority($seminar_id) + { + return DBManager::get()->fetchColumn("SELECT MAX(priority) FROM themen WHERE seminar_id=?", [$seminar_id]); + } + /** * set or update connection with document folder */ @@ -179,7 +182,7 @@ class CourseTopic extends SimpleORMap $folders = array_merge($folders, $date->folders->getArrayCopy()); } foreach ($folders as $folder) { - list($files, $typed_folders) = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); + [$files, $typed_folders] = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); foreach ($files as $file) { $all_files[$file->id] = $file; } @@ -187,4 +190,67 @@ class CourseTopic extends SimpleORMap } return ['files' => $all_files, 'folders' => $all_folders]; } + + /** + * Increases the priority of this topic. Meaning the topic will be sorted further up. + * Be aware that this actually decreases the priority property since lower numbers + * mean higher priority. + * + * @return boolean + */ + public function increasePriority() + { + // Update all the course's topics with a lower priority than this one + $query = "UPDATE `themen` + SET `priority` = `priority` + 1 + WHERE `seminar_id` = :course_id + AND `priority` < :current_priority + ORDER BY `priority` DESC + LIMIT 1"; + $changed = DBManager::get()->execute($query, [ + ':course_id' => $this->seminar_id, + ':current_priority' => $this->priority, + ]); + + // If anything has changed, decrease priority. Otherwise the current + // topic is already at top. + if ($changed) { + $this->priority -= 1; + $this->store(); + return true; + } + + return false; + } + + /** + * Decreases the priority of this topic. Meaning the topic will be sorted further down. + * Be aware that this actually increases the priority property since higher numbers + * mean lower priority. + */ + public function decreasePriority() + { + // Update all the course's topics with a higher priority than this one + $query = "UPDATE `themen` + SET `priority` = `priority` - 1 + WHERE `seminar_id` = :course_id + AND `priority` > :current_priority + ORDER BY `priority` ASC + LIMIT 1"; + $changed = DBManager::get()->execute($query, [ + ':course_id' => $this->seminar_id, + ':current_priority' => $this->priority, + ]); + + // If anything has changed, increase priority. Otherwise the current + // topic is already at bottom. + if ($changed) { + $this->priority += 1; + $this->store(); + return true; + } + + return false; + + } }