diff --git a/app/controllers/jsupdater.php b/app/controllers/jsupdater.php
index a706337d65ee864b50bc859a70ea8695cf8b19cd..b098b71a90d8d6c5d45abfd7ddacb0855592bc59 100644
--- a/app/controllers/jsupdater.php
+++ b/app/controllers/jsupdater.php
@@ -245,6 +245,7 @@ class JsupdaterController extends AuthenticatedController
             function (Questionnaire $questionnaire) use ($pageInfo, &$data) {
                 if ($questionnaire->latestAnswerTimestamp() > $pageInfo['questionnaire']['last_update']) {
                     $template = $this->get_template_factory()->open("questionnaire/evaluate");
+                    $template->filtered($pageInfo['questionnaire']['filtered']);
                     $template->set_layout(null);
                     $template->set_attribute("questionnaire", $questionnaire);
                     $data[$questionnaire->id] = [
diff --git a/app/controllers/questionnaire.php b/app/controllers/questionnaire.php
index 3daf79f63831918df493ffa3bc67180315da97e9..04230cfc245dd18c4f656bef7b7d15c0b46e1427 100644
--- a/app/controllers/questionnaire.php
+++ b/app/controllers/questionnaire.php
@@ -16,8 +16,10 @@ class QuestionnaireController extends AuthenticatedController
 
         //trigger autoloading:
         class_exists('Vote');
-        class_exists('Test');
         class_exists('Freetext');
+        class_exists('LikertScale');
+        class_exists('RangeScale');
+        class_exists('QuestionnaireInfo');
         PageLayout::setHelpKeyword('Basis/Votings');
     }
 
@@ -98,103 +100,6 @@ class QuestionnaireController extends AuthenticatedController
             );
             return;
         }
-        if (Request::isPost()) {
-            $neworder = array_flip(Request::getArray("neworder"));
-            $order = array_flip(json_decode(Request::get("order"), true));
-            $questionnaire_data = Request::getArray("questionnaire");
-            $questionnaire_data['startdate'] = $questionnaire_data['startdate']
-                ? (strtotime($questionnaire_data['startdate']) ?: time())
-                : null;
-            $questionnaire_data['stopdate'] = strtotime($questionnaire_data['stopdate']) ?: null;
-            $questionnaire_data['copyable'] = (int)($questionnaire_data['copyable'] ?? 0);
-            $questionnaire_data['anonymous'] = (int)($questionnaire_data['anonymous'] ?? 0);
-            $questionnaire_data['editanswers'] = $questionnaire_data['anonymous'] ? 0 : (int) $questionnaire_data['editanswers'];
-            if ($this->questionnaire->isNew()) {
-                $questionnaire_data['visible'] = ($questionnaire_data['startdate'] <= time() && (!$questionnaire_data['stopdate'] || $questionnaire_data['stopdate'] >= time())) ? 1 : 0;
-            }
-            $this->questionnaire->setData($questionnaire_data);
-            $question_types_data = Request::getArray("question_types");
-            foreach ($question_types_data as $question_id => $question_type) {
-                $question = null;
-                foreach ($this->questionnaire->questions as $index => $q) {
-                    if ($q->getId() === $question_id) {
-                        $question = $q;
-                        break;
-                    }
-                }
-                if (!$question) {
-                    $question = new $question_type($question_id);
-                    $this->questionnaire->questions[] = $question;
-                }
-                $question['position'] = $neworder[$question_id] + 1;
-                $question->createDataFromRequest();
-            }
-            foreach ($this->questionnaire->questions as $q) {
-                if (!in_array($q->getId(), array_keys($question_types_data))) {
-                    $q->delete();
-                }
-            }
-            if (Request::submitted("questionnaire_store")) {
-                //save everything
-                $is_new = $this->questionnaire->isNew();
-                if ($is_new) {
-                    $this->questionnaire['user_id'] = $GLOBALS['user']->id;
-                }
-                $this->questionnaire->store();
-
-                if ($is_new && Request::get("range_id") && Request::get("range_type")) {
-                    if (Request::get("range_id") === "start" && !$GLOBALS['perm']->have_perm("root")) {
-                        throw new Exception(_("Der Fragebogen darf nicht von Ihnen auf die Startseite eingehängt werden, sondern nur von einem Admin."));
-                    }
-                    if (Request::get("range_type") === "course" && !$GLOBALS['perm']->have_studip_perm("tutor", Request::get("range_id"))) {
-                        throw new Exception(_("Der Fragebogen darf nicht in die ausgewählte Veranstaltung eingebunden werden."));
-                    }
-                    if (Request::get("range_type") === "user" && Request::get("range_id") !== $GLOBALS['user']->id) {
-                        throw new Exception(_("Der Fragebogen darf nicht in diesen Bereich eingebunden werden."));
-                    }
-                    $assignment = new QuestionnaireAssignment();
-                    $assignment['questionnaire_id'] = $this->questionnaire->getId();
-                    $assignment['range_id'] = Request::option("range_id");
-                    $assignment['range_type'] = Request::get("range_type");
-                    $assignment['user_id'] = $GLOBALS['user']->id;
-                    $assignment->store();
-                }
-                if ($is_new) {
-                    $message = MessageBox::success(_("Der Fragebogen wurde erfolgreich erstellt."));
-                } else {
-                    $message = MessageBox::success(_("Der Fragebogen wurde gespeichert."));
-                }
-                if (Request::isAjax()) {
-                    $this->questionnaire->restore();
-                    $this->questionnaire->resetRelation("assignments");
-                    $this->range_type = Request::get('range_type');
-                    $this->range_id = Request::get('range_id');
-                    $output = [
-                            'questionnaire_id' => $this->questionnaire->getId(),
-                            'overview_html' => $this->render_template_as_string("questionnaire/_overview_questionnaire.php"),
-                            'widget_html' => $this->questionnaire->isStarted()
-                                ? $this->render_template_as_string("questionnaire/_widget_questionnaire.php")
-                                : "",
-                            'message' => $message->__toString()
-                    ];
-                    $this->response->add_header("X-Dialog-Close", 1);
-                    $this->response->add_header("X-Dialog-Execute", "STUDIP.Questionnaire.updateOverviewQuestionnaire");
-                    $this->render_json($output);
-                } else {
-                    PageLayout::postMessage($message);
-                    if (Request::get("range_type") === "user") {
-                        $this->redirect("profile");
-                    } elseif (Request::get("range_type") === "course") {
-                        $this->redirect("course/overview");
-                    } elseif (Request::get("range_id") === "start") {
-                        $this->redirect("start");
-                    } else {
-                        $this->redirect("questionnaire/overview");
-                    }
-                }
-            }
-            return;
-        }
 
         $statement = DBManager::get()->prepare("
             SELECT question_id
@@ -206,6 +111,90 @@ class QuestionnaireController extends AuthenticatedController
         $this->order = $statement->fetchAll(PDO::FETCH_COLUMN, 0);
     }
 
+    public function store_action($questionnaire_id = null)
+    {
+        if ($questionnaire_id) {
+            $this->questionnaire = Questionnaire::find($questionnaire_id);
+        } else {
+            $this->questionnaire = new Questionnaire();
+        }
+        if (!$this->questionnaire || !$this->questionnaire->isEditable()) {
+            throw new AccessDeniedException(_('Der Fragebogen ist nicht bearbeitbar.'));
+        }
+        if ($this->questionnaire->isRunning() && $this->questionnaire->countAnswers() > 0) {
+            $this->response->set_status('409', 'Conflict');
+            $this->render_json([
+                'error' => 'alreadystarted',
+                'message' => _("Der Fragebogen ist gestartet worden und kann jetzt nicht mehr bearbeitet werden. Stoppen oder löschen Sie den Fragebogen stattdessen.")
+            ]);
+            return;
+        }
+        if (!Request::isPost()) {
+            throw new MethodNotAllowedException();
+        }
+        $questionnaire_data = Request::getArray("questionnaire");
+        $this->questionnaire['title'] = $questionnaire_data['title'] ?? '';
+        $this->questionnaire['visible'] = $questionnaire_data['visible'] ?? 1;
+        $this->questionnaire['anonymous'] = $questionnaire_data['anonymous'] ?? 0;
+        $this->questionnaire['resultvisibility'] = $questionnaire_data['resultvisibility'] ?? 'always';
+        $this->questionnaire['editanswers'] = $questionnaire_data['editanswers'] ?? 1;
+        $this->questionnaire['copyable'] = $questionnaire_data['copyable'] ?? 1;
+        $this->questionnaire['startdate'] = is_numeric($questionnaire_data['startdate'])
+            ? $questionnaire_data['startdate']
+            : ($questionnaire_data['startdate'] ? time() : null);
+        $this->questionnaire['stopdate'] = is_numeric($questionnaire_data['stopdate'])
+            ? $questionnaire_data['stopdate']
+            : null;
+
+        $this->questionnaire['user_id'] = User::findCurrent()->id;
+        $questions_data = Request::getArray('questions_data');
+        $questions = [];
+        foreach ($questions_data as $index => $question_data) {
+            $class = $question_data['questiontype'];
+            if (!class_exists($class) || !is_subclass_of($class, 'QuestionType')) {
+                continue;
+            }
+            $question = $class::find($question_data['id']);
+            if (!$question) {
+                $question = new $class();
+                $question->setId($question_data['id']);
+            } elseif ($question['questionnaire_id'] !== $this->questionnaire->getId()) {
+                $question = new $class();
+                $question->setId($question->getNewId());
+            }
+            $question_data['questiondata'] = $question->beforeStoringQuestiondata($question_data['questiondata']);
+            unset($question_data['id']);
+            $question->setData($question_data);
+            $question['position'] = $index;
+            $questions[] = $question;
+        }
+        $this->questionnaire->questions = $questions;
+        $this->questionnaire->store();
+
+        //assignments:
+        if (Request::get("range_id") && Request::get("range_type")) {
+            if (Request::get("range_id") === "start" && !$GLOBALS['perm']->have_perm("root")) {
+                throw new AccessDeniedException();
+            }
+            if (Request::get("range_type") === "course" && !$GLOBALS['perm']->have_studip_perm("tutor", Request::get("range_id"))) {
+                throw new AccessDeniedException();
+            }
+            if (Request::get("range_type") === "user" && Request::get("range_id") !== $GLOBALS['user']->id) {
+                throw new AccessDeniedException();
+            }
+            $assignment = new QuestionnaireAssignment();
+            $assignment['questionnaire_id'] = $this->questionnaire->getId();
+            $assignment['range_id'] = Request::option("range_id");
+            $assignment['range_type'] = Request::get("range_type");
+            $assignment['user_id'] = $GLOBALS['user']->id;
+            $assignment->store();
+        }
+
+        PageLayout::postSuccess(_('Die Daten wurden erfolgreich gespeichert.'));
+        $this->render_nothing();
+    }
+
+
     public function copy_action($from)
     {
         $this->old_questionnaire = Questionnaire::find($from);
@@ -225,14 +214,8 @@ class QuestionnaireController extends AuthenticatedController
             $new_question = QuestionnaireQuestion::build($question->toArray());
             $new_question->setId($new_question->getNewId());
             $new_question['questionnaire_id'] = $this->questionnaire->getid();
+            $new_question['questiondata'] = $question['questiondata'];
             $new_question['mkdate'] = time();
-
-            $etask = new \eTask\Task();
-            $etask->setData($question->etask->toRawArray());
-            $etask->setId(null); //to get a new integer id
-            $etask->store();
-            $new_question['etask_task_id'] = $etask->getId();
-
             $new_question->store();
         }
         PageLayout::postSuccess(_('Der Fragebogen wurde kopiert. Wo soll er angezeigt werden?'));
@@ -277,25 +260,6 @@ class QuestionnaireController extends AuthenticatedController
         }
     }
 
-    public function add_question_action()
-    {
-        if (!$GLOBALS['perm']->have_perm("autor")) {
-            throw new AccessDeniedException(_('Der Fragebogen ist nicht einsehbar.'));
-        }
-        $class = Request::get("questiontype");
-        $this->question = new $class();
-        $this->question->setId($this->question->getNewId());
-
-        $template = $this->get_template_factory()->open("questionnaire/_question.php");
-        $template->set_attribute("question", $this->question);
-
-        $output = [
-            'html' => $template->render(),
-            'question_id' => $this->question->getId()
-        ];
-        $this->render_json($output);
-    }
-
     public function answer_action($questionnaire_id)
     {
         $this->questionnaire = new Questionnaire($questionnaire_id);
@@ -317,6 +281,12 @@ class QuestionnaireController extends AuthenticatedController
         object_set_visit($questionnaire_id, 'vote');
         PageLayout::setTitle(sprintf(_("Fragebogen: %s"), $this->questionnaire->title));
 
+        if (Request::submitted('filtered')) {
+            $this->filtered = [
+                $questionnaire_id => Request::getArray('filtered')
+            ];
+        }
+
         if (Request::isAjax() && !$_SERVER['HTTP_X_DIALOG']) {
             PageLayout::clearMessages();
         }
@@ -395,6 +365,31 @@ class QuestionnaireController extends AuthenticatedController
         $this->render_text(array_to_csv($csv));
     }
 
+    public function reset_action(Questionnaire $questionnaire)
+    {
+        if (!Request::isPost() || !$questionnaire->isEditable() || !CSRFProtection::verifyRequest()) {
+            throw new AccessDeniedException();
+        }
+        foreach ($questionnaire->anonymousanswers as $anonymous) {
+            $anonymous->delete();
+        }
+        foreach ($questionnaire->questions as $question) {
+            foreach ($question->answers as $answer) {
+                $answer->delete();
+            }
+        }
+        PageLayout::postSuccess(_('Antworten wurden zurückgesetzt.'));
+        if (Request::get("range_type") === "user") {
+            $this->redirect("profile");
+        } elseif (Request::get("range_type") === "course") {
+            $this->redirect("course/overview");
+        } elseif (Request::get("range_id") === "start") {
+            $this->redirect("start");
+        } else {
+            $this->redirect("questionnaire/overview");
+        }
+    }
+
     public function context_action($questionnaire_id)
     {
         $this->questionnaire = new Questionnaire($questionnaire_id);
@@ -518,16 +513,29 @@ class QuestionnaireController extends AuthenticatedController
             PageLayout::postSuccess(_('Die Bereichszuweisungen wurden gespeichert.'));
             $this->questionnaire->restore();
             $this->questionnaire->resetRelation("assignments");
-            $output = [
-                'func' => "STUDIP.Questionnaire.updateOverviewQuestionnaire",
-                'payload' => [
-                    'questionnaire_id' => $this->questionnaire->getId(),
-                    'overview_html' => $this->render_template_as_string("questionnaire/_overview_questionnaire.php")
-                ]
-            ];
-            $this->response->add_header("X-Dialog-Execute", json_encode($output));
+            $this->response->add_header("X-Dialog-Close", 1);
         }
         PageLayout::setTitle(sprintf(_("Bereiche für Fragebogen: %s"), $this->questionnaire->title));
+        // Prepare context for MyCoursesSearch...
+        if ($GLOBALS['perm']->have_perm('root')) {
+            $parameters = [
+                'exclude'   => ['']
+            ];
+        } elseif ($GLOBALS['perm']->have_perm('admin')) {
+            $parameters = [
+                'institutes' => array_map(function ($i) {
+                    return $i['Institut_id'];
+                }, Institute::getMyInstitutes()),
+                'exclude'    => ['']
+            ];
+        } else {
+            $parameters = [
+                'userid'    => $GLOBALS['user']->id,
+                'exclude'   => ['']
+            ];
+        }
+        $this->seminarsearch = MyCoursesSearch::get('Seminar_id', $GLOBALS['perm']->get_perm(), $parameters);
+
         if ($GLOBALS['perm']->have_perm("root")) {
             $this->statusgruppesearch = new SQLSearch(
                 "SELECT statusgruppen.statusgruppe_id, CONCAT(seminare.name, ': ', statusgruppen.name) AS search_name
@@ -893,21 +901,21 @@ class QuestionnaireController extends AuthenticatedController
         }
         $answered_before = $this->questionnaire->isAnswered();
         if ($this->questionnaire->isAnswerable()) {
+            $pseudonomous_id = 'q'.substr(md5(uniqid()), 1);
             foreach ($this->questionnaire->questions as $question) {
                 $answer = $question->createAnswer();
                 if (!$answer['question_id']) {
                     $answer['question_id'] = $question->getId();
                 }
-                $answer['user_id'] = $GLOBALS['user']->id;
+                $answer['user_id'] = $GLOBALS['user']->id !== "nobody" ? $GLOBALS['user']->id : $pseudonomous_id;
                 if (!$answer['answerdata']) {
                     $answer['answerdata'] = [];
                 }
                 if ($this->questionnaire['anonymous']) {
-                    $answer['user_id'] = 'anonymous';
+                    $answer['user_id'] = $pseudonomous_id;
                     $answer['chdate'] = 1;
                     $answer['mkdate'] = 1;
                     $this->anonAnswers[] = $answer->toArray();
-                    $answer['user_id'] = null;
                 }
                 $answer->store();
             }
@@ -952,4 +960,17 @@ class QuestionnaireController extends AuthenticatedController
             }
         }
     }
+
+    public function export_file_action(Questionnaire $questionnaire)
+    {
+        if (!$questionnaire->isCopyable()) {
+            throw new AccessDeniedException(_('Der Fragebogen ist nicht kopierbar.'));
+        }
+        $this->response->add_header('Content-Disposition', 'attachment; ' . encode_header_parameter('filename', $questionnaire['title'].".json"));
+
+        $rawdata = $questionnaire->exportAsFile();
+        $file_data = json_encode($rawdata);
+        $this->response->add_header('Content-Length', strlen($file_data));
+        $this->render_json($rawdata);
+    }
 }
diff --git a/app/views/questionnaire/_answer_description_container.php b/app/views/questionnaire/_answer_description_container.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e066c82cd673b3e9a55df705eb4f10142f85548
--- /dev/null
+++ b/app/views/questionnaire/_answer_description_container.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ * @var string $iconshape
+ */
+?>
+<div class="description_container">
+    <div class="icon_container">
+        <?= Icon::create($iconshape, Icon::ROLE_INFO)->asImg(20) ?>
+    </div>
+    <article class="description">
+        <? if (isset($vote->questiondata['mandatory']) && $vote->questiondata['mandatory']) : ?>
+            <?= Icon::create('star', Icon::ROLE_ATTENTION)->asImg(20, ['class' => 'text-bottom', 'alt' => '']) ?>
+            <?= _('Pflichtantwort') ?>
+        <? endif ?>
+        <?= formatReady($vote->questiondata['description']) ?>
+    </article>
+</div>
diff --git a/app/views/questionnaire/_overview_questionnaire.php b/app/views/questionnaire/_overview_questionnaire.php
index 4bf2897ca768e6b680543ae2fca83a1317851ae5..f028785683a6c0301da20ddb352219660ae77c6e 100644
--- a/app/views/questionnaire/_overview_questionnaire.php
+++ b/app/views/questionnaire/_overview_questionnaire.php
@@ -1,23 +1,12 @@
 <tr id="questionnaire_<?= $questionnaire->id ?>">
+    <? $countedAnswers = $questionnaire->countAnswers() ?>
     <td>
         <input type="checkbox" name="q[]" value="<?= htmlReady($questionnaire->id) ?>">
     </td>
     <td>
-        <a href="<?= $controller->link_for('questionnaire/answer/' . $questionnaire->id) ?>" data-dialog>
+        <a href="<?= $controller->link_for(($questionnaire->isRunning() && $countedAnswers ? 'questionnaire/evaluate/' : 'questionnaire/edit/') . $questionnaire->id) ?>" data-dialog="size=big">
             <?= htmlReady($questionnaire['title']) ?>
         </a>
-        <span>
-        <?
-            $icons = [];
-            foreach ($questionnaire->questions as $question) {
-                $class = get_class($question);
-                $icons[$class] = $class::getIcon();
-            }
-            foreach ($icons as $class => $icon) {
-                echo $icon->asImg(16, ['class' => 'text-bottom', 'title' => $class::getName()])." ";
-            }
-        ?>
-        </span>
     </td>
     <td>
     <? if ($questionnaire['startdate']): ?>
@@ -47,7 +36,8 @@
             <? if ($assignment['range_type'] === 'user') : ?>
                 <?= _('Profilseite')?>
             <? elseif ($assignment['range_type'] === 'course') : ?>
-                <?= htmlReady(Course::find($assignment['range_id'])->name) ?>
+                <? $course = Course::find($assignment['range_id']) ?>
+                <?= htmlReady((Config::get()->IMPORTANT_SEMNUMBER ? $course->veranstaltungsnummer." " : "") . $course['name'] . ' ('.$course->semester_text.')') ?>
             <? elseif ($assignment['range_type'] === 'statusgruppe') : ?>
                 <? $statusgruppe = Statusgruppen::find($assignment['range_id']) ?>
                 <? if ($statusgruppe) : ?>
@@ -77,24 +67,33 @@
     <? endif ?>
     </td>
     <td>
-        <? $countedAnswers = $questionnaire->countAnswers() ?>
         <?= htmlReady($countedAnswers) ?>
     </td>
     <td class="actions">
     <? if ($questionnaire->isRunning() && $countedAnswers) : ?>
         <?= Icon::create('edit', 'inactive')->asImg(20, ['title' => _('Der Fragebogen wurde gestartet und kann nicht mehr bearbeitet werden.')]) ?>
     <? else : ?>
-        <a href="<?= $controller->link_for('questionnaire/edit/' . $questionnaire->id) ?>" data-dialog title="<?= _('Fragebogen bearbeiten') ?>">
+        <a href="<?= $controller->link_for('questionnaire/edit/' . $questionnaire->id) ?>"
+           data-dialog="size=big"
+           title="<?= _('Fragebogen bearbeiten') ?>">
             <?= Icon::create('edit', 'clickable')->asImg(20) ?>
         </a>
     <? endif ?>
