From 24bbb999223d2c96e79e864dc8004c7153d60f2b Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Wed, 4 Dec 2024 08:26:22 +0000
Subject: [PATCH] restrict deletion of wiki pages, re #4490

Merge request studip/studip!3285
---
 app/controllers/course/wiki.php              | 30 +++++-----
 app/views/course/wiki/ask_deleting.php       | 30 ++++++----
 lib/models/WikiPage.php                      | 61 ++++++++++++++++----
 lib/models/WikiVersion.php                   | 43 +++++++++++++-
 resources/assets/stylesheets/scss/files.scss |  6 ++
 5 files changed, 129 insertions(+), 41 deletions(-)

diff --git a/app/controllers/course/wiki.php b/app/controllers/course/wiki.php
index 926f641fe4f..ddda49af86f 100644
--- a/app/controllers/course/wiki.php
+++ b/app/controllers/course/wiki.php
@@ -164,20 +164,22 @@ class Course_WikiController extends AuthenticatedController
                     Icon::create('settings'),
                     ['data-dialog' => 'width=700']
                 );
-                if (count($this->page->versions) > 0) {
-                    $action_menu->addLink(
-                        $this->ask_deletingURL($this->page),
-                        _('Seite / Version löschen'),
-                        Icon::create('trash'),
-                        ['data-dialog' => 'size=auto']
-                    );
-                } else {
-                    $action_menu->addButton(
-                        'delete',
-                        _('Seite löschen'),
-                        Icon::create('trash'),
-                        ['data-confirm' => _('Wollen Sie wirklich die komplette Seite löschen?'), 'form' => 'delete_page']
-                    );
+                if ($this->page->isDeletable()) {
+                    if (count($this->page->versions) > 0) {
+                        $action_menu->addLink(
+                            $this->ask_deletingURL($this->page),
+                            _('Seite / Version löschen'),
+                            Icon::create('trash'),
+                            ['data-dialog' => 'size=auto']
+                        );
+                    } else {
+                        $action_menu->addButton(
+                            'delete',
+                            _('Seite löschen'),
+                            Icon::create('trash'),
+                            ['data-confirm' => _('Wollen Sie wirklich die komplette Seite löschen?'), 'form' => 'delete_page']
+                        );
+                    }
                 }
             }
             $this->contentBarVueApp = $this->contentBarVueApp->withSlot('menu', $action_menu->render());
diff --git a/app/views/course/wiki/ask_deleting.php b/app/views/course/wiki/ask_deleting.php
index 5afd19fe1f4..915e689199f 100644
--- a/app/views/course/wiki/ask_deleting.php
+++ b/app/views/course/wiki/ask_deleting.php
@@ -1,31 +1,37 @@
+<?php
+/**
+ * @var Course_WikiController $controller
+ * @var WikiPage $page
+ */
+?>
 <form action="" method="post">
     <?= CSRFProtection::tokenTag() ?>
     <div class="file_select_possibilities">
         <div>
+        <? if (count($page->versions) > 0 && $page->versions->first()->isDeletable()): ?>
             <div class="clickable">
-                <?= Icon::create('archive2')->asInput(50, [
-                    'formaction' => $controller->deleteversionURL($page, ['redirect_to' => 'page']),
-                    'data-confirm' => _('Wirklich die letzte Änderung löschen?')
-                ]) ?>
                 <button
-                    class="undecorated"
+                    class="as-link"
                     data-confirm="<?= _('Wirklich die letzte Änderung löschen?') ?>"
-                    formaction="<?= $controller->deleteversionURL($page, ['redirect_to' => 'page']) ?>">
+                    formaction="<?= $controller->deleteversionURL($page, ['redirect_to' => 'page']) ?>"
+                >
+                    <?= Icon::create('archive2')->asImg(50) ?>
                     <?= _('Nur die letzte Änderung löschen') ?>
                 </button>
             </div>
+        <? endif; ?>
+        <? if ($page->isDeletable()): ?>
             <div class="clickable">
-                <?= Icon::create('wiki')->asInput(50, [
-                    'formaction' => $controller->deleteURL($page),
-                    'data-confirm' => _('Wollen Sie wirklich die komplette Seite löschen?')
-                ]) ?>
                 <button
-                    class="undecorated"
+                    class="as-link"
                     data-confirm="<?= _('Wollen Sie wirklich die komplette Seite löschen?') ?>"
-                    formaction="<?= $controller->deleteURL($page) ?>">
+                    formaction="<?= $controller->deleteURL($page) ?>"
+                >
+                    <?= Icon::create('wiki')->asImg(50) ?>
                     <?= _('Ganze Wikiseite löschen') ?>
                 </button>
             </div>
+        <? endif; ?>
         </div>
     </div>
 </form>
diff --git a/lib/models/WikiPage.php b/lib/models/WikiPage.php
index bb41d1b19e7..6cb4c8a2cf9 100644
--- a/lib/models/WikiPage.php
+++ b/lib/models/WikiPage.php
@@ -13,19 +13,23 @@
  *
  * @property int $id alias for pk
  * @property int $page_id database column
