aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFinn Schneider <schneider@data-quest.de>2025-12-16 10:28:32 +0000
committerMurtaza Sultani <sultani@data-quest.de>2025-12-16 11:28:32 +0100
commit61b2e2ba3271a911308f80fcbe2073852312bf80 (patch)
treee85e7f9829a104bbfc5d29437b3691ca25abc04d /app
parent7ace20d44455006de508db7a82b8038960b7fae7 (diff)
Resolve "Bulk-Aktions in der Themenansicht"
Closes #10 Merge request studip/studip!4514
Diffstat (limited to 'app')
-rw-r--r--app/controllers/course/topics.php113
-rw-r--r--app/views/course/topics/_dates.php13
-rw-r--r--app/views/course/topics/_material.php34
-rw-r--r--app/views/course/topics/details.php28
-rw-r--r--app/views/course/topics/index.php236
5 files changed, 284 insertions, 140 deletions
diff --git a/app/controllers/course/topics.php b/app/controllers/course/topics.php
index e955ba1..026c2bf 100644
--- a/app/controllers/course/topics.php
+++ b/app/controllers/course/topics.php
@@ -21,7 +21,10 @@ class Course_TopicsController extends AuthenticatedController
$this->forum_activated = $course->isToolActive(CoreForum::class);
$this->documents_activated = $course->isToolActive(CoreDocuments::class);
- if ($action !== 'index' && !$GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) {
+ if (
+ !in_array($action, ['index', 'details'])
+ && !User::findCurrent()->hasPermissionLevel('tutor', Context::get())
+ ) {
throw new AccessDeniedException();
}
@@ -37,9 +40,7 @@ class Course_TopicsController extends AuthenticatedController
public function delete_action(CourseTopic $topic)
{
- if (!Request::isPost()) {
- throw new MethodNotAllowedException();
- }
+ CSRFProtection::verifyUnsafeRequest();
if ($topic->seminar_id && ($topic->seminar_id !== Context::getId())) {
throw new AccessDeniedException();
@@ -102,7 +103,7 @@ class Course_TopicsController extends AuthenticatedController
}
PageLayout::postSuccess(_('Thema gespeichert.'));
- $this->redirect($this->indexURL(['open' => $topic->id]));
+ $this->redirect($this->indexURL());
}
public function swap_action(CourseTopic $a, CourseTopic $b)
@@ -123,7 +124,7 @@ class Course_TopicsController extends AuthenticatedController
$a->store();
$b->store();
- $this->redirect($this->indexURL(['open' => $a->id]));
+ $this->redirect($this->indexURL());
}
public function allow_public_action()
@@ -219,25 +220,62 @@ class Course_TopicsController extends AuthenticatedController
$this->render_json($output);
}
+ public function bulk_action(string $action): void
+ {
+ CSRFProtection::verifyUnsafeRequest();
+ $topic_ids = Request::optionArray('topics');
+
+ [$callback, $success, $failure] = match ($action) {
+ 'ftopic' => [
+ fn(CourseTopic $topic) => $topic->connectWithForumThread(),
+ _('Forumsthemen erfolgreich angelegt.'),
+ _('Fehler beim Anlegen von Forumsthemen zu:'),
+ ],
+ 'folder' => [
+ fn(CourseTopic $topic) => $topic->connectWithDocumentFolder(),
+ _('Ordner erfolgreich angelegt.'),
+ _('Fehler beim Anlegen von Ordnern zu:'),
+ ],
+ 'delete' => [
+ fn(CourseTopic $topic) => $topic->delete(),
+ _('Erfolgreich gelöscht.'),
+ _('Fehler beim Löschen von:'),
+ ],
+ default => throw new InvalidArgumentException('Unknown action'),
+ };
+
+ $errors = [];
+ CourseTopic::findEachMany(
+ function (CourseTopic $topic) use ($callback, &$errors) {
+ if (!$callback($topic)) {
+ $errors[] = $topic->title;
+ }
+ },
+ $topic_ids
+ );
+ if (count($errors) === 0) {
+ PageLayout::postSuccess($success);
+ } else {
+ PageLayout::postError(
+ $failure,
+ array_map('htmlReady', $errors)
+ );
+ }
+
+ $this->redirect($this->indexURL());
+ }
+
+ public function details_action(CourseTopic $topic)
+ {
+ PageLayout::setTitle(sprintf(_('Details: %s'), htmlReady($topic->title)));
+ $this->render_template('course/topics/details');
+ }
+
private function setupSidebar($action)
{
$sidebar = Sidebar::get();
$actions = $sidebar->addWidget(new ActionsWidget());
- if ($action === 'index') {
- $actions->addLink(
- _('Alle Themen aufklappen'),
- $this->url_for('course/topics/show'),
- Icon::create('arr_1down'),
- ['onclick' => "jQuery('table.withdetails > tbody > tr:not(.details):not(.open) > :first-child a').click(); return false;"]
- );
- $actions->addLink(
- _('Alle Themen zuklappen'),
- $this->url_for('course/topics/hide'),
- Icon::create('arr_1right'),
- ['onclick' => "jQuery('table.withdetails > tbody > tr:not(.details).open > :first-child a').click(); return false;"]
- );
- }
if ($GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) {
$actions->addLink(
_('Neues Thema erstellen'),
@@ -286,4 +324,39 @@ class Course_TopicsController extends AuthenticatedController
return $links;
}
+
+ public function getActionMenu(CourseTopic $topic): ActionMenu
+ {
+ $actions = ActionMenu::get();
+ $actions->setContext(htmlReady($topic->title));
+
+ if (User::findCurrent()->hasPermissionLevel('tutor', Context::get())) {
+ $actions->addLink(
+ $this->editURL($topic),
+ _('Bearbeiten'),
+ Icon::create('edit')
+ )->asDialog();
+ $actions->addButton(
+ 'delete',
+ _('Löschen'),
+ Icon::create('trash'),
+ [
+ 'formaction' => $this->delete($topic),
+ 'data-confirm' => _('Das Thema wirklich löschen?'),
+ ]
+ );
+ if (!$this->cancelled_dates_locked && count($topic->dates) > 0) {
+ $actions->addLink(
+ URLHelper::getURL(
+ 'dispatch.php/course/cancel_dates',
+ ['issue_id' => $topic->id]
+ ),
+ _('Alle Termine ausfallen lassen'),
+ Icon::create('date')
+ )->asDialog();
+ }
+ }
+
+ return $actions;
+ }
}
diff --git a/app/views/course/topics/_dates.php b/app/views/course/topics/_dates.php
new file mode 100644
index 0000000..4fc37f6
--- /dev/null
+++ b/app/views/course/topics/_dates.php
@@ -0,0 +1,13 @@
+<ul class="clean">
+ <? foreach ($topic->dates as $date) : ?>
+ <li>
+ <a href="<?= URLHelper::getLink('dispatch.php/course/dates/details/' . $date->id) ?>"
+ data-dialog="size=auto"
+ style="white-space: nowrap"
+ >
+ <?= Icon::create('date')->asSvg(['class' => 'text-bottom']) ?>
+ <?= htmlReady($date->getFullName()) ?>
+ </a>
+ </li>
+ <? endforeach ?>
+</ul>
diff --git a/app/views/course/topics/_material.php b/app/views/course/topics/_material.php
new file mode 100644
index 0000000..5e853ce
--- /dev/null
+++ b/app/views/course/topics/_material.php
@@ -0,0 +1,34 @@
+<ul class="clean">
+ <? $folder = $topic->folders->first() ?>
+ <? if ($documents_activated && $folder) : ?>
+ <li>
+ <? $folder_label = sprintf(_('Zu Dateiordner von Thema %s navigieren'), htmlReady($topic->title)) ?>
+ <a
+ href="<?= URLHelper::getLink('dispatch.php/course/files/index/' . $folder->id) ?>"
+ aria-label="<?= $folder_label ?>"
+ title="<?= $folder_label ?>"
+ >
+ <?= $folder->getTypedFolder()->getIcon('clickable')->asSvg(['class' => 'text-bottom']) ?>
+ <span class="<?= $always_show ? '' : 'responsive-hidden' ?>">
+ <?= _('Dateiordner') ?>
+ </span>
+ </a>
+ </li>
+ <? endif ?>
+
+ <? if ($forum_activated && $topic->forum_thread_url) : ?>
+ <li>
+ <? $ftopic_label = sprintf(_('Zu Forumsthema von Thema %s navigieren'), htmlReady($topic->title)) ?>
+ <a
+ href="<?= URLHelper::getLink($topic->forum_thread_url) ?>"
+ aria-label="<?= $ftopic_label ?>"
+ title="<?= $ftopic_label ?>"
+ >
+ <?= Icon::create('forum')->asSvg(['class' => 'text-bottom']) ?>
+ <span class="<?= $always_show ? '' : 'responsive-hidden' ?>">
+ <?= _('Thema im Forum') ?>
+ </span>
+ </a>
+ </li>
+ <? endif ?>
+</ul>
diff --git a/app/views/course/topics/details.php b/app/views/course/topics/details.php
new file mode 100644
index 0000000..df72454
--- /dev/null
+++ b/app/views/course/topics/details.php
@@ -0,0 +1,28 @@
+<div>
+ <h2><?= htmlReady($topic->title) ?></h2>
+ <? $has_content = false ?>
+ <? if (
+ ($documents_activated && $topic->folders->first())
+ || ($forum_activated && $topic->forum_thread_url)
+ ) : ?>
+ <h3><?= _('Materialien') ?></h3>
+ <?= $this->render_partial('course/topics/_material.php', ['always_show' => true]) ?>
+ <? $has_content = true ?>
+ <? endif ?>
+
+ <? if (count($topic->dates) > 0) : ?>
+ <h3><?= _('Termine') ?></h3>
+ <?= $this->render_partial('course/topics/_dates.php') ?>
+ <? $has_content = true ?>
+ <? endif ?>
+
+ <? if ($topic->description) : ?>
+ <h3><?= _('Beschreibung') ?></h3>
+ <?= formatReady($topic->description) ?>
+ <? $has_content = true ?>
+ <? endif ?>
+
+ <? if (!$has_content) : ?>
+ <?= _('Keine weiteren Informationen verfügbar.') ?>
+ <? endif ?>
+</div>
diff --git a/app/views/course/topics/index.php b/app/views/course/topics/index.php
index e538542..29a9d98 100644
--- a/app/views/course/topics/index.php
+++ b/app/views/course/topics/index.php
@@ -4,130 +4,126 @@
* @var Course_TopicsController $controller
* @var array<array{next: ?CourseTopic, previous: ?CourseTopic}> $topic_links
*/
+
+use Studip\Button;
+
?>
<? if (count($topics) > 0) : ?>
-<table class="default withdetails">
- <colgroup>
- <col width="50%">
- <col>
- </colgroup>
- <thead>
- <tr>
- <th><?= _('Thema') ?></th>
- <th><?= _('Termine') ?></th>
- </tr>
- </thead>
- <tbody>
- <? foreach ($topics as $key => $topic) : ?>
- <tr class="<?= Request::get("open") === $topic->getId() ? "open" : "" ?>">
- <td>
- <a href="#" name="<?=$topic->getId()?>" onClick="jQuery(this).closest('tr').toggleClass('open'); return false;">
- <? if ($topic->paper_related): ?>
- <?= Icon::create('glossary')->asSvg(array_merge(
- ['class' => 'text-top'],
- tooltip2(_('Thema behandelt eine Hausarbeit oder ein Referat'))
- )) ?>
- <? endif; ?>
- <?= htmlReady($topic['title']) ?>
- </a>
- </td>
- <td>
- <ul class="clean">
- <? foreach ($topic->dates as $date) : ?>
- <li>
- <a href="<?= URLHelper::getLink("dispatch.php/course/dates/details/".$date->getId()) ?>" data-dialog="size=auto">
- <?= Icon::create('date', 'clickable')->asSvg(['class' => 'text-bottom']) ?>
- <?= htmlReady($date->getFullName()) ?>
- </a>
- </li>
- <? endforeach ?>
- </ul>
- </td>
- </tr>
- <tr class="details nohover">
- <td colspan="2">
- <div class="detailscontainer">
- <table class="default nohover">
- <tbody>
- <tr>
- <td><strong><?= _('Beschreibung') ?></strong></td>
- <td><?= formatReady($topic['description']) ?></td>
- </tr>
- <tr>
- <td><strong><?= _('Materialien') ?></strong></td>
- <td>
- <? $material = false ?>
- <ul class="clean">
- <? $folder = $topic->folders->first() ?>
- <? if ($documents_activated && $folder) : ?>
- <li>
- <a href="<?= URLHelper::getLink(
- 'dispatch.php/course/files/index/' . $folder->id
- ) ?>">
- <?= $folder->getTypedFolder()->getIcon('clickable')->asSvg(['class' => 'text-bottom']) ?>
- <?= _('Dateiordner') ?>
- </a>
- </li>
- <? $material = true ?>
- <? endif ?>
-
- <? if ($forum_activated && ($link_to_thread = $topic->forum_thread_url)) : ?>
- <li>
- <a href="<?= URLHelper::getLink($link_to_thread) ?>">
- <?= Icon::create('forum', 'clickable')->asSvg(['class' => 'text-bottom']) ?>
- <?= _('Thema im Forum') ?>
- </a>
- </li>
- <? $material = true ?>
- <? endif ?>
- </ul>
- <? if (!$material) : ?>
- <?= _('Keine Materialien zu dem Thema vorhanden') ?>
+ <form method="POST">
+ <?= CSRFProtection::tokenTag() ?>
+ <table class="default" id="topics_index_table">
+ <thead>
+ <tr>
+ <th scope="col" style="width: 20px">
+ <input
+ type="checkbox"
+ data-proxyfor="#topics_index_table tbody input[type=checkbox]"
+ data-activates="#topics_index_table tfoot button"
+ aria-label="<?= _('Alle Themen auswählen') ?>"
+ >
+ </th>
+ <th scope="col"><?= _('Thema') ?></th>
+ <th scope="col" class="responsive-hidden"><?= _('Termine') ?></th>
+ <th scope="col"><?= _('Materialien') ?></th>
+ <th scope="col" class="responsive-hidden"><?= _('Beschreibung') ?></th>
+ <? if ($is_tutor = User::findCurrent()->hasPermissionLevel('tutor', Context::get())) : ?>
+ <th scope="col" class="actions"><?= _('Aktionen') ?></th>
+ <? endif ?>
+ </tr>
+ </thead>
+ <tbody>
+ <? foreach ($topics as $topic) : ?>
+ <tr>
+ <td>
+ <input type="checkbox" value="<?= htmlReady($topic->id) ?>" name="topics[]"
+ aria-label="<?= sprintf(_('Thema %s auswählen'), htmlReady($topic->title)) ?>">
+ </td>
+ <td>
+ <a
+ href="<?= URLHelper::getLink('dispatch.php/course/topics/details/' . $topic->id) ?>"
+ title=" <?= sprintf(_('Thema %s öffnen'), htmlReady($topic->title)) ?>"
+ aria-label="<?= sprintf(_('Thema %s öffnen'), htmlReady($topic->title)) ?>"
+ data-dialog="size=auto"
+ >
+ <?= htmlReady($topic['title']) ?>
+ </a>
+ <? if ($topic->paper_related): ?>
+ <?= Icon::create('info-circle')->asSvg(array_merge(
+ tooltip2(_('Thema behandelt eine Hausarbeit oder ein Referat'))
+ )) ?>
+ <? endif ?>
+ </td>
+ <td class="responsive-hidden">
+ <?= $this->render_partial('course/topics/_dates.php', ['topic' => $topic]) ?>
+ </td>
+ <td>
+ <?= $this->render_partial('course/topics/_material.php', ['topic' => $topic]) ?>
+ </td>
+ <td class="responsive-hidden">
+ <?= formatReady($topic['description']) ?>
+ </td>
+ <? if ($is_tutor) : ?>
+ <td class="actions">
+ <div>
+ <? $move_up_label = sprintf(_('%s nach oben verschieben'), htmlReady($topic->title));
+ if ($topic_links[$topic->id]['previous']) : ?>
+ <button
+ class="as-link"
+ formaction="<?= $controller->swap($topic, $topic_links[$topic->id]['previous']) ?>"
+ aria-label="<?= $move_up_label ?>"
+ title="<?= $move_up_label ?>"
+ >
+ <?= Icon::create('arr_2up') ?>
+ </button>
+ <? else : ?>
+ <?= Icon::create('arr_2up', Icon::ROLE_INACTIVE) ?>
<? endif ?>
- </td>
- </tr>
- </tbody>
- </table>
- <div style="text-align: center;">
- <? if ($GLOBALS['perm']->have_studip_perm("tutor", Context::getId())) : ?>
- <?= 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' => _('Das Thema 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 ($topic_links[$topic->id]['previous']) : ?>
- <form action="<?= $controller->swap($topic, $topic_links[$topic->id]['previous']) ?>" method="post" style="display: inline;">
- <?= Studip\Button::createMoveUp(_('Nach oben verschieben')) ?>
- </form>
- <? endif ?>
- <? if ($topic_links[$topic->id]['next']) : ?>
- <form action="<?= $controller->swap($topic, $topic_links[$topic->id]['next']) ?>" method="post" style="display: inline;">
- <?= Studip\Button::createMoveDown(_('Nach unten verschieben')) ?>
- </form>
- <? endif ?>
- </span>
+ <? $move_down_label = sprintf(_('%s nach unten verschieben'), htmlReady($topic->title));
+ if ($topic_links[$topic->id]['next']) : ?>
+ <button
+ class="as-link"
+ formaction="<?= $controller->swap($topic, $topic_links[$topic->id]['next']) ?>"
+ aria-label="<?= $move_down_label ?>"
+ title="<?= $move_down_label ?>"
+ >
+ <?= Icon::create('arr_2down') ?>
+ </button>
+ <? else : ?>
+ <?= Icon::create('arr_2down', Icon::ROLE_INACTIVE) ?>
+ <? endif ?>
+ <?= $controller->getActionMenu($topic) ?>
+ </div>
+ </td>
+ <? endif ?>
+ </tr>
+ <? endforeach ?>
+ </tbody>
+ <? if ($is_tutor) : ?>
+ <tfoot>
+ <tr>
+ <td colspan="6">
+ <? if ($documents_activated) : ?>
+ <?= Button::create(_('Dateiordner anlegen'), 'bulk_folder', [
+ 'formaction' => $controller->bulkURL('folder'),
+ 'data-confirm' => _('Sind Sie sicher, dass Sie für Ihre Auswahl je einen Dateiordner anlegen wollen?'),
+ ]) ?>
+ <? endif ?>
+ <? if ($forum_activated) : ?>
+ <?= Button::create(_('Forumsthema anlegen'), 'bulk_ftopic', [
+ 'formaction' => $controller->bulkURL('ftopic'),
+ 'data-confirm' => _('Sind Sie sicher, dass Sie für Ihre Auswahl je ein Forumsthema anlegen wollen?'),
+ ]) ?>
<? endif ?>
- </div>
- </div>
- </td>
- </tr>
- <? endforeach ?>
- </tbody>
-</table>
+ <?= Button::create(_('Löschen'), 'bulk_delete', [
+ 'formaction' => $controller->bulkURL('delete'),
+ 'data-confirm' => _('Sind Sie sicher, dass Sie Ihre Auswahl löschen wollen?'),
+ ]) ?>
+ </td>
+ </tr>
+ </tfoot>
+ <? endif ?>
+ </table>
+ </form>
<? else : ?>
<?= MessageBox::info(_('Keine Themen vorhanden.')) ?>
<? endif ?>