-        <a href="<?= $controller->link_for('questionnaire/context/' . $questionnaire->id) ?>" data-dialog title="<?= _('Zuweisungen bearbeiten') ?>">
+        <a href="<?= $controller->link_for('questionnaire/context/' . $questionnaire->id) ?>"
+           data-dialog="reload-on-close"
+           title="<?= _('Zuweisungen bearbeiten') ?>">
             <?= Icon::create('group2', 'clickable')->asImg(20) ?>
         </a>
 
         <?
         $menu = ActionMenu::get()->setContext($questionnaire['title']);
         if ($questionnaire->isRunning()) {
+            $menu->addLink(
+                $controller->url_for('questionnaire/answer/' . $questionnaire->id),
+                _('Ausfüllen'),
+                Icon::create('evaluation', 'clickable'),
+                ['data-dialog' => 1]
+            );
             $menu->addLink(
                 $controller->url_for('questionnaire/stop/' . $questionnaire->id, in_array($range_type, ['course', 'institute']) ? ['redirect' => 'questionnaire/courseoverview'] : []),
                 _('Fragebogen beenden'),
@@ -113,6 +112,28 @@
             Icon::create('stat', 'clickable'),
             ['data-dialog' => '']
         );
+        $menu->addLink(
+            $controller->url_for('questionnaire/copy/'  .$questionnaire->id),
+            _('Kopieren'),
+            Icon::create('clipboard', 'clickable'),
+            ['data-dialog' => '']
+        );
+        $menu->addLink(
+            $controller->url_for('questionnaire/export_file/'  .$questionnaire->id),
+            _('Vorlage herunterladen'),
+            Icon::create('export', 'clickable')
+        );
+        if ($questionnaire->countAnswers() > 0) {
+            $menu->addButton(
+                'reset_answers',
+                _('Antworten löschen'),
+                Icon::create('refresh', 'clickable'),
+                [
+                    'data-confirm' => _('Sollen die Antworten wirklich gelöscht werden?'),
+                    'formaction' => $controller->url_for('questionnaire/reset/' . $questionnaire->id)
+                ]
+            );
+        }
         $menu->addLink(
             $controller->url_for('questionnaire/export/'  .$questionnaire->id),
             _('Export als CSV'),
diff --git a/app/views/questionnaire/add_to_context.php b/app/views/questionnaire/add_to_context.php
index 5fdf836f2e30125b61ab1e5094cfe0102f794f11..885aad58f78c9262c62d06ed16780ab948b6bcd0 100644
--- a/app/views/questionnaire/add_to_context.php
+++ b/app/views/questionnaire/add_to_context.php
@@ -8,13 +8,13 @@ $icons = [
 <div class="file_select_possibilities">
     <div>
         <a href="<?= $controller->link_for("questionnaire/edit", ['range_type' => Context::getType(), 'range_id' => Context::get()->id]) ?>"
-           data-dialog="size=default">
+           data-dialog="size=big">
             <?= Icon::create($icons[Context::getType()], Icon::ROLE_CLICKABLE)->asImg(50) ?>
             <?= htmlReady(Context::get()->name) ?>
         </a>
         <? foreach ($statusgruppen as $statusgruppe) : ?>
             <a href="<?= $controller->link_for("questionnaire/edit", ['range_type' => "statusgruppe", 'range_id' => $statusgruppe->getId()]) ?>"
-               data-dialog="size=default">
+               data-dialog="size=big">
                 <?= Icon::create('group2', Icon::ROLE_CLICKABLE)->asImg(50) ?>
                 <?= htmlReady($statusgruppe->name) ?>
             </a>
diff --git a/app/views/questionnaire/context.php b/app/views/questionnaire/context.php
index 7d7294b84c422f1032c19f322ace3946edd90744..3b601c11a615d6ec85487e8f25d91204965344e8 100644
--- a/app/views/questionnaire/context.php
+++ b/app/views/questionnaire/context.php
@@ -28,22 +28,24 @@
         <ul class="clean courseselector">
             <? foreach ($this->questionnaire->assignments as $assignment) : ?>
             <? if ($assignment['range_type'] === "course") : ?>
+                <? $course = Course::find($assignment['range_id']) ?>
+                <? if ($course) : ?>
                 <li>
                     <label>
                         <input type="checkbox" name="remove_sem[]" value="<?= htmlReady($assignment['range_id']) ?>" style="display: none;">
-                        <? $course = Course::find($assignment['range_id']) ?>
                         <span>
                             <a href="<?= URLHelper::getLink("seminar_main.php", ['auswahl' => $course->getId()]) ?>">
-                                <?= htmlReady((Config::get()->IMPORTANT_SEMNUMBER ? $course->veranstaltungsnummer." " : "").$course->name) ?>
+                                <?= htmlReady((Config::get()->IMPORTANT_SEMNUMBER ? $course->veranstaltungsnummer." " : "").$course->name. ' ('.$course->semester_text.')') ?>
                             </a>
                             <?= Icon::create("trash", "clickable")->asimg("20px", ['class' => "text-bottom", 'title' => _("Zuweisung zur Veranstaltung aufheben.")]) ?>
                         </span>
                     </label>
                 </li>
+                <? endif ?>
             <? endif ?>
             <? endforeach ?>
         </ul>
-        <?= QuickSearch::get("add_seminar_id", new SeminarSearch())->render() ?>
+        <?= QuickSearch::get("add_seminar_id", $seminarsearch)->render() ?>
 
         <h3><?= _("Teilnehmergruppen in Veranstaltungen") ?></h3>
         <ul class="clean statusgroupselector">
@@ -54,11 +56,11 @@
                             <input type="checkbox" name="remove_statusgruppe[]" value="<?= htmlReady($assignment['range_id']) ?>" style="display: none;">
                             <? $statusgruppe = Statusgruppen::find($assignment['range_id']) ?>
                             <span>
-                            <a href="<?= URLHelper::getLink("seminar_main.php", ['auswahl' => $statusgruppe->getId()]) ?>">
-                                <?= htmlReady($statusgruppe->course['name'].": ".$statusgruppe->name) ?>
-                            </a>
-                            <?= Icon::create("trash", "clickable")->asimg("20px", ['class' => "text-bottom", 'title' => _("Zuweisung zur Veranstaltung aufheben.")]) ?>
-                        </span>
+                                <a href="<?= URLHelper::getLink("seminar_main.php", ['auswahl' => $statusgruppe->getId()]) ?>">
+                                    <?= htmlReady($statusgruppe->course['name'].": ".$statusgruppe->name) ?>
+                                </a>
+                                <?= Icon::create("trash", "clickable")->asimg("20px", ['class' => "text-bottom", 'title' => _("Zuweisung zur Veranstaltung aufheben.")]) ?>
+                            </span>
                         </label>
                     </li>
                 <? endif ?>
diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php
index 0cd4af66673a3474c467edc58c4fb8220be70981..df3677bd45b8830c88395844ee7653da106765bd 100644
--- a/app/views/questionnaire/edit.php
+++ b/app/views/questionnaire/edit.php
@@ -1,76 +1,187 @@
+<?php
+/**
+ * @var Questionnaire $questionnaire
+ */
+$questiontypes = [];
+$questiontypes['Vote'] = [
+    'name' => Vote::getName(),
+    'type' => Vote::class,
+    'icon' => Vote::getIconShape(),
+    'component' => Vote::getEditingComponent()
+];
+foreach (get_declared_classes() as $class) {
+    if (is_subclass_of($class, 'QuestionType')) {
+        if (!isset($questiontypes[$class])) {
+            $questiontypes[$class] = [
+                'name' => $class::getName(),
+                'type' => $class,
+                'icon' => $class::getIconShape(),
+                'component' => $class::getEditingComponent()
+            ];
+        }
+    }
+}
+$questionnaire_data = [
+    'id' => $questionnaire->getId(),
+    'title' => $questionnaire['title'],
+    'startdate' => $questionnaire->isNew() ? _('sofort') : $questionnaire['startdate'],
+    'stopdate' => $questionnaire['stopdate'],
+    'copyable' => $questionnaire['copyable'],
+    'anonymous' => $questionnaire['anonymous'],
+    'editanswers' => $questionnaire['editanswers'],
+    'resultvisibility' => $questionnaire['resultvisibility'],
+];
+$questions_data = [];
+foreach ($questionnaire->questions as $question) {
+    $questions_data[] = [
+        'id' => $question->getId(),
+        'questiontype' => $question['questiontype'],
+        'internal_name' => $question['internal_name'],
+        'questiondata' => $question['questiondata']->getArrayCopy()
+    ];
+}
+?>
 <form action="<?= URLHelper::getLink("dispatch.php/questionnaire/edit/".(!$questionnaire->isNew() ? $questionnaire->getId() : "")) ?>"
-      method="post" enctype="multipart/form-data"
+      method="post"
+      enctype="multipart/form-data"
       class="questionnaire_edit default"
-      <?= Request::isAjax() ? "data-dialog" : "" ?> data-secure="true">
-    <input type="hidden" name="order" value="<?= htmlReady(json_encode($order)) ?>">
-    <? if (Request::get("range_id")) : ?>
-        <input type="hidden" name="range_id" value="<?= htmlReady(Request::get("range_id")) ?>">
-        <input type="hidden" name="range_type" value="<?= htmlReady(Request::get("range_type", "static")) ?>">
-    <? endif ?>
-    <fieldset>
-        <legend><?= _("Fragebogen") ?></legend>
-        <label>
-            <?= _("Titel des Fragebogens") ?>
-            <input type="text" name="questionnaire[title]" value="<?= htmlReady($questionnaire['title']) ?>" class="size-l" required>
-        </label>
-    </fieldset>
+      data-questiontypes="<?= htmlReady(json_encode($questiontypes)) ?>"
+      data-questionnaire_data="<?= htmlReady(json_encode($questionnaire_data)) ?>"
+      data-questions_data="<?= htmlReady(json_encode($questions_data)) ?>"
+      data-range_type="<?= htmlReady(Request::get('range_type')) ?>"
+      data-range_id="<?= htmlReady(Request::get('range_id')) ?>"
+    <?= Request::isAjax() ? "data-dialog" : "" ?>
+      :data-secure="activateFormSecure">
 
-    <div class="all_questions">
-        <? foreach ($questionnaire->questions as $index => $question) : ?>
-            <?= $this->render_partial("questionnaire/_question.php", compact("question")) ?>
-        <? endforeach ?>
-    </div>
-
-    <div style="text-align: right;" class="add_questions">
-        <? foreach (get_declared_classes() as $class) :
-            if (in_array('QuestionType', class_implements($class))) : ?>
-                <a href="" onClick="STUDIP.Questionnaire.addQuestion('<?= htmlReady($class) ?>'); return false;">
-                    <?= $class::getIcon(true, true)->asimg("40px") ?>
-                    <?= htmlReady($class::getName()) ?>
-                </a>
-            <? endif;
-        endforeach ?>
-    </div>
-
-    <fieldset class="questionnaire_metadata">
 
-        <label>
-            <?= _("Startzeitpunkt (leer lassen für manuellen Start)") ?>
-            <input type="text" name="questionnaire[startdate]" value="<?= $questionnaire['startdate'] ? date("d.m.Y H:i", $questionnaire['startdate']) : ($questionnaire->isNew() ? _("sofort") : "") ?>" data-datetime-picker>
-        </label>
+    <div class="editor">
+        <div class="rightside" aria-live="polite" tabindex="0" ref="rightside">
+            <div class="admin" v-if="activeTab === 'admin'">
+                <div class="formpart">
+                    <label class="studiprequired" for="questionnaire_title">
+                        <span class="textlabel"><?= _('Titel des Fragebogens') ?></span>
+                        <span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
+                    </label>
+                    <input type="text" id="questionnaire_title" v-model="data.title" ref="autofocus">
+                </div>
 
-        <label>
-            <?= _("Endzeitpunkt (leer lassen für manuelles Ende)") ?>
-            <input type="text" name="questionnaire[stopdate]" value="<?= $questionnaire['stopdate'] ? date("d.m.Y H:i", $questionnaire['stopdate']) : "" ?>" data-datetime-picker>
-        </label>
+                <div class="hgroup">
+                    <label>
+                        <?= _('Startzeitpunkt') ?>
+                        <datetimepicker v-model="data.startdate"></datetimepicker>
+                    </label>
+                    <label>
+                        <?= _('Endzeitpunkt') ?>
+                        <datetimepicker v-model="data.stopdate"></datetimepicker>
+                    </label>
+                </div>
+                <label>
+                    <input type="checkbox" v-model="data.copyable" true-value="1" false-value="0">
+                    <?= _('Fragebogen zum Kopieren freigeben') ?>
+                </label>
+                <label>
+                    <input type="checkbox" v-model="data.anonymous" true-value="1" false-value="0">
+                    <?= _('Teilnehmende anonymisieren') ?>
+                </label>
+                <label>
+                    <input type="checkbox" v-model="data.editanswers" true-value="1" false-value="0">
+                    <?= _('Teilnehmende dürfen ihre Antworten revidieren') ?>
+                </label>
+                <label>
+                    <?= _('Ergebnisse einsehbar') ?>
+                    <select v-model="data.resultvisibility">
+                        <option value="always"><?= _('Immer') ?></option>
+                        <option value="afterending"><?= _('Nach Ende der Befragung') ?></option>
+                        <option value="afterparticipation"><?= _('Nach der Teilnahme') ?></option>
+                        <option value="never"><?= _('Niemals') ?></option>
+                    </select>
+                </label>
+            </div>
+            <div class="add_question file_select_possibilities" v-else-if="activeTab === 'add_question'">
+                <div>
+                    <button v-for="(questiontype, key) in questiontypes" :key="key"
+                       :ref="key == Object.keys(questiontypes)[0] ? 'autofocus' : ''"
+                       href=""
+                       @click.prevent="addQuestion(questiontype.type)">
+                        <studip-icon :shape="questiontype.icon" size="40" role="clickable"></studip-icon>
+                        {{questiontype.name}}
+                    </button>
+                </div>
+            </div>
+            <div v-else>
+                <? foreach ($questiontypes as $questiontype) : ?>
+                <component is="<?= htmlReady($questiontype['component'][0]) ?>"
+                           v-if="questiontypes[questions[getIndexForQuestion(activeTab)].questiontype].component[0] === '<?= htmlReady($questiontype['component'][0]) ?>'"
+                           v-model="questions[getIndexForQuestion(activeTab)].questiondata"
+                           :question_id="questions[getIndexForQuestion(activeTab)].id">
+                </component>
+                <? endforeach ?>
+            </div>
+        </div>
+        <aside>
+            <a :class="activeTab === 'admin' ? 'admin active' : 'admin'"
+               href=""
+               @click.prevent="switchTab('admin')">
+                <span class="icon"><studip-icon shape="evaluation" role="clickable" size="30" alt=""></studip-icon></span>
+                <?= _('Einstellungen') ?>
+            </a>
+            <draggable v-if="questions.length > 0" v-model="questions" handle=".handle" group="questions" class="questions_container questions">
+                <div v-for="question in questions"
+                     :key="question.id"
+                     @mouseenter="hoverTab = question.id"
+                     @mouseleave="hoverTab = null"
+                     :class="(activeTab === question.id || activeTab === 'meta_' + question.id ? 'active' : '') + (hoverTab === question.id ? ' hovered' : '')">
+                    <a href="#"
+                       @click.prevent="switchTab(question.id)">
+                        <span class="icon handle">
+                            <studip-icon :shape="(hoverTab === question.id) && (questions.length > 1) ? 'hamburger' : questiontypes[question.questiontype].icon" role="clickable" size="30" alt=""></studip-icon>
+                        </span>
 
-        <label>
-            <input type="checkbox" name="questionnaire[copyable]" value="1"<?= $questionnaire['copyable'] ? " checked" : "" ?>>
-            <?= _("Fragebogen zum Kopieren freigeben") ?>
-        </label>
+                        <div v-if="editInternalName !== question.id">{{ question.internal_name || questiontypes[question.questiontype].name}}</div>
+                        <div v-else class="inline_editing">
+                            <input type="text" ref="editInternalName" v-model="tempInternalName" class="inlineediting_internal_name">
+                            <button @click="saveInternalName(question.id)">
+                                <studip-icon shape="accept" role="clickable" size="20" title="<?= _('Internen Namen speichern') ?>"></studip-icon>
+                            </button>
+                            <button @click="editInternalName = null">
+                                <studip-icon shape="decline" role="clickable" size="20" title="<?= _('Internen Namen nicht speichern') ?>"></studip-icon>
+                            </button>
+                        </div>
+                    </a>
 
-        <label>
-            <input type="checkbox" name="questionnaire[anonymous]" onChange="jQuery('#questionnaire_editanswers').toggle(!this.checked);" value="1"<?= $questionnaire['anonymous'] ? " checked" : "" ?>>
-            <?= _("Anonym teilnehmen") ?>
-        </label>
-
-        <label id="questionnaire_editanswers" <?= $questionnaire['anonymous'] ? 'style="display: none"' : '' ?>>
-            <input type="checkbox" name="questionnaire[editanswers]" value="1"<?= $questionnaire['editanswers'] || $questionnaire->isNew() ? " checked" : "" ?>>
-            <?= _("Teilnehmende dürfen ihre Antworten revidieren") ?>
-        </label>
+                    <studip-action-menu :items="[{label: '<?= _('Umbenennen') ?>', icon: 'edit', emit: 'rename'}, {label: '<?= _('Frage kopieren') ?>', icon: 'clipboard', emit: 'copy'}, {label: '<?= _('Frage nach oben verschieben') ?>', icon: 'arr_1up', emit: 'moveup'}, {label: '<?= _('Frage nach unten verschieben') ?>', icon: 'arr_1down', emit: 'movedown'}, {label: '<?= _('Frage löschen') ?>', icon: 'trash', emit: 'delete'}]"
+                                        @copy="duplicateQuestion(question.id)"
+                                        @rename="renameInternalName(question.id)"
+                                        @moveup="moveQuestionUp(question.id)"
+                                        @movedown="moveQuestionDown(question.id)"
+                                        @delete="askForDeletingTheQuestion(question.id)"></studip-action-menu>
+                </div>
+            </draggable>
+            <a :class="activeTab === 'add_question' ? 'add_question active' : 'add_question'"
+               href=""
+               @click.prevent="switchTab('add_question')">
+                <span class="icon"><studip-icon shape="add" role="clickable" size="30" alt=""></studip-icon></span>
+                <?= _('Element hinzufügen') ?>
+            </a>
+        </aside>
+    </div>
 
-        <label>
-            <?= _("Ergebnisse an Teilnehmende") ?>
-            <select name="questionnaire[resultvisibility]">
-                <option value="always"<?= $questionnaire['resultvisibility'] === "always" ? " selected" : "" ?>><?= _("Immer.") ?></option>
-                <option value="afterending"<?= $questionnaire['resultvisibility'] === "afterending" ? " selected" : "" ?>><?= _("Nach Ende der Befragung.") ?></option>
-                <option value="never"<?= $questionnaire['resultvisibility'] === "never" ? " selected" : "" ?>><?= _("Niemals.") ?></option>
-            </select>
-        </label>
 
-    </fieldset>
+    <studip-dialog
+        v-if="askForDeletingQuestions"
+        title="<?= _('Bitte bestätigen Sie die Aktion') ?>"
+        question="<?= _('Wirklich löschen?') ?>"
+        confirmText="<?= _('Ja') ?>"
+        closeText="<?= _('Nein') ?>"
+        closeClass="cancel"
+        height="180"
+        @confirm="deleteQuestion"
+        @close="askForDeletingQuestions = false"
+    >
+    </studip-dialog>
 
     <footer data-dialog-button>
-        <?= \Studip\Button::create(_("Speichern"), 'questionnaire_store') ?>
+        <?= \Studip\LinkButton::create(_("Speichern"), 'questionnaire_store', ['onclick' => 'STUDIP.Questionnaire.Editor.submit(); return false;']) ?>
     </footer>
 </form>
+
diff --git a/app/views/questionnaire/evaluate.php b/app/views/questionnaire/evaluate.php
index 7a8def7b6c9957d200df4b273e0f0e6e68661cea..186ef2b92e46ff91226226c278a0c89c41c47bd5 100644
--- a/app/views/questionnaire/evaluate.php
+++ b/app/views/questionnaire/evaluate.php
@@ -1,9 +1,27 @@
-<div class="questionnaire_results questionnaire_<?= $questionnaire->getId() ?>" data-questionnaire_id="<?= $questionnaire->getId() ?>">
+<?
+/**
+ * @var Questionnaire $questionnaire
+ * @var array $filtered
+ */
+$only_user_ids = null;
+if ($filtered[$questionnaire->getId()] && $filtered[$questionnaire->getId()]['question_id']) {
+    foreach ($questionnaire->questions as $question) {
+        if ($question->getId() === $filtered[$questionnaire->getId()]['question_id']) {
+            $only_user_ids = $question->getUserIdsOfFilteredAnswer($filtered[$questionnaire->getId()]['filterForAnswer']);
+            break;
+        }
+    }
+}
+
+?>
+<div class="questionnaire_results questionnaire_<?= $questionnaire->getId() ?>"
+     data-questionnaire_id="<?= $questionnaire->getId() ?>"
+     data-title="<?= htmlReady($questionnaire['title']) ?>">
 
     <? if ($questionnaire->resultsVisible()) : ?>
         <? foreach ($questionnaire->questions as $question) : ?>
-            <article class="question_<?= $question->getId() ?>">
-                <? $template = $question->getResultTemplate() ?>
+            <article class="question question_<?= $question->getId() ?>">
+                <? $template = $question->getResultTemplate($only_user_ids, $filtered[$questionnaire->getId()]['question_id'] === $question->getId() ? $filtered[$questionnaire->getId()]['filterForAnswer'] : null) ?>
                 <?= $template ? $template->render(['anonAnswers' => $anonAnswers ?? '']) : _("Ergebnisse konnten nicht ausgewertet werden.") ?>
             </article>
         <? endforeach ?>
@@ -51,6 +69,9 @@
         <? if ($questionnaire->isEditable() && !$questionnaire->isRunning()) : ?>
             <?= \Studip\LinkButton::create(_("Starten"), URLHelper::getURL("dispatch.php/questionnaire/start/".$questionnaire->getId())) ?>
         <? endif ?>
+        <? if ($questionnaire->resultsVisible()) : ?>
+            <?= \Studip\LinkButton::create(_('PDF exportieren'), '#', ['onclick' => "STUDIP.Questionnaire.exportEvaluationAsPDF(); return false;"]) ?>
+        <? endif ?>
         <? if ($questionnaire->isEditable() && $questionnaire->isRunning()) : ?>
             <?= \Studip\LinkButton::create(_("Beenden"), URLHelper::getURL("dispatch.php/questionnaire/stop/".$questionnaire->getId())) ?>
         <? endif ?>
diff --git a/app/views/questionnaire/overview.php b/app/views/questionnaire/overview.php
index 8529d09e8f2af544bfc4a2159d27211a132ac518..e99ef8d5e2877d0d3c6c6929e0eb8c1f85e652e0 100644
--- a/app/views/questionnaire/overview.php
+++ b/app/views/questionnaire/overview.php
@@ -1,5 +1,6 @@
 <form action="<?= $controller->link_for("questionnaire/bulkdelete", compact('range_type', 'range_id')) ?>"
       method="post">
+    <?= CSRFProtection::tokenTag() ?>
     <table class="default" id="questionnaire_overview">
         <thead>
             <tr>
@@ -48,7 +49,7 @@ if (!empty($statusgruppen)) {
         _('Fragebogen erstellen'),
         $controller->url_for('questionnaire/edit', compact('range_type', 'range_id')),
         Icon::create('add'),
-        ['data-dialog' => '']
+        ['data-dialog' => 'size=big']
     );
 }
 Sidebar::Get()->addWidget($actions);
diff --git a/app/views/questionnaire/question_types/freetext/freetext_answer.php b/app/views/questionnaire/question_types/freetext/freetext_answer.php
index 0b2643d72dc78762676bf69166afb3c74148f993..13a0b3e05447a3e56c27916e00b7690336d03d22 100644
--- a/app/views/questionnaire/question_types/freetext/freetext_answer.php
+++ b/app/views/questionnaire/question_types/freetext/freetext_answer.php
@@ -1,19 +1,14 @@
 <?php
-    $etask = $vote->etask;
-
+/**
+ * @var QuestionnaireQuestion $vote
+ */
     $answer = $vote->getMyAnswer();
     $answerdata = $answer['answerdata'] ? $answer['answerdata']->getArrayCopy() : [];
 ?>
 
 <label>
-    <div>
-        <?= Icon::create('guestbook', Icon::ROLE_INFO)->asImg(20, ['class' => 'text-bottom']) ?>
-        <? if (isset($etask->options['mandatory']) && $etask->options['mandatory']) : ?>
-            <?= Icon::create('star', Icon::ROLE_ATTENTION)->asImg(20, ['class' => 'text-bottom', 'title' => _("Pflichtantwort")]) ?>
-        <? endif ?>
-        <?= formatReady($etask->description) ?>
-    </div>
+    <?= $this->render_partial('questionnaire/_answer_description_container', ['vote' => $vote, 'iconshape' => 'guestbook']) ?>
     <textarea name="answers[<?= $vote->getId() ?>][answerdata][text]"
-              <?= isset($etask->options['mandatory']) && $etask->options['mandatory'] ? "required" : "" ?>
+              <?= isset($vote->questiondata['mandatory']) && $vote->questiondata['mandatory'] ? "required" : "" ?>
               ><?= htmlReady($answerdata['text'] ?? '') ?></textarea>
 </label>
diff --git a/app/views/questionnaire/question_types/freetext/freetext_edit.php b/app/views/questionnaire/question_types/freetext/freetext_edit.php
deleted file mode 100644
index e9be20da09b448784b695875b9de90172d905a84..0000000000000000000000000000000000000000
--- a/app/views/questionnaire/question_types/freetext/freetext_edit.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-    $etask = $vote->etask;
-?>
-<label>
-    <?= _('Frage') ?>
-    <textarea name="questions[<?= $vote->getId() ?>][description]"
-              class="size-l wysiwyg"
-              placeholder="<?= _('Erzählen Sie uns ...') ?>"
-    ><?= wysiwygReady($etask ? $etask->description : '') ?></textarea>
-</label>
-
-<input type="hidden" name="questions[<?= $vote->getId() ?>][options][mandatory]" value="0">
-<label>
-    <input type="checkbox"
-           name="questions[<?= $vote->getId() ?>][options][mandatory]"
-           value="1"<?= isset($etask->options['mandatory']) && $etask->options['mandatory'] ? 'checked' : '' ?>>
-    <?= _("Pflichtfrage") ?>
-</label>
diff --git a/app/views/questionnaire/question_types/freetext/freetext_evaluation.php b/app/views/questionnaire/question_types/freetext/freetext_evaluation.php
index 6877a72eda6f8476863c92677c0499b4a6b0369d..26608b0bb130f8c7782356be5767d2184c34c39c 100644
--- a/app/views/questionnaire/question_types/freetext/freetext_evaluation.php
+++ b/app/views/questionnaire/question_types/freetext/freetext_evaluation.php
@@ -1,13 +1,20 @@
 <?php
-    $etask = $vote->etask;
+/**
+ * @var QuestionnaireQuestion $vote
+ * @var QuestionnaireAnswer[] $answers
+ */
 ?>
-<h3>
-    <?= formatReady($etask->description) ?>
-</h3>
-
+<div class="description_container">
+    <div class="icon_container">
+        <?= Icon::create('guestbook', Icon::ROLE_INFO)->asImg(20) ?>
+    </div>
+    <div class="description">
+        <?= formatReady($vote->questiondata['description']) ?>
+    </div>
+</div>
 
 <ul class="clean">
-<? foreach ($vote->answers as $answer) : ?>
+<? foreach ($answers as $answer) : ?>
     <? if (trim($answer['answerdata']['text'])) : ?>
     <li style="border: #d0d7e3 thin solid; margin: 10px; padding: 10px;">
     <? if (!$vote->questionnaire['anonymous']) : ?>
diff --git a/app/views/questionnaire/question_types/info/info.php b/app/views/questionnaire/question_types/info/info.php
new file mode 100644
index 0000000000000000000000000000000000000000..04bae4fa8a391a2a3a37a6975e86d028d8d9fab5
--- /dev/null
+++ b/app/views/questionnaire/question_types/info/info.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ */
+?>
+
+<div class="description_container">
+    <div class="icon_container">
+        <?= Icon::create('info-circle', Icon::ROLE_INFO)->asImg(20) ?>
+    </div>
+    <div class="description">
+        <? if (isset($vote->questiondata['url']) && trim($vote->questiondata['url'])) : ?>
+            <iframe <?= is_internal_url($vote->questiondata['url']) ? 'sandbox="allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-presentation allow-scripts"' : '' ?>
+                    src="<?= htmlReady($vote->questiondata['url']) ?>"></iframe>
+        <? endif ?>
+        <? if (isset($vote->questiondata['description']) && trim($vote->questiondata['description'])) : ?>
+        <article>
+            <?= formatReady($vote->questiondata['description']) ?>
+        </article>
+        <? endif ?>
+    </div>
+</div>
diff --git a/app/views/questionnaire/question_types/likert/likert_answer.php b/app/views/questionnaire/question_types/likert/likert_answer.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa2d9b01befc361bb70514b89780d7a5ec4c3dcc
--- /dev/null
+++ b/app/views/questionnaire/question_types/likert/likert_answer.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ */
+
+$answers = $vote->questiondata['options'] ?? [];
+$statements = $vote->questiondata['statements'] ?? [];
+$indexMap = count($statements) ? range(0, count($statements) - 1) : [];
+if ($vote->questiondata['randomize']) {
+    shuffle($indexMap);
+}
+
+$response = $vote->getMyAnswer();
+$responseData = isset($response->answerdata['answers']) ? $response->answerdata['answers']->getArrayCopy() : [];
+?>
+<div <?= isset($vote->questiondata['mandatory']) && $vote->questiondata['mandatory'] ? ' class="mandatory"' : "" ?>>
+    <?= $this->render_partial('questionnaire/_answer_description_container', ['vote' => $vote, 'iconshape' => 'likert']) ?>
+
+    <div class="hidden invalidation_notice">
+        <?= _("Diese Frage muss beantwortet werden.") ?>
+    </div>
+
+    <table class="default nohover answers">
+        <thead>
+            <tr>
+                <th><?= _('Aussage') ?></th>
+                <? foreach ($answers as $answer) : ?>
+                <th><?= htmlReady($answer) ?></th>
+                <? endforeach ?>
+            </tr>
+        </thead>
+        <tbody>
+            <? foreach ($indexMap as $index) : ?>
+            <tr>
+                <? $html_id = md5(uniqid($index)) ?>
+                <td id="<?= $html_id ?>"><?= htmlReady($statements[$index]) ?></td>
+                <? foreach ($answers as $answer_index => $answer) : ?>
+                    <td>
+                        <input type="radio"
+                               title="<?= htmlReady($answer) ?>"
+                               aria-labelledby="<?= $html_id ?>"
+                               name="answers[<?= $vote->getId() ?>][answerdata][answers][<?= htmlReady($index) ?>]"
+                                <?= $responseData[$index] === $answer_index ? 'checked' : '' ?>
+                               value="<?= htmlReady($answer_index) ?>">
+                    </td>
+                <? endforeach ?>
+            </tr>
+            <? endforeach ?>
+        </tbody>
+    </table>
+</div>
diff --git a/app/views/questionnaire/question_types/likert/likert_evaluation.php b/app/views/questionnaire/question_types/likert/likert_evaluation.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ddb12aea71096d55546f89622cfab1c58fa2ec1
--- /dev/null
+++ b/app/views/questionnaire/question_types/likert/likert_evaluation.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ * @var QuestionnaireAnswer[] $answers
+ * @var $filtered string
+ */
+$options = $vote->questiondata['options'];
+?>
+
+<div class="description_container">
+    <div class="icon_container">
+        <?= Icon::create('rangescale', Icon::ROLE_INFO)->asImg(20) ?>
+    </div>
+    <div class="description">
+        <?= formatReady(isset($vote->questiondata) && isset($vote->questiondata['description']) ? $vote->questiondata['description'] : '') ?>
+    </div>
+</div>
+
+<table class="default nohover">
+    <thead>
+        <tr>
+            <th><?= _('Aussage') ?></th>
+            <? foreach ($options as $option) : ?>
+                <th><?= htmlReady($option) ?></th>
+            <? endforeach ?>
+        </tr>
+    </thead>
+    <tbody>
+        <? $countAnswers = $vote->questionnaire->countAnswers() ?>
+        <? foreach ($vote->questiondata['statements'] as $key => $statement) : ?>
+        <tr>
+            <td>
+                <strong><?= formatReady($statement) ?></strong>
+            </td>
+
+            <? foreach($options as $option_index => $option) : ?>
+                <?
+                $hits = 0;
+                $names = [];
+                foreach ($answers as $answer) {
+                    if ($answer['answerdata']['answers'][$key] == $option_index) {
+                        $hits++;
+                        if ($answer['user_id'] && $answer['user_id'][0] !== 'n') {
+                            $names[] = $answer->user->getFullName('full');
+                        }
+                    }
+                }
+                $color = 'hsl(0 0% '.round(70 + (30 * (1 - ($hits / $countAnswers ?? 1)) )).'%)';
+                ?>
+                <td style="background-color: <?= $color ?>;" <?= count($names) > 0 ? 'title="'.htmlReady(implode(', ', $names)).'"' : ''?>>
+                    <? if ($filtered !== null && $filtered == $key.'_'.$option_index) : ?>
+                        <a href=""
+                           onclick="STUDIP.Questionnaire.removeFilter('<?= htmlReady($vote['questionnaire_id']) ?>'); return false;"
+                           title="<?= _('Zeige wieder alle Ergebnisse ohne Filterung an.') ?>">
+                            <?= Icon::create('filter2', Icon::ROLE_CLICKABLE)->asImg(16, ['class' => 'text-bottom']) ?>
+                            <?= round(100 * $hits / $countAnswers) ?>%
+                        </a>
+                    <? else : ?>
+                        <a href=""
+                           onclick="STUDIP.Questionnaire.addFilter('<?= htmlReady($vote['questionnaire_id']) ?>', '<?= htmlReady($vote->getId()) ?>', '<?= $key.'_'.$option_index ?>'); return false;"
+                           title="<?= _('Zeige nur Ergebnisse von Personen an, die diese Option gewählt haben.') ?>">
+                            <?= round(100 * $hits / $countAnswers) ?>%
+                        </a>
+                    <? endif ?>
+                </td>
+            <? endforeach ?>
+        </tr>
+    <? endforeach ?>
+    </tbody>
+</table>
diff --git a/app/views/questionnaire/question_types/rangescale/rangescale_answer.php b/app/views/questionnaire/question_types/rangescale/rangescale_answer.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ff00fe7c760a5bdcd2f09c2b4350283698babe9
--- /dev/null
+++ b/app/views/questionnaire/question_types/rangescale/rangescale_answer.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ */
+
+$answers = $vote->questiondata['options'] ?? [];
+$statements = $vote->questiondata['statements'] ?? [];
+$indexMap = count($statements) ? range(0, count($statements) - 1) : [];
+if ($vote->questiondata['randomize']) {
+    shuffle($indexMap);
+}
+
+$response = $vote->getMyAnswer();
+$responseData = $response['answerdata'] && $response['answerdata']['answers'] ? $response['answerdata']['answers']->getArrayCopy() : [];
+?>
+<div <?= isset($vote->questiondata['mandatory']) && $vote->questiondata['mandatory'] ? ' class="mandatory"' : "" ?>>
+    <?= $this->render_partial('questionnaire/_answer_description_container', ['vote' => $vote, 'iconshape' => 'rangescale']) ?>
+
+    <div class="hidden invalidation_notice">
+        <?= _("Diese Frage muss beantwortet werden.") ?>
+    </div>
+
+    <table class="default nohover answers">
+        <thead>
+            <tr>
+                <th><?= _('Aussage') ?></th>
+                <? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?>
+                <th><?= htmlReady($i) ?></th>
+                <? endfor ?>
+            </tr>
+        </thead>
+        <tbody>
+            <? foreach ($indexMap as $index) : ?>
+            <tr>
+                <? $html_id = md5(uniqid($index)) ?>
+                <td id="<?= $html_id ?>"><?= htmlReady($statements[$index]) ?></td>
+                <? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?>
+                    <td>
+                        <input type="radio"
+                               title="<?= htmlReady($i) ?>"
+                               aria-labelledby="<?= $html_id ?>"
+                               name="answers[<?= $vote->getId() ?>][answerdata][answers][<?= htmlReady($index) ?>]"
+                               <?= $responseData[$index] == $i ? 'checked' : '' ?>
+                               value="<?= htmlReady($i) ?>">
+                    </td>
+                <? endfor ?>
+            </tr>
+            <? endforeach ?>
+        </tbody>
+    </table>
+</div>
diff --git a/app/views/questionnaire/question_types/rangescale/rangescale_evaluation.php b/app/views/questionnaire/question_types/rangescale/rangescale_evaluation.php
new file mode 100644
index 0000000000000000000000000000000000000000..57f2de601f3db540ef562be04ce61da17a4f5d45
--- /dev/null
+++ b/app/views/questionnaire/question_types/rangescale/rangescale_evaluation.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ * @var QuestionnaireAnswer[] $answers
+ * @var $filtered string
+ */
+$options = range($vote->questiondata['minimum'], $vote->questiondata['maximum']);
+?>
+
+<div class="description_container">
+    <div class="icon_container">
+        <?= Icon::create('rangescale', Icon::ROLE_INFO)->asImg(20) ?>
+    </div>
+    <div class="description">
+        <?= formatReady($vote->questiondata['description']) ?>
+    </div>
+</div>
+
+<table class="default nohover">
+    <thead>
+        <tr>
+            <th><?= _('Aussage') ?></th>
+            <? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?>
+                <th><?= htmlReady($i) ?></th>
+            <? endfor ?>
+        </tr>
+    </thead>
+    <tbody>
+        <? $countAnswers = $vote->questionnaire->countAnswers() ?>
+        <? foreach ($vote->questiondata['statements'] as $key => $statement) : ?>
+        <tr>
+            <td>
+                <strong><?= formatReady($statement) ?></strong>
+            </td>
+
+            <? foreach($options as $option) : ?>
+                <?
+                $hits = 0;
+                $names = [];
+                foreach ($answers as $answer) {
+                    if ($answer['answerdata']['answers'][$key] == $option) {
+                        $hits++;
+                        if ($answer['user_id'] && $answer['user_id'][0] !== 'n') {
+                            $names[] = $answer->user->getFullName('full');
+                        }
+                    }
+                }
+                $color = 'hsl(0 0% '.round(70 + (30 * (1 - ($hits / $countAnswers ?? 1)) )).'%)';
+                ?>
+                <td style="background-color: <?= $color ?>;" style="white-space: nowrap;"<?= count($names) > 0 ? 'title="'.htmlReady(implode(', ', $names)).'"' : ''?>>
+                    <? if ($filtered !== null && $filtered == ($key.'_'.$option)) : ?>
+                        <a href=""
+                           onclick="STUDIP.Questionnaire.removeFilter('<?= htmlReady($vote['questionnaire_id']) ?>'); return false;"
+                           title="<?= _('Zeige wieder alle Ergebnisse ohne Filterung an.') ?>">
+                            <?= Icon::create('filter2', Icon::ROLE_CLICKABLE)->asImg(16, ['class' => 'text-bottom']) ?>
+                            <?= round(100 * $hits / $countAnswers) ?>%
+                        </a>
+                    <? else : ?>
+                        <a href=""
+                           onclick="STUDIP.Questionnaire.addFilter('<?= htmlReady($vote['questionnaire_id']) ?>', '<?= htmlReady($vote->getId()) ?>', '<?= $key.'_'.$option ?>'); return false;"
+                           title="<?= _('Zeige nur Ergebnisse von Personen an, die diese Option gewählt haben.') ?>">
+                            <?= round(100 * $hits / $countAnswers) ?>%
+                        </a>
+                    <? endif ?>
+                </td>
+            <? endforeach ?>
+        </tr>
+    <? endforeach ?>
+    </tbody>
+</table>
diff --git a/app/views/questionnaire/question_types/test/_answer.php b/app/views/questionnaire/question_types/test/_answer.php
deleted file mode 100644
index 15f6ba7bcfe73ca6c6ba5db16d301a68dfb66829..0000000000000000000000000000000000000000
--- a/app/views/questionnaire/question_types/test/_answer.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<li>
-    <?= Assets::img('anfasser_24.png', [ 'title' => _('Antwort verschieben'), 'class' => 'move' ]) ?>
-
-    <input type="checkbox"
-           name="questions[<?= $vote->getId() ?>][task][correct][]"
-           value="<?= $index + 1 ?>"
-           title="<?= _('Ist diese Antwort korrekt?') ?>"
-           <?= !empty($forcecorrect) || (!empty($answer['score']) && ($answer['score'] > 0)) ? 'checked' : '' ?>>
-
-    <input type="text"
-           name="questions[<?= $vote->getId() ?>][task][answers][]"
-           value="<?= htmlReady($answer['text'] ?? '') ?>"
-           placeholder="<?= _('Antwort ...') ?>"
-           aria-label="<?= _('Geben Sie eine Antwortmöglichkeit zu der von Ihnen gestellten Frage ein.') ?>">
-
-    <?= Icon::create('trash', ['title' => _('Antwort löschen')])->asImg(20, ['class' => 'text-bottom delete']) ?>
-    <?= Icon::create('add', ['title' => _('Antwort hinzufügen')])->asImg(20, ['class' => 'text-bottom add']) ?>
-</li>
diff --git a/app/views/questionnaire/question_types/test/test_edit.php b/app/views/questionnaire/question_types/test/test_edit.php
deleted file mode 100644
index f013fb3f22acda44a15de2e7631f9f22e3e3c519..0000000000000000000000000000000000000000
--- a/app/views/questionnaire/question_types/test/test_edit.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<? $etask = $vote->etask; ?>
-
-<label>
-    <?= _('Frage') ?>
-    <textarea name="questions[<?= $vote->getId() ?>][description]"
-              class="size-l wysiwyg"
-              required><?= isset($etask->description) ? wysiwygReady($etask->description) : '' ?></textarea>
-</label>
-
-<? $emptyAnswerTemplate = $this->render_partial('questionnaire/question_types/test/_answer', [ 'vote' => $vote, 'answer' => [] ]) ?>
-<ol class="clean options" data-optiontemplate="<?= htmlReady($emptyAnswerTemplate) ?>">
-    <? if (isset($etask->task['answers'])) {
-        foreach ($etask->task['answers'] as $index => $answer) {
-            echo $this->render_partial('questionnaire/question_types/test/_answer', compact('vote', 'answer', 'index'));
-        }
-    }
-    echo $this->render_partial(
-        'questionnaire/question_types/test/_answer',
-        [
-            'vote' => $vote,
-            'answer' => [],
-            'index' => ($index ?? 0) + 1,
-            'forcecorrect' => empty($etask->task['answers'])
-        ]
-    ); ?>
-</ol>
-
-<div style="padding-left: 13px; margin-bottom: 20px;">
-    <?= tooltipIcon(_('Wählen Sie über die Auswahlboxen aus, welche Antwortmöglichkeit korrekt ist.')) ?>
-</div>
-
-<label>
-    <input type="checkbox"
-           name="questions[<?= $vote->getId() ?>][task][type]"
-           value="multiple"
-           <?= $vote->isNew() || $etask->task['type'] === 'multiple' ? 'checked' : '' ?>>
-    <?= _('Mehrere Antworten sind erlaubt.') ?>
-</label>
-
-<input type="hidden" name="questions[<?= $vote->getId() ?>][options][randomize]" value="0">
-<label>
-    <input type="checkbox"
-           name="questions[<?= $vote->getId() ?>][options][randomize]"
-           value="1"
-           <?= isset($etask->options['randomize']) && $etask->options['randomize'] ? 'checked' : '' ?>>
-    <?= _('Antworten den Teilnehmenden zufällig präsentieren.') ?>
-</label>
-
-<div style="display: none" class="delete_question"><?= _('Diese Antwortmöglichkeit wirklich löschen?') ?></div>
-
-<script>
- jQuery(function () {
-     jQuery('.options').sortable({
-         'axis': 'y',
-         'containment': 'parent',
-         'handle': '.move',
-         'update': function () {
-             STUDIP.Questionnaire.Test.updateCheckboxValues();
-         }
-     });
- });
-</script>
diff --git a/app/views/questionnaire/question_types/test/test_evaluation.php b/app/views/questionnaire/question_types/test/test_evaluation.php
deleted file mode 100644
index 6c84e666fc0f0f647164ddebbe25fbadeb6c7116..0000000000000000000000000000000000000000
--- a/app/views/questionnaire/question_types/test/test_evaluation.php
+++ /dev/null
@@ -1,122 +0,0 @@
-<?
-$etask = $vote->etask;
-$taskAnswers = $etask->task['answers'];
-$numTaskAnswers = count($taskAnswers);
-
-$results = array_fill(0, $numTaskAnswers, 0);
-$results_users = array_fill(0, $numTaskAnswers, []);
-$users = [];
-
-if ($numTaskAnswers > 0) {
-    foreach ($answers as $answer) {
-        if ($etask->task['type'] === 'multiple') {
-            if (is_array($answer['answerdata']['answers']) || $answer['answerdata']['answers'] instanceof Traversable) {
-                foreach ($answer['answerdata']['answers'] as $a) {
-                    $results[(int)$a]++;
-                    $results_users[(int)$a][] = $answer['user_id'];
-                    $users[] = $answer['user_id'];
-                }
-            }
-        } else {
-            $results[(int) $answer['answerdata']['answers']]++;
-            $results_users[(int) $answer['answerdata']['answers']][] = $answer['user_id'];
-            $users[] = $answer['user_id'];
-        }
-    }
-}
-
-$users = array_unique($users);
-$labels = array_map(function ($answer) { return strip_tags(formatReady($answer['text'])); }, $taskAnswers->getArrayCopy());
-?>
-
-<h3>
-    <?= Icon::create('test', 'info')->asImg(20, ['class' => 'text-bottom']) ?>
-    <?= formatReady($etask->description) ?>
-</h3>
-
-<? if (count($vote->answers) > 0 && $numTaskAnswers > 0) : ?>
-    <div style="max-height: none; opacity: 1;"
-         id="questionnaire_<?= $vote->getId() ?>_chart"
-         class="ct-chart"></div>
-
-    <script>
-         STUDIP.Questionnaire.initTestEvaluation(
-             '#questionnaire_<?= $vote->getId() ?>_chart',
-             <?= json_encode(
-                 [
-                     "labels" => $labels,
-                     "series" => [$results],
-                 ]
-                 ) ?>,
-             <?= json_encode(Request::isAjax()) ?>,
-             <?= json_encode($etask->task['type'] === 'multiple') ?>
-         );
-    </script>
-<? endif ?>
-<? if (in_array($GLOBALS['user']->id, $users) || is_array($anonAnswers)) : ?>
-<? $correctAnswered = is_array($anonAnswers) ? $vote->correctAnswered('anonymous', $anonAnswers) : $vote->correctAnswered()?>
-    <div style="max-height: none; opacity: 1; font-size: 1.4em; text-align: center;">
-        <? if ($correctAnswered) : ?>
-            <?= MessageBox::success(_("Richtig beantwortet!")) ?>
-        <? else : ?>
-            <?= MessageBox::error(_("Falsch beantwortet!")) ?>
-        <? endif ?>
-    </div>
-<? endif ?>
-
-<table class="default nohover">
-    <tbody>
-        <? $countAnswers = $vote->questionnaire->countAnswers() ?>
-        <? $userAnswer = is_array($anonAnswers) ? @$anonAnswers[0]['answerdata'] : @$answers->findBy('user_id', $GLOBALS['user']->id)->first()->answerdata ?>
-        <? if ($userAnswer instanceOf StudipArrayObject) $userAnswer = $userAnswer->getArrayCopy() ?>
-        <? foreach ($taskAnswers as $key => $answer) : ?>
-          <tr class="<?= $data['correctanswer'] ? 'correct' : 'incorrect' ?>">
-            <? $percentage = $countAnswers ? round((int) $results[$key] / $countAnswers * 100) : 0 ?>
-
-            <td style="text-align: right; background-size: <?= $percentage ?>% 100%; background-position: right center; background-image: url('<?= Assets::image_path("vote_lightgrey.png") ?>'); background-repeat: no-repeat;" width="50%">
-                <strong><?= formatReady($answer['text']) ?></strong>
-                <? if ($userAnswer) : ?>
-                    <?= Icon::create((is_array($userAnswer['answers']) && in_array($key, $userAnswer['answers'])) ? 'checkbox-checked' : 'checkbox-unchecked', 'info')->asImg( ['class' => 'text-bottom']) ?>
-                <? endif ?>
-                <? if ($answer['score'] > 0) : ?>
-                    <?= Icon::create('accept', 'status-green', ['title' => _('Diese Antwort ist richtig')])->asImg( ['class' => 'text-bottom']) ?>
-                <? else : ?>
-                    <?= Icon::create('decline', 'status-red', ['title' => _('Eine falsche Antwort')])->asImg( ['class' => 'text-bottom']) ?>
-                <? endif ?>
-            </td>
-
-            <td style="white-space: nowrap;">
-                (<?= $percentage ?>% | <?= (int) $results[$key] ?>/<?= $countAnswers ?>)
-            </td>
-
-            <td width="50%">
-                <? if (!$vote->questionnaire['anonymous'] && $results[$key]) : ?>
-
-                    <? $users = SimpleCollection::createFromArray(
-                        User::findMany($results_users[$key])); ?>
-
-                    <? foreach ($results_users[$key] as $index => $user_id) : ?>
-
-                        <? $user = $users->findOneBy('user_id', $user_id); ?>
-
-                        <? if ($user) : ?>
-                            <a href="<?= URLHelper::getLink(
-                                     'dispatch.php/profile',
-                                     ['username' => $user->username]
-                                     ) ?>">
-                                <?= Avatar::getAvatar($user_id, $user->username)->getImageTag(
-                                    Avatar::SMALL,
-                                    ['title' => $user->getFullname('no_title')]
-                                ) ?>
-                                <? if (count($results_users[$key]) < 4) : ?>
-                                    <?= htmlReady($user->getFullname('no_title')) ?>
-                                <? endif ?>
-                            </a>
-                        <? endif ?>
-                    <? endforeach ?>
-                <? endif ?>
-            </td>
-        </tr>
-    <? endforeach ?>
-    </tbody>
-</table>
diff --git a/app/views/questionnaire/question_types/vote/vote_answer.php b/app/views/questionnaire/question_types/vote/vote_answer.php
index b42ad0352ad984de28881384590e5fc8ce6297c0..23f14f41030be732c58cf9193ebef26d291466b6 100644
--- a/app/views/questionnaire/question_types/vote/vote_answer.php
+++ b/app/views/questionnaire/question_types/vote/vote_answer.php
@@ -1,23 +1,19 @@
-<?
-$etask = $vote->etask;
-
-$taskAnswers = $etask->task['answers'];
-$indexMap = count($taskAnswers) ? range(0, count($taskAnswers) - 1) : [];
-if ($etask->options['randomize']) {
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ */
+
+$answers = $vote->questiondata['options'];
+$indexMap = count($answers) ? range(0, count($answers) - 1) : [];
+if ($vote->questiondata['randomize']) {
     shuffle($indexMap);
 }
 
 $response = $vote->getMyAnswer();
 $responseData = $response['answerdata'] ? $response['answerdata']->getArrayCopy() : [];
 ?>
-<div <?= isset($etask->options['mandatory']) && $etask->options['mandatory'] ? ' class="mandatory"' : "" ?>>
-    <h3>
-        <?= Icon::create(is_a($vote, 'Test') ? 'test' : 'vote', 'info')->asImg(20, ['class' => 'text-bottom']) ?>
-        <? if (isset($etask->options['mandatory']) && $etask->options['mandatory']) : ?>
-            <?= Icon::create('star', Icon::ROLE_ATTENTION)->asImg(20, ['class' => 'text-bottom', 'title' => _("Pflichtantwort")]) ?>
-        <? endif ?>
-        <?= formatReady($etask->description) ?>
-    </h3>
+<div <?= isset($vote->questiondata['mandatory']) && $vote->questiondata['mandatory'] ? ' class="mandatory"' : "" ?>>
+    <?= $this->render_partial('questionnaire/_answer_description_container', ['vote' => $vote, 'iconshape' => 'vote']) ?>
 
     <div class="hidden invalidation_notice">
         <?= _("Diese Frage muss beantwortet werden.") ?>
@@ -27,8 +23,7 @@ $responseData = $response['answerdata'] ? $response['answerdata']->getArrayCopy(
         <? foreach ($indexMap as $index) : ?>
             <li>
                 <label>
-
-                    <? if ($etask->task['type'] === 'multiple') : ?>
+                    <? if ($vote->questiondata['multiplechoice']) : ?>
 
                         <input type="checkbox"
                                name="answers[<?= $vote->getId() ?>][answerdata][answers][<?= $index ?>]"
@@ -43,7 +38,7 @@ $responseData = $response['answerdata'] ? $response['answerdata']->getArrayCopy(
                                <?= isset($responseData['answers']) && $index == $responseData['answers'] ? 'checked' : '' ?>>
                     <? endif ?>
 
-                    <?= formatReady($taskAnswers[$index]['text']) ?>
+                    <?= formatReady($answers[$index]) ?>
 
                 </label>
             </li>
diff --git a/app/views/questionnaire/question_types/vote/vote_edit.php b/app/views/questionnaire/question_types/vote/vote_edit.php
deleted file mode 100644
index f6521c049944cc03e1c99f072f6a20fd3afdc9c7..0000000000000000000000000000000000000000
--- a/app/views/questionnaire/question_types/vote/vote_edit.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<? $etask = $vote->etask; ?>
-
-<label>
-    <?= _('Frage') ?>
-    <textarea name="questions[<?= $vote->getId() ?>][description]"
-              class="size-l wysiwyg"
-              required><?= isset($etask->description) ? wysiwygReady($etask->description) : '' ?></textarea>
-</label>
-
-<? $emptyAnswerTemplate =  $this->render_partial('questionnaire/question_types/vote/_answer', ['vote' => $vote, 'answer' => []])?>
-<ol class="clean options" data-optiontemplate="<?= htmlReady($emptyAnswerTemplate) ?>">
-    <? if (isset($etask->task['answers'])) {
-        foreach ($etask->task['answers'] as $answer) {
-            echo $this->render_partial('questionnaire/question_types/vote/_answer', compact('vote', 'answer'));
-        }
-    }
-    echo $emptyAnswerTemplate;
-    ?>
-</ol>
-
-<label>
-    <input type="checkbox"
-           name="questions[<?= $vote->getId() ?>][task][type]"
-           value="multiple"
-           <?= $vote->isNew() || $etask->task['type'] === 'multiple' ? 'checked' : '' ?>>
-    <?= _("Mehrere Antworten sind erlaubt.") ?>
-</label>
-
-<input type="hidden" name="questions[<?= $vote->getId() ?>][options][mandatory]" value="0">
-<label>
-    <input type="checkbox"
-           name="questions[<?= $vote->getId() ?>][options][mandatory]"
-           value="1"<?= isset($etask->options['mandatory']) && $etask->options['mandatory'] ? 'checked' : '' ?>>
-    <?= _("Pflichtfrage") ?>
-</label>
-
-<input type="hidden" name="questions[<?= $vote->getId() ?>][options][randomize]" value="0">
-<label>
-    <input type="checkbox"
-           name="questions[<?= $vote->getId() ?>][options][randomize]"
-           value="1"
-           <?= isset($etask->options['randomize']) && $etask->options['randomize'] ? 'checked' : '' ?>>
-    <?= _('Antworten den Teilnehmenden zufällig präsentieren.') ?>
-</label>
-
-
-
-
-<div style="display: none" class="delete_question"><?= _('Diese Antwortmöglichkeit wirklich löschen?') ?></div>
-
-<script>
-    jQuery(function () {
-        jQuery(".options").sortable({
-            "axis": "y",
-            "containment": "parent",
-            "handle": ".move"
-        });
-    });
-</script>
diff --git a/app/views/questionnaire/question_types/vote/vote_evaluation.php b/app/views/questionnaire/question_types/vote/vote_evaluation.php
index bf762b012249279e3c7f9e91e4865f668e55f0bf..2c547d00cf5658e6f8793c9cbf7e92bc8357d784 100644
--- a/app/views/questionnaire/question_types/vote/vote_evaluation.php
+++ b/app/views/questionnaire/question_types/vote/vote_evaluation.php
@@ -1,14 +1,18 @@
-<?
-$etask = $vote->etask;
-$taskAnswers = $etask->task['answers'];
-$numTaskAnswers = count($taskAnswers);
+<?php
+/**
+ * @var QuestionnaireQuestion $vote
+ * @var QuestionnaireAnswer[] $answers
+ * @var $filtered string
+ */
+$options = $vote->questiondata['options'];
+$numTaskAnswers = count($vote->answers);
 
 $results = array_fill(0, $numTaskAnswers, 0);
 $results_users = array_fill(0, $numTaskAnswers, []);
 
 if ($numTaskAnswers > 0) {
     foreach ($answers as $answer) {
-        if ($etask->task['type'] === 'multiple') {
+        if ($vote->questiondata['multiplechoice']) {
             if (is_array($answer['answerdata']['answers']) || $answer['answerdata']['answers'] instanceof Traversable) {
                 foreach ($answer['answerdata']['answers'] as $a) {
                     $results[(int)$a]++;
@@ -30,7 +34,7 @@ $ordered_answer_options = [];
 $ordered_users = [];
 foreach ($ordered_results as $index => $value) {
     if ($value > 0) {
-        $ordered_answer_options[] = strip_tags(formatReady($taskAnswers[$index]['text']));
+        $ordered_answer_options[] = strip_tags(formatReady($options[$index]));
     } else {
         unset($ordered_results[$index]);
     }
@@ -38,10 +42,14 @@ foreach ($ordered_results as $index => $value) {
 rsort($ordered_results);
 ?>
 
-<h3>
-    <?= Icon::create('vote', 'info')->asImg(20, ['class' => 'text-bottom']) ?>
-    <?= formatReady($etask->description) ?>
-</h3>
+<div class="description_container">
+    <div class="icon_container">
+        <?= Icon::create('vote', Icon::ROLE_INFO)->asImg(20) ?>
+    </div>
+    <div class="description">
+        <?= formatReady($vote->questiondata['description']) ?>
+    </div>
+</div>
 
 <? if (count($vote->answers) > 0 && $numTaskAnswers > 0) : ?>
     <div style="max-height: none; opacity: 1;"
@@ -58,7 +66,7 @@ rsort($ordered_results);
                  ]
              ) ?>,
              <?= json_encode(Request::isAjax()) ?>,
-             <?= json_encode($etask->task['type'] === 'multiple') ?>
+             <?= json_encode($vote->questiondata['type'] === 'multiple') ?>
          );
     </script>
 <? endif ?>
@@ -66,16 +74,29 @@ rsort($ordered_results);
 <table class="default nohover">
     <tbody>
         <? $countAnswers = $vote->questionnaire->countAnswers() ?>
-        <? foreach ($taskAnswers as $key => $answer) : ?>
+        <? foreach ($options as $key => $answer) : ?>
         <tr>
             <? $percentage = $countAnswers ? round((int) $results[$key] / $countAnswers * 100) : 0 ?>
 
             <td style="text-align: right; background-size: <?= $percentage ?>% 100%; background-position: right center; background-image: url('<?= Assets::image_path("vote_lightgrey.png") ?>'); background-repeat: no-repeat;" width="50%">
-                <strong><?= formatReady($answer['text']) ?></strong>
+                <strong><?= formatReady($answer) ?></strong>
             </td>
 
             <td style="white-space: nowrap;">
-                (<?= $percentage ?>% | <?= (int) $results[$key] ?>/<?= $countAnswers ?>)
+                <? if ($filtered !== null && $filtered == $key) : ?>
+                    <a href=""
+                       title="<?= _('Zeige wieder alle Ergebnisse ohne Filterung an.') ?>"
+                       onclick="STUDIP.Questionnaire.removeFilter('<?= htmlReady($vote['questionnaire_id']) ?>'); return false;">
+                        <?= Icon::create('filter2', Icon::ROLE_CLICKABLE)->asImg(16, ['class' => 'text-bottom']) ?>
+                        (<?= $percentage ?>% | <?= (int) $results[$key] ?>/<?= $countAnswers ?>)
+                    </a>
+                <? else : ?>
+                    <a href=""
+                       onclick="STUDIP.Questionnaire.addFilter('<?= htmlReady($vote['questionnaire_id']) ?>', '<?= htmlReady($vote->getId()) ?>', '<?= $key ?>'); return false;"
+                       title="<?= _('Zeige nur Ergebnisse von Personen an, die diese Option gewählt haben.') ?>">
+                        (<?= $percentage ?>% | <?= (int) $results[$key] ?>/<?= $countAnswers ?>)
+                    </a>
+                <? endif ?>
             </td>
 
             <td width="50%">
diff --git a/db/migrations/5.3.14_revamp_questionnaires.php b/db/migrations/5.3.14_revamp_questionnaires.php
new file mode 100644
index 0000000000000000000000000000000000000000..eeb4d59fbb873f05a0c260d855e7b737a3882af1
--- /dev/null
+++ b/db/migrations/5.3.14_revamp_questionnaires.php
@@ -0,0 +1,124 @@
+<?php
+
+
+class RevampQuestionnaires extends Migration
+{
+    public function description()
+    {
+        return 'Better questionnaires and no old evaluations for Stud.IP';
+    }
+
+
+    public function up()
+    {
+        $query = 'INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`)
+                  VALUES (:name, :value, :type, :range, :section, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :description)';
+        $statement = DBManager::get()->prepare($query);
+        $statement->execute([
+            'name'        => 'EVAL_ENABLE',
+            'description' => 'Sollen die alten Evaluationen weiterhin eingeschaltet bleiben? Achtung, die alten Evaluationen werden in einem zukünftigen Stud.IP-Release entfernt.',
+            'range'       => 'global',
+            'type'        => 'boolean',
+            'value'       => '0',
+            'section'     => 'evaluation'
+        ]);
+
+        //Umbau der Fragebögen, sodass sie nicht mehr die etask-Tabellen verwenden:
+        DBManager::get()->exec("
+            ALTER TABLE `questionnaire_questions`
+            ADD COLUMN `questiontype` varchar(64) NOT NULL DEFAULT '' AFTER `questionnaire_id`,
+            ADD COLUMN `internal_name` varchar(128) DEFAULT NULL AFTER `questiontype`,
+            ADD COLUMN `questiondata` text NOT NULL DEFAULT '' AFTER `internal_name`
+        ");
+
+        $allquestions = DBManager::get()->prepare("
+            SELECT * FROM `questionnaire_questions`
+        ");
+        $allquestions->execute();
+        $updatequestion = DBManager::get()->prepare("
+            UPDATE `questionnaire_questions`
+            SET `questiondata` = :questiondata,
+                `questiontype` = :questiontype
+            WHERE `question_id` = :question_id
+        ");
+        $get_etask = DBManager::get()->prepare("
+            SELECT * FROM `etask_tasks` WHERE `id` = ?
+        ");
+
+        while ($question = $allquestions->fetch(PDO::FETCH_ASSOC)) {
+            $get_etask->execute([$question['etask_task_id']]);
+            $etask = $get_etask->fetch(PDO::FETCH_ASSOC);
+
+            $task = json_decode($etask['task'], true);
+            $options = array_map(function ($answer) { return $answer['text']; }, (array) $task['answers']);
+            $scores = array_map(function ($answer) { return $answer['score']; }, (array) $task['answers']);
+
+            if ($etask['type'] === 'multiple-choice') {
+                //Vote or Test
+                $questiontype = array_sum($scores) > 0 ? 'Test' : 'Vote';
+                $questiondata = [
+                    'description' => $etask['description'],
+                    'multiplechoice' => $task['type'] === 'multiple' ? '1' : '0',
+                    'options' => $options
+                ];
+            } else {
+                //Most of the times Freetext
+                $questiontype = ucfirst($etask['type']);
+                $questiondata = $task;
+                $questiondata['description'] = $etask['description'];
+            }
+            $questiondata = array_merge($questiondata, json_decode($etask['options'], true));
+
+            $updatequestion->execute([
+                'question_id' => $question['question_id'],
+                'questiondata' => json_encode($questiondata),
+                'questiontype' => $questiontype
+            ]);
+        }
+
+        DBManager::get()->exec("
+            DELETE FROM `etask_tasks`
+            WHERE `id` IN (SELECT `etask_task_id` FROM `questionnaire_questions`)
+        ");
+
+        DBManager::get()->exec("
+            ALTER TABLE `questionnaire_questions`
+            DROP COLUMN `etask_task_id`
+        ");
+
+        DBManager::get()->exec("
+            ALTER TABLE `questionnaires`
+            CHANGE COLUMN `resultvisibility` `resultvisibility` enum('always','never','afterending', 'afterparticipation') CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT 'always'
+        ");
+
+        //Tests aus den Fragebögen löschen:
+        DBManager::get()->exec("
+            DELETE FROM `questionnaire_questions`
+            WHERE `questiontype` = 'Test'
+        ");
+        //Dann noch die jetzt vielleicht leeren Fragebögen abräumen:
+        DBManager::get()->exec("
+            DELETE FROM `questionnaires`
+            WHERE `questionnaire_id` NOT IN (SELECT `questionnaire_id` FROM `questionnaire_questions`)
+        ");
+        DBManager::get()->exec("
+            DELETE FROM `questionnaire_anonymous_answers`
+            WHERE `questionnaire_id` NOT IN (SELECT `questionnaire_id` FROM `questionnaires`)
+        ");
+        DBManager::get()->exec("
+            DELETE FROM `questionnaire_assignments`
+            WHERE `questionnaire_id` NOT IN (SELECT `questionnaire_id` FROM `questionnaires`)
+        ");
+        DBManager::get()->exec("
+            DELETE FROM `questionnaire_answers`
+            WHERE `question_id` NOT IN (SELECT `question_id` FROM questionnaire_questions)
+        ");
+
+    }
+
+
+    public function down()
+    {
+
+    }
+}
diff --git a/lib/classes/QuestionType.interface.php b/lib/classes/QuestionType.interface.php
index 342a0bbb910dc2c2b6e2721ca845106af29c3b1e..ada600549260540c0e87a6a94a4eed7d913a1606 100644
--- a/lib/classes/QuestionType.interface.php
+++ b/lib/classes/QuestionType.interface.php
@@ -14,7 +14,14 @@ interface QuestionType {
      * object but called staticly.
      * @return Icon the specific icon for this type of question
      */
-    static public function getIcon($active = false, $add = false);
+    static public function getIcon(bool $active = false) : Icon;
+
+
+    /**
+     * Returns the shape of the icon that is used in vue.
+     * @return string
+     */
+    static public function getIconShape();
 
     /**
      * Returns the name of the type of question like "Frage" or "Test" or "Dateiablage"
@@ -26,19 +33,20 @@ interface QuestionType {
     static public function getName();
 
     /**
-     * Returns a template that is used to edit or create the question. Note that
-     * $this['data'] might already be filled with data, when the user is editing an
-     * existing question.
-     * @return Flexi_Template
+     * Returns an array with two parts: First one is the name of the component for editing the question. Second
+     * one is the import path of the component. Plugins can use this to get their component imported.
+     * @return Array
      */
-    public function getEditingTemplate();
+    static public function getEditingComponent();
 
     /**
-     * Called right before the questionnaire and the question is stored or when the user
-     * needs to refresh the editing-window, This method is called to store the
-     * request-values into $this['data']. You get them from the Request-class as usual.
+     * Usually the $questiondata is already in the correct format. But for some question types
+     * some data have to be manipulated by for example the HTML-purifier. So this takes
+     * the questiondata and changed them before they get stored.
+     * @param $questiondata
+     * @return mixed
      */
-    public function createDataFromRequest();
+    public function beforeStoringQuestiondata($questiondata);
 
     /**
      * Display the question to the user. This template will be embedded into a
@@ -63,6 +71,15 @@ interface QuestionType {
      */
     public function createAnswer();
 
+    /**
+     * In the evaluation of the questionnaire you can click on a certain answer and get the evaluation filtered
+     * by the the people that have given that answer. This method asks from the question, what user_ids have
+     * given the answer_option. Answer option could be anything that this question understands as an answer.
+     * @param $answer_option
+     * @return mixed
+     */
+    public function getUserIdsOfFilteredAnswer($answer_option);
+
     /**
      * Returns a template with the results of this question.
      * @param $only_user_ids : array\null array of user_ids that the results should be restricted to.
@@ -95,4 +112,4 @@ interface QuestionType {
      * @return void
      */
     public function onEnding();
-}
\ No newline at end of file
+}
diff --git a/lib/models/Freetext.php b/lib/models/Freetext.php
index 0c8272b99651dbb297177aff77c0a13c037d1c2b..4cc8b62348c01cc03fb58458468470422c696c8b 100644
--- a/lib/models/Freetext.php
+++ b/lib/models/Freetext.php
@@ -2,73 +2,50 @@
 
 require_once 'lib/classes/QuestionType.interface.php';
 
-use eTask\Task;
-
 class Freetext extends QuestionnaireQuestion implements QuestionType
 {
     /**
      * Returns the Icon-object to this QuestionType.
      * @param bool $active: true if Icon should be clickable, false for black info-icon.
-     * @param bool $add : true if the add-appendix shoudl be added to the icon.
      * @return Icon : guestbook-icon.
      */
-    public static function getIcon($active = false, $add = false)
+    public static function getIcon(bool $active = false) : Icon
     {
         return Icon::create(
-            'guestbook',
+            static::getIconShape(),
             $active ? Icon::ROLE_CLICKABLE : Icon::ROLE_INFO
         );
     }
 
     /**
-     * Returns the name of this QuestionType "Freitextfrage".
+     * Returns the shape of the icon of this QuestionType
      * @return string
      */
-    public static function getName()
+    public static function getIconShape()
     {
-        return _('Freitextfrage');
+        return 'guestbook';
     }
 
     /**
-     * Returns the template to edit this question
-     * @return Flexi_Template
-     * @throws Flexi_TemplateNotFoundException if there is no template.
+     * Returns the name of this QuestionType "Freitextfrage".
+     * @return string
      */
-    public function getEditingTemplate()
+    public static function getName()
     {
-        $factory = new Flexi_TemplateFactory(realpath(__DIR__ . '/../../app/views'));
-        $template = $factory->open('questionnaire/question_types/freetext/freetext_edit.php');
-        $template->vote = $this;
-        return $template;
+        return _('Freitextfrage');
     }
 
-    /**
-     * Processes the request and stores the given values into the etask-object.
-     * Called when the question is saved by the user.
-     */
-    public function createDataFromRequest()
+    static public function getEditingComponent()
     {
-        $questions = Request::getArray('questions');
-        $data = $questions[$this->getId()];
-
-        if (!$this->etask) {
-            $this->etask = Task::create([
-                'type'    => 'freetext',
-                'user_id' => $GLOBALS['user']->id,
-            ]);
-        }
-
-        $this->etask->description = Studip\Markup::purifyHtml($data['description']);
-        $this->etask->task = [];
-
-        // update mandatory option
-        if (isset($data['options']['mandatory'])) {
-            $options = $this->etask->options;
-            $options['mandatory'] = (bool) $data['options']['mandatory'];
-            $this->etask->options = $options;
-        }
+        return ['freetext-edit', ''];
+    }
 
-        $this->etask->store();
+    public function beforeStoringQuestiondata($questiondata)
+    {
+        $questiondata['description'] = \Studip\Markup::markAsHtml(
+            \Studip\Markup::purifyHtml($questiondata['description'])
+        );
+        return $questiondata;
     }
 
     /**
@@ -98,6 +75,11 @@ class Freetext extends QuestionnaireQuestion implements QuestionType
         return $answer;
     }
 
+    public function getUserIdsOfFilteredAnswer($answer_option)
+    {
+        return [];
+    }
+
     /**
      * Returns the template with the answers of the question so far.
      * @param null $only_user_ids : array of user_ids
@@ -106,9 +88,18 @@ class Freetext extends QuestionnaireQuestion implements QuestionType
      */
     public function getResultTemplate($only_user_ids = null)
     {
+        $answers = $this->answers;
+        if ($only_user_ids !== null) {
+            foreach ($answers as $key => $answer) {
+                if (!in_array($answer['user_id'], $only_user_ids)) {
+                    unset($answers[$key]);
+                }
+            }
+        }
         $factory = new Flexi_TemplateFactory(realpath(__DIR__ . '/../../app/views'));
         $template = $factory->open('questionnaire/question_types/freetext/freetext_evaluation.php');
         $template->vote = $this;
+        $template->set_attribute('answers', $answers);
         return $template;
     }
 
@@ -121,7 +112,7 @@ class Freetext extends QuestionnaireQuestion implements QuestionType
         $output = [];
         $countNobodys = 0;
 
-        $question = trim(strip_tags($this->etask->description));
+        $question = trim(strip_tags($this->questiondata['description']));
         foreach ($this->answers as $answer) {
             if ($answer['user_id'] && $answer['user_id'] != 'nobody') {
                 $userId = $answer['user_id'];
diff --git a/lib/models/LikertScale.php b/lib/models/LikertScale.php
new file mode 100644
index 0000000000000000000000000000000000000000..d5d2d06da1edd8366da4ce6342308097f67d2548
--- /dev/null
+++ b/lib/models/LikertScale.php
@@ -0,0 +1,115 @@
+<?php
+require_once 'lib/classes/QuestionType.interface.php';
+
+class LikertScale extends QuestionnaireQuestion implements QuestionType
+{
+    public static function getIcon(bool $active = false) : Icon
+    {
+        return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info');
+    }
+
+    /**
+     * Returns the shape of the icon of this QuestionType
+     * @return string
+     */
+    public static function getIconShape()
+    {
+        return 'likert';
+    }
+
+    public static function getName()
+    {
+        return _('Likert-Skala');
+    }
+
+    static public function getEditingComponent()
+    {
+        return ['likert-edit', ''];
+    }
+
+    public function beforeStoringQuestiondata($questiondata)
+    {
+        $questiondata['description'] = \Studip\Markup::markAsHtml(
+            \Studip\Markup::purifyHtml($questiondata['description'])
+        );
+        $questiondata['statements'] = array_filter($questiondata['statements']);
+        return $questiondata;
+    }
+
+    public function getDisplayTemplate()
+    {
+        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
+        $template = $factory->open('questionnaire/question_types/likert/likert_answer');
+        $template->set_attribute('vote', $this);
+        return $template;
+    }
+
+    public function createAnswer()
+    {
+        $answer = $this->getMyAnswer();
+
+        $answers = Request::getArray('answers');
+        $userAnswer = (array) $answers[$this->getId()]['answerdata']['answers'];
+        $userAnswer = array_map(function ($val) { return (int) $val; }, $userAnswer);
+        $answer->setData(['answerdata' => ['answers' => $userAnswer ] ]);
+        return $answer;
+    }
+
+    public function getUserIdsOfFilteredAnswer($answer_option)
+    {
+        $user_ids = [];
+        list($statement_key, $options_key) = explode('_', $answer_option);
+        foreach ($this->answers as $answer) {
+            $answerData = $answer['answerdata']->getArrayCopy();
+            if ($answerData['answers'][$statement_key] == $options_key) {
+                $user_ids[] = $answer['user_id'];
+            }
+        }
+        return $user_ids;
+    }
+
+    public function getResultTemplate($only_user_ids = null, $filtered = null)
+    {
+        $answers = $this->answers;
+        if ($only_user_ids !== null) {
+            foreach ($answers as $key => $answer) {
+                if (!in_array($answer['user_id'], $only_user_ids)) {
+                    unset($answers[$key]);
+                }
+            }
+        }
+        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
+        $template = $factory->open('questionnaire/question_types/likert/likert_evaluation');
+        $template->set_attribute('vote', $this);
+        $template->set_attribute('answers', $answers);
+        $template->set_attribute('filtered', $filtered);
+        return $template;
+    }
+
+    public function getResultArray()
+    {
+        $output = [];
+
+        $statements = $this['questiondata']['statements']->getArrayCopy();
+
+        foreach ($statements as $statement_key => $statement) {
+            $answerOption = [];
+            $countNobodys = 0;
+
+            foreach ($this->answers as $answer) {
+                $answerData = $answer['answerdata']->getArrayCopy();
+
+                if ($answer['user_id'] && $answer['user_id'] != 'nobody') {
+                    $userId = $answer['user_id'];
+                } else {
+                    $countNobodys++;
+                    $userId = _('unbekannt').' '.$countNobodys;
+                }
+
+                $answerOption[$userId] = $this['questiondata']['options'][$answerData['answers'][$statement_key]];
+            }
+            $output[$statement] = $answerOption;
+        }
+        return $output;
+    }
+}
diff --git a/lib/models/Questionnaire.php b/lib/models/Questionnaire.php
index cd180820b424d1aabda8513c1191b027c6eb221a..f221ca116ce906c0055db4d2e9133ef8e2e2477e 100644
--- a/lib/models/Questionnaire.php
+++ b/lib/models/Questionnaire.php
@@ -8,6 +8,7 @@ class Questionnaire extends SimpleORMap implements PrivacyObject
 
         $config['has_many']['questions'] = [
             'class_name' => QuestionnaireQuestion::class,
+            'order_by' => 'ORDER BY position ASC',
             'on_delete' => 'delete',
             'on_store' => 'store'
         ];
@@ -214,7 +215,8 @@ class Questionnaire extends SimpleORMap implements PrivacyObject
         }
         return $this['resultvisibility'] === "always"
             || $this->isEditable()
-            || ($this['resultvisibility'] === "afterending" && $this->isStopped());
+            || ($this['resultvisibility'] === "afterending" && $this->isStopped())
+            || ($this['resultvisibility'] === 'afterparticipation' && $this->isAnswered());
     }
 
     /**
@@ -236,4 +238,29 @@ class Questionnaire extends SimpleORMap implements PrivacyObject
             }
         }
     }
+
+    /**
+     * Returns all data as an array that could be stored as JSON.
+     * @return array
+     */
+    public function exportAsFile()
+    {
+        $data = [
+            'questionnaire' => [
+                'title' => $this['title'],
+                'anonymous' => $this['anonymous'],
+                'resultvisibility' => $this['resultvisibility'],
+                'editanswers' => $this['editanswers']
+            ],
+            'questions_data' => []
+        ];
+        foreach ($this->questions as $question) {
+            $data['questions_data'][] = [
+                'questiontype' => $question['questiontype'],
+                'internal_name' => $question['internal_name'],
+                'questiondata' => $question['questiondata']->getArrayCopy()
+            ];
+        }
+        return $data;
+    }
 }
diff --git a/lib/models/QuestionnaireAnswer.php b/lib/models/QuestionnaireAnswer.php
index 6e895b63abe9ebf8775731e80140bd850e3669a0..4713b7f72647a7b3c96911ae1bdc596a0eff387f 100644
--- a/lib/models/QuestionnaireAnswer.php
+++ b/lib/models/QuestionnaireAnswer.php
@@ -9,6 +9,10 @@ class QuestionnaireAnswer extends SimpleORMap implements PrivacyObject
         $config['belongs_to']['question'] = [
             'class_name' => QuestionnaireQuestion::class,
         ];
+        $config['belongs_to']['user'] = [
+            'class_name' => User::class,
+            'foreign_key' => 'user_id'
+        ];
         $config['serialized_fields']['answerdata'] = "JSONArrayObject";
 
         parent::configure($config);
diff --git a/lib/models/QuestionnaireInfo.php b/lib/models/QuestionnaireInfo.php
new file mode 100644
index 0000000000000000000000000000000000000000..75965866f7eb46271defa27e6637239a01e40aca
--- /dev/null
+++ b/lib/models/QuestionnaireInfo.php
@@ -0,0 +1,68 @@
+<?php
+require_once 'lib/classes/QuestionType.interface.php';
+
+class QuestionnaireInfo extends QuestionnaireQuestion implements QuestionType
+{
+    public static function getIcon(bool $active = false) : Icon
+    {
+        return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info');
+    }
+
+    /**
+     * Returns the shape of the icon of this QuestionType
+     * @return string
+     */
+    public static function getIconShape()
+    {
+        return 'info-circle';
+    }
+
+    public static function getName()
+    {
+        return _('Information');
+    }
+
+    static public function getEditingComponent()
+    {
+        return ['questionnaire-info-edit', ''];
+    }
+
+    public function beforeStoringQuestiondata($questiondata)
+    {
+        $questiondata['description'] = \Studip\Markup::markAsHtml(
+            \Studip\Markup::purifyHtml($questiondata['description'])
+        );
+        return $questiondata;
+    }
+
+    public function getDisplayTemplate()
+    {
+        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
+        $template = $factory->open('questionnaire/question_types/info/info');
+        $template->set_attribute('vote', $this);
+        return $template;
+    }
+
+    public function createAnswer()
+    {
+
+    }
+
+    public function getUserIdsOfFilteredAnswer($answer_option)
+    {
+        return [];
+    }
+
+    public function getResultTemplate($only_user_ids = null)
+    {
+        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
+        $template = $factory->open('questionnaire/question_types/info/info');
+        $template->set_attribute('vote', $this);
+        return $template;
+    }
+
+    public function getResultArray()
+    {
+        return [];
+    }
+}
diff --git a/lib/models/QuestionnaireQuestion.php b/lib/models/QuestionnaireQuestion.php
index 93c3279b63f38ec2d29f60c2af42bc00530b4ea6..1e3734091f514d631e0bd97c9396e920586b2bd9 100644
--- a/lib/models/QuestionnaireQuestion.php
+++ b/lib/models/QuestionnaireQuestion.php
@@ -17,11 +17,7 @@ class QuestionnaireQuestion extends SimpleORMap
             'on_delete' => 'delete',
             'on_store' => 'store'
         ];
-        $config['belongs_to']['etask'] = [
-            'class_name' => \eTask\Task::class,
-            'foreign_key' => 'etask_task_id'
-        ];
-
+        $config['serialized_fields']['questiondata'] = 'JSONArrayObject';
         parent::configure($config);
 
     }
@@ -38,24 +34,7 @@ class QuestionnaireQuestion extends SimpleORMap
         $data = $statement->fetchAll();
         $questions = [];
         foreach ($data as $questionnaire_data) {
-
-            if (!$task = Task::find($questionnaire_data['etask_task_id'])) {
-                continue;
-            }
-
-            $class = $task->type;
-
-            if ($class === 'multiple-choice') {
-                $totalScore = array_reduce(
-                    isset($task->task['answers']) ? $task->task['answers']->getArrayCopy() : [],
-                    function ($totalScore, $answer) {
-                        return $totalScore + intval($answer['score'] ?: 0);
-                    },
-                    0
-                );
-                $class = $totalScore === 0 ? 'Vote' : 'Test';
-            }
-
+            $class = $questionnaire_data['questiontype'];
             if (class_exists(ucfirst($class))) {
                 $questions[] = $class::buildExisting($questionnaire_data);
             }
diff --git a/lib/models/RangeScale.php b/lib/models/RangeScale.php
new file mode 100644
index 0000000000000000000000000000000000000000..a196462c89ebe673f34bfbe912d39ae5f794fafb
--- /dev/null
+++ b/lib/models/RangeScale.php
@@ -0,0 +1,114 @@
+<?php
+require_once 'lib/classes/QuestionType.interface.php';
+
+class RangeScale extends QuestionnaireQuestion implements QuestionType
+{
+    public static function getIcon(bool $active = false) : Icon
+    {
+        return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info');
+    }
+
+    /**
+     * Returns the shape of the icon of this QuestionType
+     * @return string
+     */
+    public static function getIconShape()
+    {
+        return 'rangescale';
+    }
+
+    public static function getName()
+    {
+        return _('Pol-Skala');
+    }
+
+    public function beforeStoringQuestiondata($questiondata)
+    {
+        $questiondata['description'] = \Studip\Markup::markAsHtml(
+            \Studip\Markup::purifyHtml($questiondata['description'])
+        );
+        $questiondata['statements'] = array_filter($questiondata['statements']);
+        return $questiondata;
+    }
+
+    static public function getEditingComponent()
+    {
+        return ['rangescale-edit', ''];
+    }
+
+    public function getDisplayTemplate()
+    {
+        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
+        $template = $factory->open('questionnaire/question_types/rangescale/rangescale_answer');
+        $template->set_attribute('vote', $this);
+        return $template;
+    }
+
+    public function createAnswer()
+    {
+        $answer = $this->getMyAnswer();
+
+        $answers = Request::getArray('answers');
+        $userAnswer = (array) $answers[$this->getId()]['answerdata']['answers'];
+        $answer->setData(['answerdata' => ['answers' => $userAnswer ] ]);
+        return $answer;
+    }
+
+    public function getUserIdsOfFilteredAnswer($answer_option)
+    {
+        $user_ids = [];
+        list($statement_key, $options_key) = explode('_', $answer_option);
+        foreach ($this->answers as $answer) {
+            $answerData = $answer['answerdata']->getArrayCopy();
+            if ($answerData['answers'][$statement_key] == $options_key) {
+                $user_ids[] = $answer['user_id'];
+            }
+        }
+        return $user_ids;
+    }
+
+    public function getResultTemplate($only_user_ids = null, $filtered = null)
+    {
+        $answers = $this->answers;
+        if ($only_user_ids !== null) {
+            foreach ($answers as $key => $answer) {
+                if (!in_array($answer['user_id'], $only_user_ids)) {
+                    unset($answers[$key]);
+                }
+            }
+        }
+        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
+        $template = $factory->open('questionnaire/question_types/rangescale/rangescale_evaluation');
+        $template->set_attribute('vote', $this);
+        $template->set_attribute('answers', $answers);
+        $template->set_attribute('filtered', $filtered);
+        return $template;
+    }
+
+    public function getResultArray()
+    {
+        $output = [];
+
+        $statements = $this['questiondata']['statements']->getArrayCopy();
+
+        foreach ($statements as $statement_key => $statement) {
+            $answerOption = [];
+            $countNobodys = 0;
+
+            foreach ($this->answers as $answer) {
+                $answerData = $answer['answerdata']->getArrayCopy();
+
+                if ($answer['user_id'] && $answer['user_id'] != 'nobody') {
+                    $userId = $answer['user_id'];
+                } else {
+                    $countNobodys++;
+                    $userId = _('unbekannt').' '.$countNobodys;
+                }
+
+                $answerOption[$userId] = $answerData['answers'][$statement_key];
+            }
+            $output[$statement] = $answerOption;
+        }
+        return $output;
+    }
+}
diff --git a/lib/models/Test.php b/lib/models/Test.php
deleted file mode 100644
index 53e87906121f5b7e8d376588c171aae40e628e91..0000000000000000000000000000000000000000
--- a/lib/models/Test.php
+++ /dev/null
@@ -1,189 +0,0 @@
-<?php
-
-require_once 'lib/classes/QuestionType.interface.php';
-
-use eTask\Task;
-
-class Test extends QuestionnaireQuestion implements QuestionType
-{
-    public static function getIcon($active = false, $add = false)
-    {
-        return Icon::create('test', $active ? 'clickable' : 'info');
-    }
-
-    public static function getName()
-    {
-        return _('Test');
-    }
-
-    public function getEditingTemplate()
-    {
-        $tf = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
-        $template = $tf->open('questionnaire/question_types/test/test_edit');
-        $template->set_attribute('vote', $this);
-        return $template;
-    }
-
-    public function createDataFromRequest()
-    {
-        $questions = Request::getArray('questions');
-        $requestData = $questions[$this->getId()];
-
-        // create a new eTask if this is a new question
-        if (!$this->etask) {
-            $this->etask = Task::create(
-                [
-                    'type' => 'multiple-choice',
-                    'user_id' => $GLOBALS['user']->id,
-                ]
-            );
-        }
-
-        // update description
-        $this->etask->description = Studip\Markup::purifyHtml($requestData['description']);
-
-        // update task's type (single|multiple)
-        $task = [
-            'type' => $requestData['task']['type'] === 'multiple' ? 'multiple' : 'single',
-            'answers' => []
-        ];
-
-        // update task's answers
-        $correct = isset($requestData['task']['correct']) ? $requestData['task']['correct'] : [];
-        foreach ($requestData['task']['answers'] as $index => $text) {
-            $trimmedText = trim($text);
-            if ($trimmedText === '') {
-                continue;
-            }
-
-            $task['answers'][] = [
-                'text' => $trimmedText,
-                'score' => in_array($index + 1, $correct) ? 1 : 0,
-                'feedback' => ''
-            ];
-        }
-
-        $this->etask->task = $task;
-
-        // update randomize option
-        if (isset($requestData['options']['randomize'])) {
-            $options = $this->etask->options;
-            $options['randomize'] = (bool) $requestData['options']['randomize'];
-            $this->etask->options = $options;
-        }
-
-        // store the eTask instance
-        $this->etask->store();
-    }
-
-    public function getDisplayTemplate()
-    {
-        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
-        $template = $factory->open('questionnaire/question_types/vote/vote_answer');
-        $template->set_attribute('vote', $this);
-        return $template;
-    }
-
-    public function createAnswer()
-    {
-        $answer = $this->getMyAnswer();
-
-        $answers = Request::getArray('answers');
-        if (array_key_exists($this->getId(), $answers)) {
-            $userAnswer = $answers[$this->getId()]['answerdata']['answers'];
-            if (is_array($userAnswer)) {
-                $userAnswer = array_map('intval', $userAnswer);
-            }
-            else {
-                $userAnswer = (int) $userAnswer;
-            }
-        }
-        $answer->setData(['answerData' => ['answers' => $userAnswer ] ]);
-        return $answer;
-    }
-
-    public function getResultTemplate($only_user_ids = null)
-    {
-        $answers = $this->answers;
-        if ($only_user_ids !== null) {
-            foreach ($answers as $key => $answer) {
-                if (!in_array($answer['user_id'], $only_user_ids)) {
-                    unset($answers[$key]);
-                }
-            }
-        }
-        $factory = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
-        $template = $factory->open('questionnaire/question_types/test/test_evaluation');
-        $template->set_attribute('vote', $this);
-        $template->set_attribute('answers', $answers);
-        return $template;
-    }
-
-    public function getResultArray()
-    {
-        $output = [];
-
-        $taskAnswers = $this->etask->task['answers'];
-
-        foreach ($taskAnswers as $key => $option) {
-            $answerOption = [];
-            $countNobodys = 0;
-
-            foreach ($this->answers as $answer) {
-                $answerData = $answer['answerdata']->getArrayCopy();
-
-                if ($answer['user_id'] && $answer['user_id'] != 'nobody') {
-                    $userId = $answer['user_id'];
-                } else {
-                    $countNobodys++;
-                    $userId = _('unbekannt').' '.$countNobodys;
-                }
-
-                if (in_array($key, (array) $answerData['answers'])) {
-                    $answerOption[$userId] = 1;
-                } else {
-                    $answerOption[$userId] = 0;
-                }
-            }
-            $output[$option['text']] = $answerOption;
-        }
-        return $output;
-    }
-
-    public function correctAnswered($userId = null,  $answersToCheck = null)
-    {
-        $userId = $userId ?: $GLOBALS['user']->id;
-        $correctAnswered = true;
-        $task = $this->etask->task;
-        $numTaskAnswers = count($task['answers']);
-        $resultsUsers = array_fill(0, $numTaskAnswers, []);
-        if ($answersToCheck && !is_array($answersToCheck)) {
-            $answersToCheck = [$answersToCheck];
-        }
-        $answersToCheck = is_array($answersToCheck) ? $answersToCheck : $this->answers->findBy('user_id', $userId);
-
-        foreach ($answersToCheck as $answer) {
-            if ($task['type'] === 'multiple'  && is_object($answer['answerdata']['answers'])) {
-                foreach ($answer['answerdata']['answers'] as $a) {
-                    $resultsUsers[(int) $a][] = $answer['user_id'];
-                }
-            } else {
-                $resultsUsers[(int) $answer['answerdata']['answers']][] = $answer['user_id'];
-            }
-        }
-        foreach ($task['answers'] as $index => $option) {
-            if ($option['score']) {
-                if (!in_array($userId, $resultsUsers[$index])) {
-                    $correctAnswered = false;
-                    break;
-                }
-            } else {
-                if (in_array($userId, $resultsUsers[$index])) {
-                    $correctAnswered = false;
-                    break;
-                }
-            }
-        }
-        return $correctAnswered;
-    }
-}
diff --git a/lib/models/Vote.php b/lib/models/Vote.php
index 6060f0beb24e99b4a2d944a6be04f587f56e62a3..12c8feed9dc8cb551a8736a3fc3e4f2d691d4c57 100644
--- a/lib/models/Vote.php
+++ b/lib/models/Vote.php
@@ -1,13 +1,20 @@
 <?php
 require_once 'lib/classes/QuestionType.interface.php';
 
-use eTask\Task;
-
 class Vote extends QuestionnaireQuestion implements QuestionType
 {
-    public static function getIcon($active = false, $add = false)
+    public static function getIcon(bool $active = false) : Icon
+    {
+        return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info');
+    }
+
+    /**
+     * Returns the shape of the icon of this QuestionType
+     * @return string
+     */
+    public static function getIconShape()
     {
-        return Icon::create('vote', $active ? 'clickable' : 'info');
+        return 'vote';
     }
 
     public static function getName()
@@ -15,69 +22,18 @@ class Vote extends QuestionnaireQuestion implements QuestionType
         return _('Auswahlfrage');
     }
 
-    public function getEditingTemplate()
+    static public function getEditingComponent()
     {
-        $tf = new Flexi_TemplateFactory(realpath(__DIR__.'/../../app/views'));
-        $template = $tf->open('questionnaire/question_types/vote/vote_edit');
-        $template->set_attribute('vote', $this);
-        return $template;
+        return ['vote-edit', ''];
     }
 
-    public function createDataFromRequest()
+    public function beforeStoringQuestiondata($questiondata)
     {
-        $questions = Request::getArray('questions');
-        $data = $questions[$this->getId()];
-
-        // create a new eTask if this is a new question
-        if (!$this->etask) {
-            $this->etask = Task::create(
-                [
-                    'type' => 'multiple-choice',
-                    'user_id' => $GLOBALS['user']->id,
-                ]
-            );
-        }
-
-        // update description
-        $this->etask->description = Studip\Markup::purifyHtml($data['description']);
-
-        // update task's type (single|multiple)
-        $task = [
-            'type' => $data['task']['type'] === 'multiple' ? 'multiple' : 'single',
-            'answers' => []
-        ];
-
-        // update task's answers
-        foreach ($data['task']['answers'] as $index => $text) {
-            $trimmedText = trim($text);
-            if ($trimmedText === '') {
-                continue;
-            }
-
-            $task['answers'][] = [
-                'text' => $trimmedText,
-                'score' => 0,
-                'feedback' => ''
-            ];
-        }
-
-        $this->etask->task = $task;
-
-        // update randomize option
-        if (isset($data['options']['randomize'])) {
-            $options = $this->etask->options;
-            $options['randomize'] = (bool) $data['options']['randomize'];
-            $this->etask->options = $options;
-        }
-        // update mandatory option
-        if (isset($data['options']['mandatory'])) {
-            $options = $this->etask->options;
-            $options['mandatory'] = (bool) $data['options']['mandatory'];
-            $this->etask->options = $options;
-        }
-
-        // store the eTask instance
-        $this->etask->store();
+        $questiondata['description'] = \Studip\Markup::markAsHtml(
+            \Studip\Markup::purifyHtml($questiondata['description'])
+        );
+        $questiondata['options'] = array_filter($questiondata['options']);
+        return $questiondata;
     }
 
     public function getDisplayTemplate()
@@ -106,7 +62,19 @@ class Vote extends QuestionnaireQuestion implements QuestionType
         return $answer;
     }
 
-    public function getResultTemplate($only_user_ids = null)
+    public function getUserIdsOfFilteredAnswer($answer_option)
+    {
+        $user_ids = [];
+        foreach ($this->answers as $answer) {
+            $answerData = $answer['answerdata']->getArrayCopy();
+            if (in_array($answer_option, (array) $answerData['answers'])) {
+                $user_ids[] = $answer['user_id'];
+            }
+        }
+        return $user_ids;
+    }
+
+    public function getResultTemplate($only_user_ids = null, $filtered = null)
     {
         $answers = $this->answers;
         if ($only_user_ids !== null) {
@@ -120,6 +88,7 @@ class Vote extends QuestionnaireQuestion implements QuestionType
         $template = $factory->open('questionnaire/question_types/vote/vote_evaluation');
         $template->set_attribute('vote', $this);
         $template->set_attribute('answers', $answers);
+        $template->set_attribute('filtered', $filtered);
         return $template;
     }
 
@@ -127,9 +96,9 @@ class Vote extends QuestionnaireQuestion implements QuestionType
     {
         $output = [];
 
-        $taskAnswers = $this->etask->task['answers'];
+        $options = $this['questiondata']['options'] ? $this['questiondata']['options']->getArrayCopy() : [];
 
-        foreach ($taskAnswers as $key => $option) {
+        foreach ($options as $key => $option) {
             $answerOption = [];
             $countNobodys = 0;
 
@@ -149,7 +118,7 @@ class Vote extends QuestionnaireQuestion implements QuestionType
                     $answerOption[$userId] = 0;
                 }
             }
-            $output[$option['text']] = $answerOption;
+            $output[$option] = $answerOption;
         }
         return $output;
     }
diff --git a/lib/modules/CoreAdmin.class.php b/lib/modules/CoreAdmin.class.php
index b307b6f21bfc6dde4c7a10a01962baa9840d32f1..b02cf932e869a0b1a7eb9b028c7bc8b387eed040 100644
--- a/lib/modules/CoreAdmin.class.php
+++ b/lib/modules/CoreAdmin.class.php
@@ -88,7 +88,8 @@ class CoreAdmin extends CorePlugin implements StudipModule
                     $item->setImage(Icon::create('vote'));
                     $item->setDescription(_('Erstellen und bearbeiten von Fragebögen.'));
                     $navigation->addSubNavigation('questionnaires', $item);
-
+                }
+                if (Config::get()->EVAL_ENABLE) {
                     $item = new Navigation(_('Evaluationen'), 'admin_evaluation.php?view=eval_sem');
                     $item->setImage(Icon::create('evaluation'));
                     $item->setDescription(_('Richten Sie fragebogenbasierte Umfragen und Lehrevaluationen ein.'));
diff --git a/lib/modules/EvaluationsWidget.php b/lib/modules/EvaluationsWidget.php
index 3c5cf6fe1690e5aa37607b4d6dbf0cf50008111b..64458d98dde07afc8f611d195bf3a2d34e8729c1 100644
--- a/lib/modules/EvaluationsWidget.php
+++ b/lib/modules/EvaluationsWidget.php
@@ -46,7 +46,9 @@ class EvaluationsWidget extends CorePlugin implements PortalPlugin
         $controller = app(AuthenticatedController::class, ['dispatcher' => app(\Trails_Dispatcher::class)]);
         $controller->suppress_empty_output = true;
 
-        $response = $controller->relay('evaluation/display/studip')->body;
+        if (Config::get()->EVAL_ENABLE) {
+            $response = $controller->relay('evaluation/display/studip')->body;
+        }
 
         $controller->suppress_empty_output = (bool)$response;
         $response .= $controller->relay('questionnaire/widget/start')->body;
diff --git a/lib/navigation/AdminNavigation.php b/lib/navigation/AdminNavigation.php
index b45859c59c7f14186b5c210cc7b13f274d4ec0fe..de3481b331de93abd2138351b564e1bba6e55bd3 100644
--- a/lib/navigation/AdminNavigation.php
+++ b/lib/navigation/AdminNavigation.php
@@ -75,7 +75,7 @@ class AdminNavigation extends Navigation
         $navigation->addSubNavigation('faculty', new Navigation(_('Mitarbeiter'), 'dispatch.php/institute/members?admin_view=1'));
         $navigation->addSubNavigation('groups', new Navigation(_('Funktionen / Gruppen'), 'dispatch.php/admin/statusgroups?type=inst'));
 
-        if (Config::get()->VOTE_ENABLE) {
+        if (Config::get()->EVAL_ENABLE) {
             $navigation->addSubNavigation('evaluation', new Navigation(_('Evaluationen'), 'admin_evaluation.php?view=eval_inst'));
         }
 
diff --git a/lib/navigation/ContentsNavigation.php b/lib/navigation/ContentsNavigation.php
index 8e814ef472898d4d965e7cd47b4146f422bf4fa3..965f598b0b7edcb8892afa6de28f48833ff536ac 100644
--- a/lib/navigation/ContentsNavigation.php
+++ b/lib/navigation/ContentsNavigation.php
@@ -126,7 +126,9 @@ class ContentsNavigation extends Navigation
                 );
                 $questionnaire->addSubNavigation('assign', $sub_nav);
             }
+        }
 
+        if (Config::get()->EVAL_ENABLE) {
             $eval = new Navigation(_('Evaluationen'), 'admin_evaluation.php', ['rangeID' => $GLOBALS['user']->username]);
             $eval->setImage(Icon::create('test'));
             $eval->setDescription(_('Erstellen Sie komplexe Befragungen'));
diff --git a/lib/navigation/StartNavigation.php b/lib/navigation/StartNavigation.php
index e5779a50b418251fe75bea29ece942c3f8a8a318..6f959719c6cf55ca21dfecddcfc1fa7109dd32b1 100644
--- a/lib/navigation/StartNavigation.php
+++ b/lib/navigation/StartNavigation.php
@@ -55,19 +55,6 @@ class StartNavigation extends Navigation
                 $statement->execute(['threshold' => $threshold,
                     ':user_id' => $GLOBALS['user']->id, ':plugin_id' => -1]);
                 $vote = (int) $statement->fetchColumn();
-                $query = "SELECT COUNT(IF(chdate > IFNULL(b.visitdate, :threshold) AND d.author_id != :user_id, a.eval_id, NULL))
-                          FROM eval_range a
-                          INNER JOIN eval d ON (a.eval_id = d.eval_id AND d.startdate < UNIX_TIMESTAMP() AND
-                                            (d.stopdate > UNIX_TIMESTAMP() OR d.startdate + d.timespan > UNIX_TIMESTAMP() OR (d.stopdate IS NULL AND d.timespan IS NULL)))
-                          LEFT JOIN object_user_visits b ON (b.object_id = d.eval_id AND b.user_id = :user_id AND b.plugin_id = :plugin_id)
-                          WHERE a.range_id = 'studip'
-                          GROUP BY a.range_id";
-                $statement = DBManager::get()->prepare($query);
-                $statement->bindValue(':user_id', $GLOBALS['user']->id);
-                $statement->bindValue(':threshold', $threshold);
-                $statement->bindValue(':plugin_id', -2);
-                $statement->execute();
-                $vote += (int)$statement->fetchColumn();
             }
         }
 
@@ -243,6 +230,8 @@ class StartNavigation extends Navigation
 
         if (Config::get()->VOTE_ENABLE) {
             $navigation->addSubNavigation('questionnaire', new Navigation(_('Ankündigungen'), 'dispatch.php/news/admin_news'));
+        }
+        if (Config::get()->EVAL_ENABLE) {
             $navigation->addSubNavigation('evaluation', new Navigation(_('Evaluationen'), 'admin_evaluation.php', ['rangeID' => $auth->auth['uname']]));
         }
 
diff --git a/public/assets/images/icons/black/likert.svg b/public/assets/images/icons/black/likert.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2869129805053c863810a1a542143f168cbddac0
--- /dev/null
+++ b/public/assets/images/icons/black/likert.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g transform="matrix(1.0976,0,0,1.0976,-0.780776,-0.780776)">
+        <path d="M7.25,5.75C7.25,6.164 6.914,6.5 6.5,6.5C6.086,6.5 5.75,6.164 5.75,5.75C5.75,5.336 6.086,5 6.5,5C6.914,5 7.25,5.336 7.25,5.75ZM9.25,5.75C9.25,6.164 8.914,6.5 8.5,6.5C8.086,6.5 7.75,6.164 7.75,5.75C7.75,5.336 8.086,5 8.5,5C8.914,5 9.25,5.336 9.25,5.75ZM11.25,5.75C11.25,6.164 10.914,6.5 10.5,6.5C10.086,6.5 9.75,6.164 9.75,5.75C9.75,5.336 10.086,5 10.5,5C10.914,5 11.25,5.336 11.25,5.75ZM13.25,5.75C13.25,6.164 12.914,6.5 12.5,6.5C12.086,6.5 11.75,6.164 11.75,5.75C11.75,5.336 12.086,5 12.5,5C12.914,5 13.25,5.336 13.25,5.75ZM6.5,11C6.914,11 7.25,10.664 7.25,10.25C7.25,9.836 6.914,9.5 6.5,9.5C6.086,9.5 5.75,9.836 5.75,10.25C5.75,10.664 6.086,11 6.5,11ZM8.5,11C8.914,11 9.25,10.664 9.25,10.25C9.25,9.836 8.914,9.5 8.5,9.5C8.086,9.5 7.75,9.836 7.75,10.25C7.75,10.664 8.086,11 8.5,11ZM10.5,11C10.914,11 11.25,10.664 11.25,10.25C11.25,9.836 10.914,9.5 10.5,9.5C10.086,9.5 9.75,9.836 9.75,10.25C9.75,10.664 10.086,11 10.5,11ZM12.5,11C12.914,11 13.25,10.664 13.25,10.25C13.25,9.836 12.914,9.5 12.5,9.5C12.086,9.5 11.75,9.836 11.75,10.25C11.75,10.664 12.086,11 12.5,11Z" style="fill-rule:nonzero;"/>
+    </g>
+    <g transform="matrix(1.0976,0,0,1.0976,-0.780776,-0.780776)">
+        <path d="M1,5.5C1,4.119 2.119,3 3.5,3L12.5,3C13.881,3 15,4.119 15,5.5L15,10.5C15,11.881 13.881,13 12.5,13L3.5,13C2.119,13 1,11.881 1,10.5L1,5.5ZM3.5,4C2.672,4 2,4.672 2,5.5L2,7.5L4,7.5L4,4L3.5,4ZM5,4L5,7.5L14,7.5L14,5.5C14,4.672 13.328,4 12.5,4L5,4ZM4,8.5L2,8.5L2,10.5C2,11.328 2.672,12 3.5,12L4,12L4,8.5ZM5,12L12.5,12C13.328,12 14,11.328 14,10.5L14,8.5L5,8.5L5,12Z" style="fill-rule:nonzero;"/>
+    </g>
+</svg>
diff --git a/public/assets/images/icons/black/rangescale.svg b/public/assets/images/icons/black/rangescale.svg
new file mode 100644
index 0000000000000000000000000000000000000000..1183e00b40a6faadb2341976150e5cf0f0c53a7a
--- /dev/null
+++ b/public/assets/images/icons/black/rangescale.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 700 700" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M314.72,263.2L211.12,263.2L233.518,240.802C240.237,234.083 240.237,224.001 233.518,217.282C226.799,210.563 216.717,210.563 209.998,217.282L159.6,267.68C152.881,274.399 152.881,284.481 159.6,291.2L209.998,341.598C213.357,344.957 217.279,346.637 221.76,346.637C226.241,346.637 230.158,344.957 233.522,341.598C240.241,334.879 240.241,324.797 233.522,318.078L211.124,295.68L314.724,295.68C323.685,295.68 331.525,288.399 331.525,278.879C331.517,271.039 323.677,263.199 314.716,263.199L314.72,263.2Z" style="fill-rule:nonzero;"/>
+        </g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M490.56,217.84C487.201,214.481 482.72,212.801 478.802,212.801C474.322,212.801 470.404,214.481 467.04,217.84C460.321,224.559 460.321,234.641 467.04,241.36L489.438,263.758L385.278,263.762C376.317,263.762 368.477,271.043 368.477,280.563C368.477,289.524 375.758,297.364 385.278,297.364L488.878,297.364L466.48,319.762C459.761,326.481 459.761,336.563 466.48,343.282C473.199,350.001 483.281,350.001 490,343.282L540.398,292.884C547.117,286.165 547.117,276.083 540.398,269.364L490.56,217.84Z" style="fill-rule:nonzero;"/>
+        </g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M551.604,138.32L148.4,138.32C113.119,138.32 84.002,167.441 84.002,202.718L84.002,357.278C84.002,392.559 113.123,421.676 148.4,421.676L551.6,421.676C586.881,421.676 615.998,392.555 615.998,357.278L616.002,202.718C616.002,167.441 586.881,138.32 551.604,138.32ZM582.397,357.28C582.397,374.639 568.397,388.081 551.596,388.081L148.396,388.077C131.037,388.077 117.595,374.077 117.595,357.276L117.599,202.716C117.599,185.357 131.599,171.915 148.4,171.915L551.6,171.915C568.959,171.915 582.401,185.915 582.401,202.716L582.397,357.28Z" style="fill-rule:nonzero;"/>
+        </g>
+    </g>
+</svg>
diff --git a/public/assets/images/icons/blue/likert.svg b/public/assets/images/icons/blue/likert.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6fe00e910e966cac49d3d08c228dad21b3d17f0f
--- /dev/null
+++ b/public/assets/images/icons/blue/likert.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g transform="matrix(1.0976,0,0,1.0976,-0.780776,-0.780776)">
+        <path d="M7.25,5.75C7.25,6.164 6.914,6.5 6.5,6.5C6.086,6.5 5.75,6.164 5.75,5.75C5.75,5.336 6.086,5 6.5,5C6.914,5 7.25,5.336 7.25,5.75ZM9.25,5.75C9.25,6.164 8.914,6.5 8.5,6.5C8.086,6.5 7.75,6.164 7.75,5.75C7.75,5.336 8.086,5 8.5,5C8.914,5 9.25,5.336 9.25,5.75ZM11.25,5.75C11.25,6.164 10.914,6.5 10.5,6.5C10.086,6.5 9.75,6.164 9.75,5.75C9.75,5.336 10.086,5 10.5,5C10.914,5 11.25,5.336 11.25,5.75ZM13.25,5.75C13.25,6.164 12.914,6.5 12.5,6.5C12.086,6.5 11.75,6.164 11.75,5.75C11.75,5.336 12.086,5 12.5,5C12.914,5 13.25,5.336 13.25,5.75ZM6.5,11C6.914,11 7.25,10.664 7.25,10.25C7.25,9.836 6.914,9.5 6.5,9.5C6.086,9.5 5.75,9.836 5.75,10.25C5.75,10.664 6.086,11 6.5,11ZM8.5,11C8.914,11 9.25,10.664 9.25,10.25C9.25,9.836 8.914,9.5 8.5,9.5C8.086,9.5 7.75,9.836 7.75,10.25C7.75,10.664 8.086,11 8.5,11ZM10.5,11C10.914,11 11.25,10.664 11.25,10.25C11.25,9.836 10.914,9.5 10.5,9.5C10.086,9.5 9.75,9.836 9.75,10.25C9.75,10.664 10.086,11 10.5,11ZM12.5,11C12.914,11 13.25,10.664 13.25,10.25C13.25,9.836 12.914,9.5 12.5,9.5C12.086,9.5 11.75,9.836 11.75,10.25C11.75,10.664 12.086,11 12.5,11Z" style="fill:rgb(35,66,122);fill-rule:nonzero;"/>
+    </g>
+    <g transform="matrix(1.0976,0,0,1.0976,-0.780776,-0.780776)">
+        <path d="M1,5.5C1,4.119 2.119,3 3.5,3L12.5,3C13.881,3 15,4.119 15,5.5L15,10.5C15,11.881 13.881,13 12.5,13L3.5,13C2.119,13 1,11.881 1,10.5L1,5.5ZM3.5,4C2.672,4 2,4.672 2,5.5L2,7.5L4,7.5L4,4L3.5,4ZM5,4L5,7.5L14,7.5L14,5.5C14,4.672 13.328,4 12.5,4L5,4ZM4,8.5L2,8.5L2,10.5C2,11.328 2.672,12 3.5,12L4,12L4,8.5ZM5,12L12.5,12C13.328,12 14,11.328 14,10.5L14,8.5L5,8.5L5,12Z" style="fill:rgb(35,66,122);fill-rule:nonzero;"/>
+    </g>
+</svg>
diff --git a/public/assets/images/icons/blue/rangescale.svg b/public/assets/images/icons/blue/rangescale.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7d0a9bfa5463271b18dcdfd8c92112045f70f307
--- /dev/null
+++ b/public/assets/images/icons/blue/rangescale.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 700 700" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M314.72,263.2L211.12,263.2L233.518,240.802C240.237,234.083 240.237,224.001 233.518,217.282C226.799,210.563 216.717,210.563 209.998,217.282L159.6,267.68C152.881,274.399 152.881,284.481 159.6,291.2L209.998,341.598C213.357,344.957 217.279,346.637 221.76,346.637C226.241,346.637 230.158,344.957 233.522,341.598C240.241,334.879 240.241,324.797 233.522,318.078L211.124,295.68L314.724,295.68C323.685,295.68 331.525,288.399 331.525,278.879C331.517,271.039 323.677,263.199 314.716,263.199L314.72,263.2Z" style="fill:rgb(35,66,122);fill-rule:nonzero;"/>
+        </g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M490.56,217.84C487.201,214.481 482.72,212.801 478.802,212.801C474.322,212.801 470.404,214.481 467.04,217.84C460.321,224.559 460.321,234.641 467.04,241.36L489.438,263.758L385.278,263.762C376.317,263.762 368.477,271.043 368.477,280.563C368.477,289.524 375.758,297.364 385.278,297.364L488.878,297.364L466.48,319.762C459.761,326.481 459.761,336.563 466.48,343.282C473.199,350.001 483.281,350.001 490,343.282L540.398,292.884C547.117,286.165 547.117,276.083 540.398,269.364L490.56,217.84Z" style="fill:rgb(35,66,122);fill-rule:nonzero;"/>
+        </g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M551.604,138.32L148.4,138.32C113.119,138.32 84.002,167.441 84.002,202.718L84.002,357.278C84.002,392.559 113.123,421.676 148.4,421.676L551.6,421.676C586.881,421.676 615.998,392.555 615.998,357.278L616.002,202.718C616.002,167.441 586.881,138.32 551.604,138.32ZM582.397,357.28C582.397,374.639 568.397,388.081 551.596,388.081L148.396,388.077C131.037,388.077 117.595,374.077 117.595,357.276L117.599,202.716C117.599,185.357 131.599,171.915 148.4,171.915L551.6,171.915C568.959,171.915 582.401,185.915 582.401,202.716L582.397,357.28Z" style="fill:rgb(35,66,122);fill-rule:nonzero;"/>
+        </g>
+    </g>
+</svg>
diff --git a/public/assets/images/icons/white/likert.svg b/public/assets/images/icons/white/likert.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6d424678280d3699fadc8abc89969cd5d1f7225b
--- /dev/null
+++ b/public/assets/images/icons/white/likert.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g transform="matrix(1.0976,0,0,1.0976,-0.780776,-0.780776)">
+        <path d="M7.25,5.75C7.25,6.164 6.914,6.5 6.5,6.5C6.086,6.5 5.75,6.164 5.75,5.75C5.75,5.336 6.086,5 6.5,5C6.914,5 7.25,5.336 7.25,5.75ZM9.25,5.75C9.25,6.164 8.914,6.5 8.5,6.5C8.086,6.5 7.75,6.164 7.75,5.75C7.75,5.336 8.086,5 8.5,5C8.914,5 9.25,5.336 9.25,5.75ZM11.25,5.75C11.25,6.164 10.914,6.5 10.5,6.5C10.086,6.5 9.75,6.164 9.75,5.75C9.75,5.336 10.086,5 10.5,5C10.914,5 11.25,5.336 11.25,5.75ZM13.25,5.75C13.25,6.164 12.914,6.5 12.5,6.5C12.086,6.5 11.75,6.164 11.75,5.75C11.75,5.336 12.086,5 12.5,5C12.914,5 13.25,5.336 13.25,5.75ZM6.5,11C6.914,11 7.25,10.664 7.25,10.25C7.25,9.836 6.914,9.5 6.5,9.5C6.086,9.5 5.75,9.836 5.75,10.25C5.75,10.664 6.086,11 6.5,11ZM8.5,11C8.914,11 9.25,10.664 9.25,10.25C9.25,9.836 8.914,9.5 8.5,9.5C8.086,9.5 7.75,9.836 7.75,10.25C7.75,10.664 8.086,11 8.5,11ZM10.5,11C10.914,11 11.25,10.664 11.25,10.25C11.25,9.836 10.914,9.5 10.5,9.5C10.086,9.5 9.75,9.836 9.75,10.25C9.75,10.664 10.086,11 10.5,11ZM12.5,11C12.914,11 13.25,10.664 13.25,10.25C13.25,9.836 12.914,9.5 12.5,9.5C12.086,9.5 11.75,9.836 11.75,10.25C11.75,10.664 12.086,11 12.5,11Z" style="fill:white;fill-rule:nonzero;"/>
+    </g>
+    <g transform="matrix(1.0976,0,0,1.0976,-0.780776,-0.780776)">
+        <path d="M1,5.5C1,4.119 2.119,3 3.5,3L12.5,3C13.881,3 15,4.119 15,5.5L15,10.5C15,11.881 13.881,13 12.5,13L3.5,13C2.119,13 1,11.881 1,10.5L1,5.5ZM3.5,4C2.672,4 2,4.672 2,5.5L2,7.5L4,7.5L4,4L3.5,4ZM5,4L5,7.5L14,7.5L14,5.5C14,4.672 13.328,4 12.5,4L5,4ZM4,8.5L2,8.5L2,10.5C2,11.328 2.672,12 3.5,12L4,12L4,8.5ZM5,12L12.5,12C13.328,12 14,11.328 14,10.5L14,8.5L5,8.5L5,12Z" style="fill:white;fill-rule:nonzero;"/>
+    </g>
+</svg>
diff --git a/public/assets/images/icons/white/rangescale.svg b/public/assets/images/icons/white/rangescale.svg
new file mode 100644
index 0000000000000000000000000000000000000000..27e10d162ec3e2fd774e0fadcba2b8c20b6f8322
--- /dev/null
+++ b/public/assets/images/icons/white/rangescale.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 700 700" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M314.72,263.2L211.12,263.2L233.518,240.802C240.237,234.083 240.237,224.001 233.518,217.282C226.799,210.563 216.717,210.563 209.998,217.282L159.6,267.68C152.881,274.399 152.881,284.481 159.6,291.2L209.998,341.598C213.357,344.957 217.279,346.637 221.76,346.637C226.241,346.637 230.158,344.957 233.522,341.598C240.241,334.879 240.241,324.797 233.522,318.078L211.124,295.68L314.724,295.68C323.685,295.68 331.525,288.399 331.525,278.879C331.517,271.039 323.677,263.199 314.716,263.199L314.72,263.2Z" style="fill:white;fill-rule:nonzero;"/>
+        </g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M490.56,217.84C487.201,214.481 482.72,212.801 478.802,212.801C474.322,212.801 470.404,214.481 467.04,217.84C460.321,224.559 460.321,234.641 467.04,241.36L489.438,263.758L385.278,263.762C376.317,263.762 368.477,271.043 368.477,280.563C368.477,289.524 375.758,297.364 385.278,297.364L488.878,297.364L466.48,319.762C459.761,326.481 459.761,336.563 466.48,343.282C473.199,350.001 483.281,350.001 490,343.282L540.398,292.884C547.117,286.165 547.117,276.083 540.398,269.364L490.56,217.84Z" style="fill:white;fill-rule:nonzero;"/>
+        </g>
+        <g transform="matrix(1.27148,0,0,1.27148,-95.0191,-6.01074)">
+            <path d="M551.604,138.32L148.4,138.32C113.119,138.32 84.002,167.441 84.002,202.718L84.002,357.278C84.002,392.559 113.123,421.676 148.4,421.676L551.6,421.676C586.881,421.676 615.998,392.555 615.998,357.278L616.002,202.718C616.002,167.441 586.881,138.32 551.604,138.32ZM582.397,357.28C582.397,374.639 568.397,388.081 551.596,388.081L148.396,388.077C131.037,388.077 117.595,374.077 117.595,357.276L117.599,202.716C117.599,185.357 131.599,171.915 148.4,171.915L551.6,171.915C568.959,171.915 582.401,185.915 582.401,202.716L582.397,357.28Z" style="fill:white;fill-rule:nonzero;"/>
+        </g>
+    </g>
+</svg>
diff --git a/resources/assets/javascripts/bootstrap/questionnaire.js b/resources/assets/javascripts/bootstrap/questionnaire.js
index 33ead0b1b66ae9f7a31d6503716785a05c3e05e9..4970b64293f5dfcf94c97b298ab840a060668a7e 100644
--- a/resources/assets/javascripts/bootstrap/questionnaire.js
+++ b/resources/assets/javascripts/bootstrap/questionnaire.js
@@ -1,75 +1,8 @@
-jQuery(document).on('paste', '.questionnaire_edit .options > li input', function(ui) {
-    var event = ui.originalEvent;
-    var text = event.clipboardData.getData('text');
-    text = text.split(/[\n\t]/);
-    if (text.length > 1) {
-        if (text[0]) {
-            this.value += text.shift().trim();
-        }
-        var current = jQuery(this).closest('li');
-        for (var i in text) {
-            if (text[i].trim()) {
-                var li = jQuery(
-                    jQuery(this)
-                        .closest('.options')
-                        .data('optiontemplate')
-                );
-                li.find('input:text').val(text[i].trim());
-                li.insertAfter(current);
-                current = li;
-            }
-        }
-        STUDIP.Questionnaire.Test.updateCheckboxValues();
-        event.preventDefault();
-    }
-});
-jQuery(document).on('blur', '.questionnaire_edit .options > li:last-child input:text', function() {
-    if (this.value) {
-        jQuery(this)
-            .closest('.options')
-            .append(
-                jQuery(this)
-                    .closest('.options')
-                    .data('optiontemplate')
-            );
-        jQuery(this)
-            .closest('.options')
-            .find('li:last-child input')
-            .focus();
-    }
-    STUDIP.Questionnaire.Test.updateCheckboxValues();
-});
-jQuery(document).on('click', '.questionnaire_edit .options .delete', function() {
-    var icon = this;
-    STUDIP.Dialog.confirm(
-        jQuery(this)
-            .closest('.questionnaire_edit')
-            .find('.delete_question')
-            .text(),
-        function() {
-            jQuery(icon)
-                .closest('li')
-                .fadeOut(function() {
-                    jQuery(this).remove();
-                    STUDIP.Questionnaire.Test.updateCheckboxValues();
-                });
-        }
-    );
-});
-jQuery(document).on('click', '.questionnaire_edit .options .add', function() {
-    jQuery(this)
-        .closest('.options')
-        .append(
-            jQuery(this)
-                .closest('.options')
-                .data('optiontemplate')
-        );
-    jQuery(this)
-        .closest('.options')
-        .find('li:last-child input:text')
-        .focus();
-    STUDIP.Questionnaire.Test.updateCheckboxValues();
+import {dialogReady, ready} from "../lib/ready";
+STUDIP.ready(() => {
+    STUDIP.Questionnaire.initEditor();
 });
+
 jQuery(document).on('change', '.show_validation_hints .questionnaire_answer [data-question_type=Vote] input', function() {
     STUDIP.Questionnaire.Vote.validator.call($(this).closest("article")[0]);
 });
diff --git a/resources/assets/javascripts/lib/questionnaire.js b/resources/assets/javascripts/lib/questionnaire.js
index d9048693fef512d34fba75c890433d5187571dc4..20c356c6d23de9b9254e4f35e8b0ac46b79e1fd7 100644
--- a/resources/assets/javascripts/lib/questionnaire.js
+++ b/resources/assets/javascripts/lib/questionnaire.js
@@ -1,9 +1,203 @@
 import { $gettext } from '../lib/gettext.js';
+import md5 from 'md5';
+//import html2canvas from "html2canvas";
+//import {jsPDF} from "jspdf";
 
 const Questionnaire = {
     delayedQueue: [],
+    Editor: null,
+    initEditor () {
+        $('.questionnaire_edit').each(function () {
+            STUDIP.Vue.load().then(({createApp}) => {
+                let form = this;
+                let components = {};
+                let questiontypes = $(form).data('questiontypes');
+                for (let i in questiontypes) {
+                    if (questiontypes[i].component[0] && questiontypes[i].component[1]) {
+                        //for plugins to be able to import their vue components:
+                        components[questiontypes[i].component[0]] = () => import(questiontypes[i].component[1]);
+                    }
+                }
+                components.draggable = () => import('vuedraggable');
+                components['vote-edit'] = () => import('../../../vue/components/questionnaires/VoteEdit.vue');
+                components['freetext-edit'] = () => import('../../../vue/components/questionnaires/FreetextEdit.vue');
+                components['likert-edit'] = () => import('../../../vue/components/questionnaires/LikertEdit.vue');
+                components['rangescale-edit'] = () => import('../../../vue/components/questionnaires/RangescaleEdit.vue');
+                components['questionnaire-info-edit'] = () => import('../../../vue/components/questionnaires/QuestionnaireInfoEdit.vue');
+                STUDIP.Questionnaire.Editor = createApp({
+                    el: form,
+                    data() {
+                        return {
+                            questions: $(form).data('questions_data'),
+                            activeTab: 'admin',
+                            hoverTab: null,
+                            questiontypes: questiontypes,
+                            data: $(form).data('questionnaire_data'),
+                            askForDeletingQuestions: false,
+                            whatQuestionIndexShouldBeDeleted: null,
+                            form_secured: true,
+                            oldData: {
+                                questions: [],
+                                data: {}
+                            },
+                            range_type: $(form).data('range_type'),
+                            range_id: $(form).data('range_id'),
+                            editInternalName: null,
+                            tempInternalName: ''
+                        };
+                    },
+                    methods: {
+                        addQuestion: function (questiontype) {
+                            let id = md5(STUDIP.USER_ID + '_QUESTIONTYPE_' + Math.random());
+                            this.questions.push({
+                                id: id,
+                                questiontype: questiontype,
+                                internal_name: '',
+                                questiondata: {},
+                            });
+                            this.activeTab = id;
+                        },
+                        submit: function () {
+                            let data = {
+                                title: this.data.title,
+                                copyable: this.data.copyable,
+                                anonymous: this.data.anonymous,
+                                editanswers: this.data.editanswers,
+                                startdate: this.data.startdate,
+                                stopdate: this.data.stopdate,
+                                resultvisibility: this.data.resultvisibility
+                            };
+                            let questions = [];
+                            for (let i in this.questions) {
+                                questions.push({
+                                    id: this.questions[i].id,
+                                    questiontype: this.questions[i].questiontype,
+                                    internal_name: this.questions[i].internal_name,
+                                    questiondata: Object.assign({}, this.questions[i].questiondata),
+                                });
+                            }
+                            let v = this;
+                            $.ajax({
+                                url: STUDIP.URLHelper.getURL(STUDIP.ABSOLUTE_URI_STUDIP + 'dispatch.php/questionnaire/store/' + (this.data.id || '')),
+                                data: {
+                                    questionnaire: data,
+                                    questions_data: questions,
+                                    range_type: this.range_type,
+                                    range_id: this.range_id
+                                },
+                                type: 'post',
+                                success: function () {
+                                    v.form_secured = false;
+                                    v.$nextTick(function () {
+                                        location.reload();
+                                    });
+                                },
+                                error: function () {
+                                    window.alert('Could not save questionnaire.');
+                                }
+                            });
+                        },
+                        getIndexForQuestion: function (question_id) {
+                            for (let i in this.questions) {
+                                if (this.questions[i].id === question_id || this.questions[i].id === question_id.substring(5)) {
+                                    return typeof i === "string" ? parseInt(i, 10) : i;
+                                }
+                            }
+                        },
+                        duplicateQuestion: function (question_id) {
+                            let i = this.getIndexForQuestion(question_id);
+                            let id = md5(STUDIP.USER_ID + '_QUESTIONTYPE_' + Math.random());
+                            this.questions.push({
+                                id: id,
+                                questiontype: this.questions[i].questiontype,
+                                internal_name: this.questions[i].internal_name,
+                                questiondata: Object.assign({}, this.questions[i].questiondata)
+                            });
+                            this.activeTab = id;
+                        },
+                        askForDeletingTheQuestion: function (question_id) {
+                            this.askForDeletingQuestions = true;
+                            this.whatQuestionIndexShouldBeDeleted = this.getIndexForQuestion(question_id);
+                        },
+                        deleteQuestion: function () {
+                            this.$delete(this.questions, this.whatQuestionIndexShouldBeDeleted);
+                            this.switchTab('add_question');
+                            this.askForDeletingQuestions = false;
+                        },
+                        switchTab: function (tab_id) {
+                            this.activeTab = tab_id;
+                            this.$nextTick(function () {
+                                if (typeof this.$refs.autofocus !== "undefined") {
+                                    if (Array.isArray(this.$refs.autofocus)) {
+                                        if (typeof this.$refs.autofocus[0] !== "undefined") {
+                                            this.$refs.autofocus[0].focus();
+                                        }
+                                    } else {
+                                        this.$refs.autofocus.focus();
+                                    }
+                                }
+                            });
+                        },
+                        objectsEqual: function (obj1, obj2) {
+                            return _.isEqual(obj1, obj2);
+                        },
+                        renameInternalName: function (question_id) {
+                            this.editInternalName = question_id;
+                            let index = this.getIndexForQuestion(question_id);
+                            this.tempInternalName = this.questions[index].internal_name;
+                            this.$nextTick(function () {
+                                this.$refs.editInternalName[0].focus();
+                            });
+                        },
+                        saveInternalName: function (question_id) {
+                            let index = this.getIndexForQuestion(question_id);
+                            this.questions[index].internal_name = this.tempInternalName;
+                            this.editInternalName = null;
+                        },
+                        moveQuestionDown: function (question_id) {
+                            let index = this.getIndexForQuestion(question_id);
+                            if (index < this.questions.length - 1) {
+                                let question = this.questions[index];
+                                this.questions[index] = this.questions[index + 1];
+                                this.questions[index + 1] = question;
+                                this.$forceUpdate();
+                            }
+                        },
+                        moveQuestionUp: function (question_id) {
+                            let index = this.getIndexForQuestion(question_id);
+                            if (index > 0) {
+                                let question = this.questions[index];
+                                this.questions[index] = this.questions[index - 1];
+                                this.questions[index - 1] = question;
+                                this.$forceUpdate();
+                            }
+                        }
+                    },
+                    computed: {
+                        activateFormSecure: function () {
+                            let newData = {
+                                'questions': this.questions,
+                                'data': this.data
+                            };
+                            return this.form_secured && !this.objectsEqual(this.oldData, newData);
+                        }
+                    },
+                    mounted: function () {
+                        this.$refs.autofocus.focus();
+                        this.oldData = {
+                            'questions': [...this.questions],
+                            'data': Object.assign({}, this.data)
+                        };
+                    },
+                    components: components
+                });
+
+            });
+        });
+    },
     delayedInterval: null,
     lastUpdate: null,
+    filtered: {},
     initialize() {
         STUDIP.JSUpdater.register(
             'questionnaire',
@@ -15,7 +209,8 @@ const Questionnaire = {
     getParamsForPolling: function() {
         var questionnaires = {
             questionnaire_ids: [],
-            last_update: Questionnaire.lastUpdate
+            last_update: Questionnaire.lastUpdate,
+            filtered: Questionnaire.filtered
         };
         Questionnaire.lastUpdate = Math.floor(Date.now() / 1000);
         jQuery('.questionnaire_results').each(function() {
@@ -34,6 +229,35 @@ const Questionnaire = {
             }
         }
     },
+    addFilter: function (questionnaire_id, question_id, answer) {
+        Questionnaire.filtered[questionnaire_id] = {
+            question_id: question_id,
+            filterForAnswer: answer
+        };
+        $.ajax({
+            url: STUDIP.URLHelper.getURL(STUDIP.ABSOLUTE_URI_STUDIP + 'dispatch.php/questionnaire/evaluate/' + questionnaire_id),
+            data: {
+                filtered: {
+                    question_id: question_id,
+                    filterForAnswer: answer
+                }
+            },
+            success: Questionnaire.updateWidgetQuestionnaire,
+            error: function () {
+                window.alert('Cannot load page.');
+            }
+        });
+    },
+    removeFilter: function (questionnaire_id) {
+        delete Questionnaire.filtered[questionnaire_id];
+        $.ajax({
+            url: STUDIP.URLHelper.getURL(STUDIP.ABSOLUTE_URI_STUDIP + 'dispatch.php/questionnaire/evaluate/' + questionnaire_id),
+            success: Questionnaire.updateWidgetQuestionnaire,
+            error: function () {
+                window.alert('Cannot load page.');
+            }
+        });
+    },
     updateOverviewQuestionnaire: function(data) {
         if (jQuery('#questionnaire_overview tr#questionnaire_' + data.questionnaire_id).length > 0) {
             jQuery('#questionnaire_overview tr#questionnaire_' + data.questionnaire_id).replaceWith(data.overview_html);
@@ -90,7 +314,10 @@ const Questionnaire = {
     updateWidgetQuestionnaire: function(html) {
         //update the results of a questionnaire
         var questionnaire_id = jQuery(html).data('questionnaire_id');
-        jQuery('.questionnaire_widget .questionnaire_' + questionnaire_id).replaceWith(html);
+        jQuery('.questionnaire_' + questionnaire_id).replaceWith(html);
+        if (jQuery('.questionnaire_' + questionnaire_id).is('.ui-dialog .questionnaire_results')) {
+            jQuery('.questionnaire_' + questionnaire_id + ' [data-dialog-button]').hide();
+        }
         jQuery(document).trigger('dialog-open');
     },
     beforeAnswer: function() {
@@ -168,38 +395,100 @@ const Questionnaire = {
             return true;
         }
     },
-    addQuestion: function(questiontype) {
-        jQuery.ajax({
-            url: STUDIP.ABSOLUTE_URI_STUDIP + 'dispatch.php/questionnaire/add_question',
-            data: {
-                questiontype: questiontype
-            },
-            dataType: 'json',
-            success: function(output) {
-                var order = JSON.parse(jQuery("input[name=order]").val());
-                order.push(output.question_id);
-                jQuery("input[name=order]").val(JSON.stringify(order));
-                jQuery(output.html)
-                    .hide()
-                    .appendTo('.questionnaire_edit .all_questions')
-                    .show('fade');
+    LikertScale: {
+        validator: function () {
+            if ($(this).find(".mandatory").length > 0) {
+                let invalid = false;
+                $(this).find('table.answers tbody tr').each(function () {
+                    if ($(this).find(':checked').length === 0) {
+                        invalid = true;
+                    }
+                });
+                if (invalid) {
+                    $(this).find(".invalidation_notice").addClass("invalid");
+                    return false;
+                } else {
+                    $(this).find(".invalidation_notice").removeClass("invalid");
+                }
             }
-        });
+            return true;
+        }
     },
-    moveQuestionUp: function () {
-        let thisquestion = jQuery(this).closest(".question");
-        let upper = thisquestion.prev();
-        thisquestion.insertBefore(upper);
-        upper.hide().fadeIn();
-        thisquestion.hide().fadeIn();
+    RangeScale: {
+        validator: function () {
+            return Questionnaire.LikertScale.validator.call(this);
+        }
     },
-    moveQuestionDown: function () {
-        let thisquestion = jQuery(this).closest(".question");
-        let downer = thisquestion.next();
-        thisquestion.insertAfter(downer);
-        downer.hide().fadeIn();
-        thisquestion.hide().fadeIn();
+
+
+    exportEvaluationAsPDF: function () {
+        window.scrollTo(0, 0);
+        const html2canvas = import('html2canvas');
+        const jsPDF = import('jspdf');
+        jsPDF.then(function (jsPDF) {
+            let pdfExporter = jsPDF.default;
+            html2canvas.then(function (canvas) {
+                let canvasCreator = canvas.default;
+
+                let pdf = new pdfExporter({
+                    orientation: 'portrait'
+                });
+                $(".questionnaire_results").addClass('print-view');
+
+                let title = $(".questionnaire_results").data('title');
+
+                let splitTitle = pdf.splitTextToSize(title, 180);
+                pdf.text(splitTitle, 25, 20);
+
+                let count_questions = $(".questionnaire_results .question").length;
+                let questions_rendered = 0;
+                let canvasses = [];
+
+                let blobToDataURL = function (blob, callback) {
+                    let a = new FileReader();
+                    a.onload = function(e) {callback(e.target.result);}
+                    a.readAsDataURL(blob);
+                };
+
+                $(".questionnaire_results .question").each(function (index) {
+                    canvasCreator(this, {logging: false}).then(canvas => {
+                        canvasses[index] = canvas;
+                        questions_rendered++;
+                        if (questions_rendered === count_questions) {
+                            //then all renders are finished:
+                            let height_sum = 0;
+                            for (let i = 0; i < count_questions; i++) {
+                                if (i === 0) {
+                                    height_sum += 15;
+                                }
+                                let imgData = canvasses[i].toDataURL('image/png');
+
+                                let height = Math.floor(160 / canvasses[i].width * canvasses[i].height);
+                                if (height_sum + height > 240 && height < 240) {
+                                    pdf.addPage();
+                                    height_sum = 0;
+                                }
+                                pdf.addImage(imgData, 'JPEG',
+                                    25,
+                                    20 + height_sum,
+                                    160,
+                                    height,
+                                    'image_' + i,
+                                    'NONE',
+
+                                );
+                                height_sum += height + 10;
+                            }
+                            pdf.save(title + '.pdf');
+                        }
+                    });
+                });
+                $(".questionnaire_results").removeClass('print-view');
+            })
+        });
+
     },
+
     addDelayedInit(el, data, isAjax, isMultiple) {
         this.delayedQueue.push({
             el,
diff --git a/resources/assets/stylesheets/scss/buttons.scss b/resources/assets/stylesheets/scss/buttons.scss
index 88b208941a8a0a5f2f575a89b8d53669c95a068a..05f2a26e504e2579bb272d6f3e2add249f340959 100644
--- a/resources/assets/stylesheets/scss/buttons.scss
+++ b/resources/assets/stylesheets/scss/buttons.scss
@@ -123,7 +123,18 @@ button.button {
     }
 }
 
+
 button.styleless {
     background-color: unset;
     border: 0;
 }
+button.as-link {
+    border: none;
+    padding: 0px;
+    margin: 0px;
+    cursor: pointer;
+    background-color: transparent;
+    &:hover {
+        background-color: transparent;
+    }
+}
diff --git a/resources/assets/stylesheets/scss/questionnaire.scss b/resources/assets/stylesheets/scss/questionnaire.scss
index da2a64c03b17f6db2313a3b0857bd8a8ac983cfb..32ecefb5be9b1fdbd8997a10e562d7194603824f 100644
--- a/resources/assets/stylesheets/scss/questionnaire.scss
+++ b/resources/assets/stylesheets/scss/questionnaire.scss
@@ -1,4 +1,158 @@
+$width: 270px;
+
 .questionnaire_edit {
+
+
+    .editor {
+        display: flex;
+        flex-direction: row-reverse;
+        align-items: stretch;
+        width: 100%;
+        aside {
+            background: $white;
+            border: 1px solid $content-color-40;
+            min-width: $width;
+            width: $width;
+            .questions_container {
+                padding: 0px;
+                .questions {
+                    display: flex;
+                    flex-direction: column;
+                }
+            }
+
+            > .admin, > .add_question, .questions > * {
+                width: calc(100% - 8px);
+                padding: 4px;
+                border-bottom: 1px solid $content-color-40;
+                min-height: 40px;
+                display: flex;
+                justify-content: start;
+                align-items: center;
+                position: relative;
+                > .icon {
+                    width: 30px;
+                    height: 30px;
+                    margin-right: 10px;
+                    margin-left: 7px;
+                }
+                &.active {
+                    background-color: $yellow-40;
+
+                    &::before {
+                        content: '';
+                        position: absolute;
+                        height: 0px;
+                        width: 0px;
+                        border-top: 25px transparent solid;
+                        border-bottom: 25px transparent solid;
+                        border-left: 7px $content-color-40 solid;
+                        right: -8px;
+                    }
+                    &::after {
+                        content: '';
+                        position: absolute;
+                        height: 0px;
+                        width: 0px;
+                        border-top: 25px transparent solid;
+                        border-bottom: 25px transparent solid;
+                        border-left: 7px $yellow-40 solid;
+                        right: -7px;
+                    }
+                }
+            }
+            .questions {
+                display: flex;
+                justify-content: start;
+                align-items: center;
+                flex-direction: column;
+                > * {
+                    display: flex;
+                    flex-direction: row;
+                    justify-content: space-between;
+                    > :first-child {
+                        width: 100%;
+                        overflow: hidden;
+                    }
+                }
+                a {
+                    display: flex;
+                    align-items: center;
+                    .icon {
+                        width: 30px;
+                        height: 30px;
+                        margin-right: 10px;
+                        margin-left: 7px;
+                        cursor: grabbing;
+                    }
+                }
+
+            }
+
+        }
+        .rightside {
+            border: 1px solid $content-color-40;
+            border-left: none;
+            width: 100%;
+            padding: 10px;
+            padding-left: 15px;
+            min-height: 150px;
+        }
+        .vote_edit {
+            .options {
+                > li {
+                    display: flex;
+                    align-items: center;
+                    > * {
+                        margin-right: 10px;
+                    }
+                }
+            }
+        }
+        .rangescale_edit table.default > thead > tr > th.number {
+            padding-left: 12px;
+        }
+
+        .dragcolumn {
+            max-width: 8px;
+        }
+
+        .dragarea {
+            cursor: grabbing;
+        }
+
+        .input-array {
+            margin-left: 4px;
+        }
+
+        .likert_edit .input-array {
+            margin-left: 7px;
+        }
+        .inline_editing {
+            width: 100%;
+            display: flex;
+            align-items: center;
+            input {
+                width: calc(100% - 74px);
+                border: 1px solid $light-gray-color-40;
+            }
+            button {
+                border: 1px solid $light-gray-color-40;
+                width: 32px;
+                height: 32px;
+                padding: 6px;
+                margin-left: 5px;
+                background-color: white;
+                cursor: pointer;
+                display: inline-flex;
+                align-items: center;
+                justify-items: center;
+            }
+        }
+    }
+
+    /* ab hier der alte kram */
+
     section {
         border: thin solid $black;
         margin: 3px;
@@ -149,17 +303,39 @@
 }
 
 
-.questionnaire_answer > article {
-    padding: 7px;
-    border: none;
-    border-top: 1px solid $content-color-40;
-
-    > :first-child {
-        margin-top: 0px;
+.questionnaire_answer, .questionnaire_results {
+    .description_container {
+        display: flex;
+        > .icon_container {
+            width: 30px;
+            height: 30px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin-right: 3px;
+            margin-bottom: 10px;
+        }
+        > .description {
+            margin-top: 6px;
+            width: 100%;
+            iframe {
+                width: 100%;
+                height: 400px;
+                border: none;
+            }
+        }
     }
+    > article {
+        padding: 7px;
+        border: none;
 
-    .invalidation_notice {
-        color: $red;
+        > :first-child {
+            margin-top: 0px;
+        }
+
+        .invalidation_notice {
+            color: $red;
+        }
     }
 }
 
diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss
index 7226eeabace622b84ae518904741ba34e8db9d67..d294ae52dbf879f0a266ddb56442c47537bb4a87 100644
--- a/resources/assets/stylesheets/studip.scss
+++ b/resources/assets/stylesheets/studip.scss
@@ -124,3 +124,16 @@
 }
 
 div.indent { margin-left: 2em; }
+
+.input-array {
+    .options > li {
+        display: flex;
+        align-items: center;
+        > * {
+            margin-right: 10px;
+            .dragarea {
+                cursor: grabbing;
+            }
+        }
+    }
+}
diff --git a/resources/vue/components/questionnaires/FreetextEdit.vue b/resources/vue/components/questionnaires/FreetextEdit.vue
new file mode 100644
index 0000000000000000000000000000000000000000..29c6f34b81781a832e11a7238ff41db6bc6a5f21
--- /dev/null
+++ b/resources/vue/components/questionnaires/FreetextEdit.vue
@@ -0,0 +1,52 @@
+<template>
+    <div>
+        <div class="formpart" tabindex="0" ref="autofocus">
+            {{ $gettext('Frage') }}
+            <studip-wysiwyg v-model="val_clone.description" :key="question_id"></studip-wysiwyg>
+        </div>
+
+        <label>
+            <input type="checkbox" v-model="val_clone.mandatory">
+            {{ $gettext('Pflichtfrage') }}
+        </label>
+    </div>
+</template>
+
+<script>
+import StudipWysiwyg from "../StudipWysiwyg.vue";
+
+export default {
+    name: 'freetext-edit',
+    components: {
+        StudipWysiwyg
+    },
+    props: {
+        value: {
+            type: Object,
+            required: false,
+            default: function () {
+                return {};
+            }
+        },
+        question_id: {
+            type: String,
+            required: false
+        }
+    },
+    data: function () {
+        return {
+            val_clone: ''
+        };
+    },
+    mounted: function () {
+        this.val_clone = this.value;
+        this.$refs.autofocus.focus();
+    },
+    watch: {
+        value (new_val) {
+            this.val_clone = new_val;
+        }
+    }
+
+}
+</script>
diff --git a/resources/vue/components/questionnaires/InputArray.vue b/resources/vue/components/questionnaires/InputArray.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ab2249f59b3f17a2b870115e0e72093945804cc8
--- /dev/null
+++ b/resources/vue/components/questionnaires/InputArray.vue
@@ -0,0 +1,177 @@
+<template>
+    <div class="input-array">
+        <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
+        <draggable v-model="options" handle=".dragarea" tag="ol" class="clean options">
+            <li v-for="(option, index) in options" :key="index">
+                <a class="dragarea"
+                   v-if="options.length > 1"
+                   tabindex="0"
+                   :ref="'draghandle_' + index"
+                   :title="$gettextInterpolate('Sortierelement für Option %{option}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {option: option})"
+                   @keydown="keyHandler($event, index)">
+                    <studip-icon shape="hamburger" role="clickable" alt=""></studip-icon>
+                </a>
+                <input type="text"
+                       :placeholder="$gettext('Option')"
+                       :ref="'option_' + index"
+                       @paste="(ev) => onPaste(ev, index)"
+                       v-model="options[index]">
+                <button class="as-link"
+                   :title="$gettext('Option löschen')"
+                   @click.prevent="askForDeletingOption(index)">
+                    <studip-icon shape="trash" role="clickable" size="20" alt=""></studip-icon>
+                </button>
+                <button v-if="index == options.length - 1"
+                   class="as-link"
+                   :title="$gettext('Option hinzufügen')"
+                   @click.prevent="addOption">
+                    <studip-icon shape="add" role="clickable" size="20" alt=""></studip-icon>
+                </button>
+            </li>
+        </draggable>
+
+        <studip-dialog
+            v-if="askForDeleting"
+            :title="$gettext('Bitte bestätigen Sie die Aktion.')"
+            :question="$gettext('Wirklich löschen?')"
+            :confirmText="$gettext('Ja')"
+            :closeText="$gettext('Nein')"
+            closeClass="cancel"
+            height="180"
+            @confirm="deleteOption"
+            @close="askForDeleting = false"
+        >
+        </studip-dialog>
+    </div>
+</template>
+
+<script>
+import StudipIcon from "../StudipIcon.vue";
+import StudipDialog from "../StudipDialog.vue";
+import draggable from 'vuedraggable';
+export default {
+    name: 'input-array',
+    components: {
+        StudipIcon,
+        StudipDialog,
+        draggable
+    },
+    props: {
+        value: {
+            type: Array,
+            required: false
+        }
+    },
+    data: function () {
+        return {
+            options: [],
+            askForDeleting: false,
+            indexOfDeletingOption: 0,
+            unique_id: null,
+            assistiveLive: ''
+        };
+    },
+    methods: {
+        addOption: function (val, position) {
+            let data = this.value;
+            if (val.target) {
+                val = '';
+            }
+            if (typeof position === "undefined") {
+                data.push(val || '');
+                position = this.value.length - 1
+            } else {
+                data.splice(position, 0, val || '');
+            }
+            this.$emit('input', data);
+            let v = this;
+            this.$nextTick(function () {
+                v.$refs['option_' + position][0].focus();
+            });
+        },
+        askForDeletingOption: function (index) {
+            this.indexOfDeletingOption = index;
+            if (this.value[index]) {
+                this.askForDeleting = true;
+            } else {
+                this.deleteOption();
+            }
+        },
+        deleteOption: function () {
+            this.$delete(this.value, this.indexOfDeletingOption);
+            this.askForDeleting = false;
+        },
+        onPaste: function (ev, position) {
+            let data = ev.clipboardData.getData("text").split("\n");
+            for (let i = 0; i < data.length; i++) {
+                if (data[i].trim()) {
+                    this.addOption(data[i], position + i);
+                }
+            }
+        },
+        keyHandler(e, index) {
+            switch (e.keyCode) {
+                case 38: // up
+                    e.preventDefault();
+                    if (index > 0) {
+                        this.moveUp(index);
+                        this.$nextTick(function () {
+                            this.$refs['draghandle_' + (index - 1)][0].focus();
+                            this.assistiveLive = this.$gettextInterpolate(
+                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
+                                , {pos: index, listLength: this.options.length}
+                            );
+                        });
+                    }
+                    break;
+                case 40: // down
+                    e.preventDefault();
+                    if (index < this.options.length - 1) {
+                        this.moveDown(index);
+                        this.$nextTick(function () {
+                            this.$refs['draghandle_' + (index + 1)][0].focus();
+                            this.assistiveLive = this.$gettextInterpolate(
+                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
+                                , {pos: index + 2, listLength: this.options.length}
+                            );
+                        });
+                    }
+                    break;
+            }
+        },
+        moveDown: function (index) {
+            if (index == this.options.length - 1) {
+                return;
+            }
+            let option = this.options[index];
+            this.options[index] = this.options[index + 1];
+            this.options[index + 1] = option;
+            this.$forceUpdate();
+        },
+        moveUp: function (index) {
+            if (index === 0) {
+                return;
+            }
+            let option = this.options[index];
+            this.options[index] = this.options[index - 1];
+            this.options[index - 1] = option;
+            this.$forceUpdate();
+        }
+    },
+    mounted: function () {
+        this.options = this.value;
+        this.unique_id = 'array_input_' + Math.floor(Math.random() * 100000000);
+    },
+    watch: {
+        options (new_data, old_data) {
+            if (typeof old_data === 'undefined' || typeof new_data === 'undefined') {
+                return;
+            }
+            this.$emit('input', new_data);
+        },
+        value (new_val) {
+            this.options = new_val;
+        }
+    }
+}
+</script>
diff --git a/resources/vue/components/questionnaires/LikertEdit.vue b/resources/vue/components/questionnaires/LikertEdit.vue
new file mode 100644
index 0000000000000000000000000000000000000000..413b34e816a1e34114574bbb0d06a1bc7727d41c
--- /dev/null
+++ b/resources/vue/components/questionnaires/LikertEdit.vue
@@ -0,0 +1,225 @@
+<template>
+    <div class="likert_edit">
+
+        <div class="formpart" tabindex="0" ref="autofocus">
+            {{$gettext('Einleitungstext')}}
+            <studip-wysiwyg v-model="val_clone.description"></studip-wysiwyg>
+        </div>
+
+        <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
+
+        <table class="default nohover">
+            <thead>
+                <tr>
+                    <th class="dragcolumn"></th>
+                    <th>{{ $gettext('Aussagen') }}</th>
+                    <th v-for="(option, index) in val_clone.options" :key="index">{{ option }}</th>
+                    <th class="actions"></th>
+                </tr>
+            </thead>
+            <draggable v-model="val_clone.statements" handle=".dragarea" tag="tbody" class="statements">
+                <tr v-for="(statement, index) in val_clone.statements" :key="index">
+                    <td class="dragcolumn">
+                        <a class="dragarea"
+                           tabindex="0"
+                           :title="$gettextInterpolate('Sortierelement für Aussage %{statement}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {statement: statement})"
+                           @keydown="keyHandler($event, index)"
+                           :ref="'draghandle_' + index">
+                            <studip-icon shape="hamburger" role="clickable"></studip-icon>
+                        </a>
+                    </td>
+                    <td>
+                        <input type="text"
+                               :ref="'statement_' + index"
+                               :placeholder="$gettext('Aussage')"
+                               @paste="(ev) => onPaste(ev, index)"
+                               v-model="val_clone.statements[index]">
+                    </td>
+                    <td v-for="(option, index2) in val_clone.options" :key="index2">
+                        <input type="radio" value="1" disabled :title="option">
+                    </td>
+                    <td class="actions">
+                        <button class="as-link"
+                           @click.prevent="askForDeletingStatement(index)"
+                           :title="$gettext('Aussage löschen')">
+                            <studip-icon shape="trash" role="clickable" size="20" alt=""></studip-icon>
+                        </button>
+                    </td>
+                </tr>
+            </draggable>
+            <tfoot>
+                <tr>
+                    <td :colspan="typeof val_clone.options !== 'undefined' ? val_clone.options.length + 3 : 3">
+                        <button @click.prevent="addStatement" class="as-link" :title="$gettext('Aussage hinzufügen')">
+                            <studip-icon shape="add" role="clickable" size="20" alt=""></studip-icon>
+                        </button>
+                    </td>
+                </tr>
+            </tfoot>
+        </table>
+
+        <label>
+            <input type="checkbox" v-model.number="val_clone.mandatory" true-value="1" false-value="0">
+            {{ $gettext('Pflichtfrage') }}
+        </label>
+        <label>
+            <input type="checkbox" v-model.number="val_clone.randomize" true-value="1" false-value="0">
+            {{ $gettext('Antworten den Teilnehmenden zufällig präsentieren') }}
+        </label>
+
+        <div>
+            <div>{{ $gettext('Antwortmöglichkeiten konfigurieren') }}</div>
+            <input-array v-model="val_clone.options"></input-array>
+        </div>
+
+        <studip-dialog
+            v-if="askForDeleting"
+            :title="$gettext('Bitte bestätigen Sie die Aktion.')"
+            :question="$gettext('Wirklich löschen?')"
+            :confirmText="$gettext('Ja')"
+            :closeText="$gettext('Nein')"
+            closeClass="cancel"
+            height="180"
+            @confirm="deleteStatement"
+            @close="askForDeleting = false"
+        >
+        </studip-dialog>
+    </div>
+</template>
+
+<script>
+import StudipIcon from "../StudipIcon.vue";
+import StudipDialog from "../StudipDialog.vue";
+import draggable from 'vuedraggable';
+import StudipWysiwyg from "../StudipWysiwyg.vue";
+import InputArray from "./InputArray.vue";
+import { $gettext } from '../../../assets/javascripts/lib/gettext.js';
+const default_value = {
+    statements: ['', '', '', ''],
+        options: [$gettext('trifft zu'), $gettext('trifft eher zu'), $gettext('teils-teils'), $gettext('trifft eher nicht zu'), $gettext('trifft nicht zu')]
+};
+export default {
+    name: 'likert-edit',
+    components: {
+        StudipWysiwyg,
+        StudipIcon,
+        StudipDialog,
+        draggable,
+        InputArray
+    },
+    props: {
+        value: {
+            type: Object,
+            required: false,
+            default: function () {
+                return default_value;
+            }
+        },
+        question_id: {
+            type: String,
+            required: false
+        }
+    },
+    data: function () {
+        return {
+            val_clone: {},
+            askForDeleting: false,
+            indexOfDeletingStatement: 0,
+            assistiveLive: ''
+        };
+    },
+    methods: {
+        addStatement: function (val, position) {
+            if (val.target) {
+                val = '';
+            }
+            let data = this.value;
+            if (typeof position === "undefined") {
+                data.statements.push(val || '');
+                position = this.value.length - 1
+            } else {
+                data.statements.splice(position, 0, val || '');
+            }
+            this.$emit('input', data);
+            let v = this;
+            this.$nextTick(function () {
+                v.$refs['statement_' + (v.value.statements.length - 1)][0].focus();
+            });
+        },
+        askForDeletingStatement: function (index) {
+            this.indexOfDeletingStatement = index;
+            if (this.value.statements[index]) {
+                this.askForDeleting = true;
+            } else {
+                this.deleteStatement();
+            }
+        },
+        deleteStatement: function () {
+            this.$delete(this.value.statements, this.indexOfDeletingStatement);
+            this.askForDeleting = false;
+        },
+        onPaste: function (ev, position) {
+            let data = ev.clipboardData.getData("text").split("\n");
+            for (let i = 0; i < data.length; i++) {
+                if (data[i].trim()) {
+                    this.addStatement(data[i], position + i);
+                }
+            }
+        },
+        keyHandler(e, index) {
+            switch (e.keyCode) {
+                case 38: // up
+                    e.preventDefault();
+                    if (index > 0) {
+                        this.moveUp(index);
+                        this.$nextTick(function () {
+                            this.$refs['draghandle_' + (index - 1)][0].focus();
+                            this.assistiveLive = this.$gettextInterpolate(
+                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
+                                , {pos: index, listLength: this.val_clone.statements.length}
+                            );
+                        });
+                    }
+                    break;
+                case 40: // down
+                    e.preventDefault();
+                    if (index < this.val_clone.statements.length - 1) {
+                        this.moveDown(index);
+                        this.$nextTick(function () {
+                            this.$refs['draghandle_' + (index + 1)][0].focus();
+                            this.assistiveLive = this.$gettextInterpolate(
+                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
+                                , {pos: index + 2, listLength: this.val_clone.statements.length}
+                            );
+                        });
+                    }
+                    break;
+            }
+        },
+        moveDown: function (index) {
+            let statement = this.val_clone.statements[index];
+            this.val_clone.statements[index] = this.val_clone.statements[index + 1];
+            this.val_clone.statements[index + 1] = statement;
+            this.$forceUpdate();
+        },
+        moveUp: function (index) {
+            let statement = this.val_clone.statements[index];
+            this.val_clone.statements[index] = this.val_clone.statements[index - 1];
+            this.val_clone.statements[index - 1] = statement;
+            this.$forceUpdate();
+        }
+    },
+    mounted: function () {
+        this.val_clone = this.value;
+        if (!this.value.statements) {
+            this.$emit('input', default_value);
+        }
+        this.$refs.autofocus.focus();
+    },
+    watch: {
+        value (new_val) {
+            this.val_clone = new_val;
+        }
+    }
+}
+</script>
diff --git a/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue b/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
new file mode 100644
index 0000000000000000000000000000000000000000..57d6c1310fe71a089644a3a5aa17befe60c24667
--- /dev/null
+++ b/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
@@ -0,0 +1,54 @@
+<template>
+    <div class="vote_edit">
+        <label>
+            {{ $gettext('Link eines Videos oder einer anderen Informationsseite (optional)') }}
+            <input type="text" v-model="val_clone.url" ref="autofocus">
+        </label>
+
+        <div class="formpart">
+            {{ $gettext('Hinweistext (optional)') }}
+            <studip-wysiwyg v-model="val_clone.description" :key="question_id"></studip-wysiwyg>
+        </div>
+    </div>
+</template>
+
+<script>
+import StudipWysiwyg from "../StudipWysiwyg.vue";
+
+export default {
+    name: 'questionnaire-info-edit',
+    components: {
+        StudipWysiwyg
+    },
+    props: {
+        value: {
+            type: Object,
+            required: false,
+            default: function () {
+                return {
+                    url: '',
+                    description: ''
+                };
+            }
+        },
+        question_id: {
+            type: String,
+            required: false
+        }
+    },
+    data: function () {
+        return {
+            val_clone: ''
+        };
+    },
+    mounted: function () {
+        this.val_clone = this.value;
+        this.$refs.autofocus.focus();
+    },
+    watch: {
+        value (new_val) {
+            this.val_clone = new_val;
+        }
+    }
+}
+</script>
diff --git a/resources/vue/components/questionnaires/RangescaleEdit.vue b/resources/vue/components/questionnaires/RangescaleEdit.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c1e473f425b65f7daa313f5938c9a8c318fc0895
--- /dev/null
+++ b/resources/vue/components/questionnaires/RangescaleEdit.vue
@@ -0,0 +1,242 @@
+<template>
+    <div class="rangescale_edit">
+
+        <div class="formpart" tabindex="0" ref="autofocus">
+            {{$gettext('Einleitungstext')}}
+            <studip-wysiwyg v-model="val_clone.description"></studip-wysiwyg>
+        </div>
+
+        <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
+
+        <table class="default nohover">
+            <thead>
+                <tr>
+                    <th class="dragcolumn"></th>
+                    <th>{{ $gettext('Aussagen') }}</th>
+                    <template v-if="typeof val_clone.maximum !== 'undefined' && typeof val_clone.minimum !== 'undefined'">
+                    <th v-for="i in (val_clone.maximum - val_clone.minimum + 1)" :key="i" class="number">{{ (val_clone.minimum - 1 + i) }}</th>
+                    </template>
+                    <th v-if="typeof val_clone.alternative_answer !== 'undefined' && val_clone.alternative_answer.trim().length > 0">{{ val_clone.alternative_answer }}</th>
+                    <th class="actions"></th>
+                </tr>
+            </thead>
+            <draggable v-model="val_clone.statements" handle=".dragarea" tag="tbody" class="statements">
+                <tr v-for="(statement, index) in val_clone.statements" :key="index">
+                    <td class="dragcolumn">
+                        <a class="dragarea"
+                           tabindex="0"
+                           :title="$gettextInterpolate('Sortierelement für Aussage %{statement}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {statement: statement})"
+                           @keydown="keyHandler($event, index)"
+                           :ref="'draghandle_' + index">
+                            <studip-icon shape="hamburger" role="clickable"></studip-icon>
+                        </a>
+                    </td>
+                    <td>
+                        <input type="text"
+                               :ref="'statement_' + index"
+                               :placeholder="$gettext('Aussage')"
+                               @paste="(ev) => onPaste(ev, index)"
+                               v-model="val_clone.statements[index]">
+                    </td>
+                    <template v-if="typeof val_clone.maximum !== 'undefined' && typeof val_clone.minimum !== 'undefined'">
+                    <td v-for="i in (val_clone.maximum - value.minimum + 1)" :key="i">
+                        <input type="radio" value="1" disabled :title="i + val_clone.minimum - 1">
+                    </td>
+                    </template>
+                    <td v-if="typeof val_clone.alternative_answer !== 'undefined' && val_clone.alternative_answer.trim().length > 0 > 0">
+                        <input type="radio" value="1" disabled :title="val_clone.alternative_answer">
+                    </td>
+                    <td class="actions">
+                        <button class="as-link"
+                           @click.prevent="askForDeletingStatement(index)"
+                           :title="$gettext('Aussage löschen')">
+                            <studip-icon shape="trash" role="clickable" size="20" alt=""></studip-icon>
+                        </button>
+                    </td>
+                </tr>
+            </draggable>
+            <tfoot>
+                <tr>
+                    <td :colspan="val_clone.maximum - val_clone.minimum + 4 + (typeof val_clone.alternative_answer !== 'undefined' && val_clone.alternative_answer.trim().length > 0 ? 1 : 0)">
+                        <button @click.prevent="addStatement" class="as-link" :title="$gettext('Aussage hinzufügen')">
+                            <studip-icon shape="add" role="clickable" size="20" alt=""></studip-icon>
+                        </button>
+                    </td>
+                </tr>
+            </tfoot>
+        </table>
+
+        <label>
+            <input type="checkbox" v-model="val_clone.mandatory">
+            {{ $gettext('Pflichtfrage') }}
+        </label>
+        <label>
+            <input type="checkbox" v-model="val_clone.randomize">
+            {{ $gettext('Antworten den Teilnehmenden zufällig präsentieren') }}
+        </label>
+
+        <label>
+            {{ $gettext('Maximum') }}
+            <input type="number" v-model.number="val_clone.maximum">
+        </label>
+
+        <label>
+            {{ $gettext('Minimum') }}
+            <input type="number" v-model.number="val_clone.minimum">
+        </label>
+
+        <label>
+            {{ $gettext('Ausweichantwort (leer lassen für keine)') }}
+            <input type="text" v-model="val_clone.alternative_answer">
+        </label>
+
+        <studip-dialog
+            v-if="askForDeleting"
+            :title="$gettext('Bitte bestätigen Sie die Aktion.')"
+            :question="$gettext('Wirklich löschen?')"
+            :confirmText="$gettext('Ja')"
+            :closeText="$gettext('Nein')"
+            closeClass="cancel"
+            height="180"
+            @confirm="deleteStatement"
+            @close="askForDeleting = false"
+        >
+        </studip-dialog>
+    </div>
+</template>
+
+<script>
+import StudipIcon from "../StudipIcon.vue";
+import StudipDialog from "../StudipDialog.vue";
+import draggable from 'vuedraggable';
+import StudipWysiwyg from "../StudipWysiwyg.vue";
+const default_value = {
+    statements: ['', '', '', ''],
+    minimum: 1,
+    maximum: 5,
+    alternative_answer: ''
+};
+export default {
+    name: 'likert-edit',
+    components: {
+        StudipIcon,
+        StudipDialog,
+        draggable,
+        StudipWysiwyg
+    },
+    props: {
+        value: {
+            type: Object,
+            required: false,
+            default: function () {
+                return default_value;
+            }
+        },
+        question_id: {
+            type: String,
+            required: false
+        }
+    },
+    data: function () {
+        return {
+            val_clone: {},
+            askForDeleting: false,
+            indexOfDeletingStatement: 0,
+            assistiveLive: ''
+        };
+    },
+    methods: {
+        addStatement: function (val, position) {
+            if (val.target) {
+                val = '';
+            }
+            let data = this.value;
+            if (typeof position === "undefined") {
+                data.statements.push(val || '');
+                position = this.value.length - 1
+            } else {
+                data.statements.splice(position, 0, val || '');
+            }
+            this.$emit('input', data);
+            let v = this;
+            this.$nextTick(function () {
+                v.$refs['statement_' + (v.value.statements.length - 1)][0].focus();
+            });
+        },
+        askForDeletingStatement: function (index) {
+            this.indexOfDeletingStatement = index;
+            if (this.value.statements[index]) {
+                this.askForDeleting = true;
+            } else {
+                this.deleteStatement();
+            }
+        },
+        deleteStatement: function () {
+            this.$delete(this.value.statements, this.indexOfDeletingStatement);
+            this.askForDeleting = false;
+        },
+        onPaste: function (ev, position) {
+            let data = ev.clipboardData.getData("text").split("\n");
+            for (let i = 0; i < data.length; i++) {
+                if (data[i].trim()) {
+                    this.addStatement(data[i], position + i);
+                }
+            }
+        },
+        keyHandler(e, index) {
+            switch (e.keyCode) {
+                case 38: // up
+                    e.preventDefault();
+                    if (index > 0) {
+                        this.moveUp(index);
+                        this.$nextTick(function () {
+                            this.$refs['draghandle_' + (index - 1)][0].focus();
+                            this.assistiveLive = this.$gettextInterpolate(
+                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
+                                , {pos: index, listLength: this.val_clone.statements.length}
+                            );
+                        });
+                    }
+                    break;
+                case 40: // down
+                    e.preventDefault();
+                    if (index < this.val_clone.statements.length - 1) {
+                        this.moveDown(index);
+                        this.$nextTick(function () {
+                            this.$refs['draghandle_' + (index + 1)][0].focus();
+                            this.assistiveLive = this.$gettextInterpolate(
+                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
+                                , {pos: index + 2, listLength: this.val_clone.statements.length}
+                            );
+                        });
+                    }
+                    break;
+            }
+        },
+        moveDown: function (index) {
+            let statement = this.val_clone.statements[index];
+            this.val_clone.statements[index] = this.val_clone.statements[index + 1];
+            this.val_clone.statements[index + 1] = statement;
+            this.$forceUpdate();
+        },
+        moveUp: function (index) {
+            let statement = this.val_clone.statements[index];
+            this.val_clone.statements[index] = this.val_clone.statements[index - 1];
+            this.val_clone.statements[index - 1] = statement;
+            this.$forceUpdate();
+        }
+    },
+    mounted: function () {
+        this.val_clone = this.value;
+        if (!this.value.statements) {
+            this.$emit('input', default_value);
+        }
+        this.$refs.autofocus.focus();
+    },
+    watch: {
+        value (new_val) {
+            this.val_clone = new_val;
+        }
+    }
+}
+</script>
diff --git a/resources/vue/components/questionnaires/VoteEdit.vue b/resources/vue/components/questionnaires/VoteEdit.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9acb01fafd65f9723b2756106e7ad6f2044f6876
--- /dev/null
+++ b/resources/vue/components/questionnaires/VoteEdit.vue
@@ -0,0 +1,70 @@
+<template>
+    <div class="vote_edit">
+        <div class="formpart" tabindex="0" ref="autofocus">
+            {{ $gettext('Frage') }}
+            <studip-wysiwyg v-model="val_clone.description" :key="question_id"></studip-wysiwyg>
+        </div>
+
+        <input-array v-model="val_clone.options"></input-array>
+
+        <label>
+            <input type="checkbox" v-model.number="val_clone.multiplechoice" true-value="1" false-value="0">
+            {{ $gettext('Mehrere Antworten sind erlaubt') }}
+        </label>
+        <label>
+            <input type="checkbox" v-model.number="val_clone.mandatory" true-value="1" false-value="0">
+            {{ $gettext('Pflichtfrage') }}
+        </label>
+        <label>
+            <input type="checkbox" v-model.number="val_clone.randomize" true-value="1" false-value="0">
+            {{ $gettext('Antworten den Teilnehmenden zufällig präsentieren') }}
+        </label>
+
+    </div>
+</template>
+
+<script>
+import StudipWysiwyg from "../StudipWysiwyg.vue";
+import InputArray from "./InputArray.vue";
+
+export default {
+    name: 'vote-edit',
+    components: {
+        StudipWysiwyg,
+        InputArray
+    },
+    props: {
+        value: {
+            type: Object,
+            required: false,
+            default: function () {
+                return {};
+            }
+        },
+        question_id: {
+            type: String,
+            required: false
+        }
+    },
+    data: function () {
+        return {
+            val_clone: {}
+        };
+    },
+    mounted: function () {
+        this.val_clone = this.value;
+        if (!this.value.description) {
+            this.$emit('input', {
+                multiplechoice: 1,
+                options: ['', '', '', '']
+            });
+        }
+        this.$refs.autofocus.focus();
+    },
+    watch: {
+        value (new_val) {
+            this.val_clone = new_val;
+        }
+    }
+}
+</script>