- * @property string $course_id database column
- * @property string|null $user_id database column
+ * @property string $range_id database column
  * @property string $name database column
  * @property string $content database column
- * @property string|null $ancestor database column
- * @property int|null $chdate database column
- * @property int $version database column
+ * @property string|null $parent_id database column
+ * @property string $read_permission database column
+ * @property string $write_permission database column
+ * @property string $user_id database column
+ * @property int|null $locked_since database column
+ * @property string|null $locked_by_user_id database column
  * @property int|null $mkdate database column
+ * @property int|null $chdate database column
+ *
  * @property User|null $user belongs_to User
  * @property Course $course belongs_to Course
  * @property WikiVersion[]|SimpleORMapCollection $versions
  * @property WikiOnlineEditingUser[]|SimpleORMapCollection $onlineeditingusers
- * @property-read WikiPage $parent additional field
+ * @property-read WikiPage|null $parent additional field
  * @property-read WikiPage[] $children additional field
  * @property-read WikiVersion|null $predecessor additional field
  * @property-read int $versionnumber additional field
@@ -179,16 +183,18 @@ class WikiPage extends SimpleORMap implements PrivacyObject
         if ($user_id === null && User::findCurrent()) {
             $user_id = User::findCurrent()->id;
         }
-        if ($GLOBALS['perm']->have_studip_perm(
-            'dozent',
-            $this->range_id,
-            $user_id
-        )) {
-            return true;
+
+        if (
+            !$user_id
+            || !$GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user_id)
+        ) {
+            return false;
         }
+
         if ($this->write_permission === 'all') {
             return true;
         }
+
         if (in_array($this->write_permission, ['tutor', 'dozent'])) {
             return $GLOBALS['perm']->have_studip_perm(
                 $this->write_permission,
@@ -200,6 +206,37 @@ class WikiPage extends SimpleORMap implements PrivacyObject
         }
     }
 
+    public function isDeletable(?string $user_id = null): bool
+    {
+        if ($user_id === null && User::findCurrent()) {
+            $user_id = User::findCurrent()->id;
+        }
+
+        if (!$user_id) {
+            return false;
+        }
+
+        $permission  = $this->write_permission;
+        if ($permission === 'all') {
+            $permission = 'tutor';
+        }
+
+        if (
+            in_array($permission, ['tutor', 'dozent'])
+            && $GLOBALS['perm']->have_studip_perm(
+                $this->write_permission,
+                $this->range_id,
+                $user_id
+            )
+        ) {
+            return true;
+        }
+
+        return $this->user_id === $user_id
+            && $this->versions->every(function (WikiVersion $version) use ($user_id): bool {
+                return $version->user_id === $user_id;
+            });
+    }
 
     /**
      * Export available data of a given user into a storage object
diff --git a/lib/models/WikiVersion.php b/lib/models/WikiVersion.php
index 289c57d8aba..2adb9580950 100644
--- a/lib/models/WikiVersion.php
+++ b/lib/models/WikiVersion.php
@@ -13,11 +13,19 @@
  * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
  * @category    Stud.IP
  *
- * @property string page_id       database column
- * @property string id            alias column for user_id
- * @property string last_lifesign computed column read/write
+ * @property int $id alias column for user_id
+ * @property int $version_id database column
+ * @property int $page_id database column
+ * @property string $name database column
+ * @property string $content database column
+ * @property string $user_id database column
+ * @property int $mkdate database column
  *
  * @property WikiPage $page
+ * @property User $user
+ * @property WikiPage|null $predecessor
+ * @property WikiPage|null $successor
+ * @property int|null $versionnumber
  */
 class WikiVersion extends SimpleORMap
 {
@@ -63,4 +71,33 @@ class WikiVersion extends SimpleORMap
         ];
         parent::configure($config);
     }
+
+    public function isDeletable(?string $user_id = null): bool
+    {
+        if ($user_id === null && User::findCurrent()) {
+            $user_id = User::findCurrent()->id;
+        }
+
+        if (!$user_id) {
+            return false;
+        }
+
+        if ($this->user_id === $user_id) {
+            return true;
+        }
+
+        $permission = $this->page->write_permission;
+        if ($permission === 'all') {
+            $permission = 'tutor';
+        }
+
+        return in_array($permission, ['tutor', 'dozent'])
+            && $GLOBALS['perm']->have_studip_perm(
+                $this->page->write_permission,
+                $this->page->range_id,
+                $user_id
+            );
+
+
+    }
 }
diff --git a/resources/assets/stylesheets/scss/files.scss b/resources/assets/stylesheets/scss/files.scss
index 03c062116c3..31dadfb88e5 100644
--- a/resources/assets/stylesheets/scss/files.scss
+++ b/resources/assets/stylesheets/scss/files.scss
@@ -126,6 +126,12 @@ div.file_select_possibilities,
                 margin-left: auto;
                 margin-right: auto;
             }
+
+            button img {
+                display: block;
+                margin-left: auto;
+                margin-right: auto;
+            }
         }
 
         > .important-item {
-- 
GitLab