From cdc7050e5be7a0813b4634ca2aaf33fff311ff52 Mon Sep 17 00:00:00 2001
From: Rasmus Fuhse <fuhse@data-quest.de>
Date: Mon, 25 Nov 2024 10:13:55 +0000
Subject: [PATCH] =?UTF-8?q?Resolve=20"Automatisierte=20Angaben=20in=20Frag?=
 =?UTF-8?q?eb=C3=B6gen"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #4259

Merge request studip/studip!3156
---
 app/controllers/questionnaire.php             |   1 +
 app/views/questionnaire/edit.php              |   1 +
 .../automated_data/_evaluation_part.php       |  81 ++++++++
 .../question_types/automated_data/answer.php  |  54 ++++++
 .../automated_data/evaluation.php             | 110 +++++++++++
 ...6.0.33_add_automated_data_questiontype.php |  36 ++++
 lib/classes/JsonApi/RouteMap.php              |   2 +
 .../JsonApi/Routes/DatafieldsIndex.php        |  38 ++++
 lib/classes/JsonApi/SchemaMap.php             |   1 +
 lib/classes/JsonApi/Schemas/Datafield.php     |  42 ++++
 lib/models/QuestionnaireAutomatedData.php     | 182 ++++++++++++++++++
 .../images/icons/blue/question-automation.svg |   1 +
 .../icons/blue/question-automation2.svg       |   1 +
 .../stylesheets/scss/questionnaire.scss       |   4 +
 .../questionnaires/AutomatedDataEdit.vue      |  61 ++++++
 15 files changed, 615 insertions(+)
 create mode 100644 app/views/questionnaire/question_types/automated_data/_evaluation_part.php
 create mode 100644 app/views/questionnaire/question_types/automated_data/answer.php
 create mode 100644 app/views/questionnaire/question_types/automated_data/evaluation.php
 create mode 100644 db/migrations/6.0.33_add_automated_data_questiontype.php
 create mode 100644 lib/classes/JsonApi/Routes/DatafieldsIndex.php
 create mode 100644 lib/classes/JsonApi/Schemas/Datafield.php
 create mode 100644 lib/models/QuestionnaireAutomatedData.php
 create mode 100644 public/assets/images/icons/blue/question-automation.svg
 create mode 100644 public/assets/images/icons/blue/question-automation2.svg
 create mode 100644 resources/vue/components/questionnaires/AutomatedDataEdit.vue

diff --git a/app/controllers/questionnaire.php b/app/controllers/questionnaire.php
index c1361786f84..9f16442cfcd 100644
--- a/app/controllers/questionnaire.php
+++ b/app/controllers/questionnaire.php
@@ -18,6 +18,7 @@ class QuestionnaireController extends AuthenticatedController
         class_exists('LikertScale');
         class_exists('RangeScale');
         class_exists('QuestionnaireInfo');
+        class_exists('QuestionnaireAutomatedData');
         PageLayout::setHelpKeyword('Basis/Votings');
     }
 
diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php
index e5ef007b62e..cd73fbc282a 100644
--- a/app/views/questionnaire/edit.php
+++ b/app/views/questionnaire/edit.php
@@ -13,6 +13,7 @@ foreach (get_declared_classes() as $class) {
     if (
         is_subclass_of($class, QuestionType::class)
         && !isset($questiontypes[$class])
+        && isset($class::getEditingComponent()[0])
     ) {
         $questiontypes[$class] = [
             'name' => $class::getName(),
diff --git a/app/views/questionnaire/question_types/automated_data/_evaluation_part.php b/app/views/questionnaire/question_types/automated_data/_evaluation_part.php
new file mode 100644
index 00000000000..d2b7bacd945
--- /dev/null
+++ b/app/views/questionnaire/question_types/automated_data/_evaluation_part.php
@@ -0,0 +1,81 @@
+<?
+/**
+ * @var QuestionnaireQuestion $question
+ * @var QuestionnaireAnswer[] $answers
+ * @var $filtered string
+ * @var $description string
+ * @var $answerdata array
+ * @var $options array
+ */
+
+?>
+
+<div class="description_container">
+    <div class="icon_container">
+        <?= Icon::create('tan2', Icon::ROLE_INFO) ?>
+    </div>
+    <div class="description">
+        <?= formatReady($description) ?>
+    </div>
+</div>
+
+
+<table class="default nohover">
+    <tbody>
+    <? $countAnswers = $question->questionnaire->countAnswers() ?>
+    <? foreach ($options as $key => $answer) : ?>
+        <tr>
+            <? $percentage = $countAnswers && isset($answerdata[$key]) ? round(count((array) $answerdata[$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><?= htmlReady($answer) ?></strong>
+            </td>
+
+            <td style="white-space: nowrap;">
+                <? if (!empty($filtered) && $filtered == $key) : ?>
+                    <a href=""
+                       title="<?= _('Zeige wieder alle Ergebnisse ohne Filterung an.') ?>"
+                       onclick="STUDIP.Questionnaire.removeFilter('<?= htmlReady($question['questionnaire_id']) ?>'); return false;">
+                        <?= Icon::create('filter2')->asImg(['class' => 'text-bottom']) ?>
+                        (<?= $percentage ?>% | <?= (int) count((array) $answerdata[$key]) ?>/<?= $countAnswers ?>)
+                    </a>
+                <? else : ?>
+                    <a href=""
+                       onclick="STUDIP.Questionnaire.addFilter('<?= htmlReady($question['questionnaire_id']) ?>', '<?= htmlReady($question->getId()) ?>', '<?= $key ?>'); return false;"
+                       title="<?= _('Zeige nur Ergebnisse von Personen an, die diese Option gewählt haben.') ?>">
+                        (<?= $percentage ?>% | <?= isset($answerdata[$key]) ? count((array) $answerdata[$key]) : 0 ?>/<?= $countAnswers ?>)
+                    </a>
+                <? endif ?>
+            </td>
+
+            <td width="50%">
+                <? if (!$question->questionnaire['anonymous'] && isset($answerdata[$key]) && $answerdata[$key]) : ?>
+
+                    <? $users = SimpleCollection::createFromArray(
+                        User::findMany($answerdata[$key])); ?>
+
+                    <? foreach ($answerdata[$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($answerdata[$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/automated_data/answer.php b/app/views/questionnaire/question_types/automated_data/answer.php
new file mode 100644
index 00000000000..53ee2224197
--- /dev/null
+++ b/app/views/questionnaire/question_types/automated_data/answer.php
@@ -0,0 +1,54 @@
+<?
+/**
+ * @var QuestionnaireQuestion $question
+ */
+$data = [];
+$user = User::findCurrent();
+if ($question['questiondata']['geschlecht']) {
+    $map = [
+        0 => _('unbekannt'),
+        1 => _('männlich'),
+        2 => _('weiblich'),
+        3 => _('divers')
+    ];
+    $data[] = _('Geschlecht:') . ' ' . $map[$user['geschlecht']];
+}
+if ($question['questiondata']['studienfach']) {
+    $studienfach = [];
+    foreach ($user->studycourses as $studycourse) {
+        $studienfach[] = $studycourse->studycourse_name;
+    }
+    $data[] = _('Studienfach:') . ' ' . (implode(', ', $studienfach) ?: _('Kein Eintrag'));
+}
+if ($question['questiondata']['studiengang']) {
+    $studiengang = [];
+    foreach ($user->studycourses as $studycourse) {
+        $studiengang[] = $studycourse->studycourse_name . ' ' . $studycourse->degree_name;
+    }
+    $data[] = _('Studiengang:') . ' ' . (implode(', ', $studiengang) ?: _('Kein Eintrag'));
+}
+if ($question['questiondata']['studiengangfachsemester']) {
+    $studiengang = [];
+    foreach ($user->studycourses as $studycourse) {
+        $studiengang[] = $studycourse->studycourse_name . ' ' . $studycourse->degree_name . ' ' . $studycourse->semester;
+    }
+    $data[] = _('Studiengang und Fachsemester:') . ' ' . (implode(', ', $studiengang) ?: _('Kein Eintrag'));
+}
+if (isset($question['questiondata']['datafields'])) {
+    foreach ($question['questiondata']['datafields'] as $datafield_id) {
+        $datafield = DataField::find($datafield_id);
+        if ($datafield) {
+            $entry = DatafieldEntryModel::findOneBySQL('range_id = :user_id AND datafield_id = :datafield_id', [
+                'user_id' => $user->getId(),
+                'datafield_id' => $datafield_id
+            ]);
+            $data[] = $datafield['name'] . ': ' . ($entry ? $entry['content'] : _('Kein Eintrag'));
+        }
+    }
+}
+?>
+<? if ($question->questionnaire['anonymous']) : ?>
+    <?= MessageBox::info(_('Die folgenden Daten werden in diesem Fragebogen automatisch erfasst. Die Teilnahme an diesem Fragebogen erfolgt grundsätzlich anonym, aber eventuell können die automatisch erfassten Daten zu einer Deanonymisierung führen.'), array_map('htmlReady', $data)) ?>
+<? else : ?>
+    <?= MessageBox::info(_('Die folgenden Daten werden in diesem Fragebogen automatisch erfasst:'), array_map('htmlReady', $data)) ?>
+<? endif ?>
diff --git a/app/views/questionnaire/question_types/automated_data/evaluation.php b/app/views/questionnaire/question_types/automated_data/evaluation.php
new file mode 100644
index 00000000000..16b8f46d76f
--- /dev/null
+++ b/app/views/questionnaire/question_types/automated_data/evaluation.php
@@ -0,0 +1,110 @@
+<?
+/**
+ * @var QuestionnaireQuestion $question
+ * @var QuestionnaireAnswer[] $answers
+ * @var $filtered string
+ */
+?>
+
+<? if ($question['questiondata']['geschlecht']) : ?>
+    <? $options = [
+        0 => _('unbekannt'),
+        1 => _('männlich'),
+        2 => _('weiblich'),
+        3 => _('divers')
+    ];
+    $answerdata = [];
+    foreach ($answers as $answer) {
+        if (isset($answer['answerdata']['geschlecht'])) {
+            $answerdata[$answer['answerdata']['geschlecht']][] = $answer['user_id'];
+        }
+    }
+    ?>
+    <?= $this->render_partial('questionnaire/question_types/automated_data/_evaluation_part.php', [
+        'options' => $options,
+        'description' => _('Geschlecht'),
+        'answerdata' => $answerdata,
+    ]) ?>
+<? endif ?>
+
+<? if ($question['questiondata']['studienfach']) : ?>
+    <? $options = [];
+    $answerdata = [];
+    foreach ($answers as $answer) {
+        if (isset($answer['answerdata']['studienfach'])) {
+            foreach ($answer['answerdata']['studienfach'] as $studienfach) {
+                if (!in_array($studienfach, $options)) {
+                    $options[$studienfach] = $studienfach;
+                }
+                $answerdata[$studienfach][] = $answer['user_id'];
+            }
+        }
+    }
+    ?>
+    <?= $this->render_partial('questionnaire/question_types/automated_data/_evaluation_part.php', [
+        'options' => $options,
+        'description' => _('Studiengang'),
+        'answerdata' => $answerdata,
+    ]) ?>
+<? endif ?>
+
+<? if ($question['questiondata']['studiengang']) : ?>
+    <? $options = [];
+    $answerdata = [];
+    foreach ($answers as $answer) {
+        if (isset($answer['answerdata']['studiengang'])) {
+            foreach ($answer['answerdata']['studiengang'] as $studiengang) {
+                if (!in_array($studiengang, $options)) {
+                    $options[$studiengang] = $studiengang;
+                }
+                $answerdata[$studiengang][] = $answer['user_id'];
+            }
+        }
+    }
+    ?>
+    <?= $this->render_partial('questionnaire/question_types/automated_data/_evaluation_part.php', ['options' => $options, 'description' => _('Studiengang'), 'answerdata' => $answerdata]) ?>
+<? endif ?>
+
+<? if ($question['questiondata']['studiengangfachsemester']) : ?>
+    <? $options = [];
+    $answerdata = [];
+    foreach ($answers as $answer) {
+        if (isset($answer['answerdata']['studiengangfachsemester'])) {
+            foreach ($answer['answerdata']['studiengangfachsemester'] as $studiengangfachsemester) {
+                if (!in_array($studiengangfachsemester, $options)) {
+                    $options[$studiengangfachsemester] = $studiengangfachsemester;
+                }
+                $answerdata[$studiengangfachsemester][] = $answer['user_id'];
+            }
+        }
+    }
+    ?>
+    <?= $this->render_partial('questionnaire/question_types/automated_data/_evaluation_part.php', [
+        'options' => $options, 'description' => _('Studiengang und Fachsemester'),
+        'answerdata' => $answerdata,
+    ]) ?>
+<? endif ?>
+
+<? if (isset($question['questiondata']['datafields'])) :
+    foreach ($question['questiondata']['datafields'] as $datafield_id) :
+        $datafield = DataField::find($datafield_id);
+        if ($datafield) : ?>
+            <? $options = [];
+            $answerdata = [];
+            foreach ($answers as $answer) {
+                if (isset($answer['answerdata']['datafields'][$datafield_id])) {
+                    if (!in_array($answer['answerdata']['datafields'][$datafield_id], $options)) {
+                        $options[$answer['answerdata']['datafields'][$datafield_id]] = $answer['answerdata']['datafields'][$datafield_id];
+                    }
+                    $answerdata[$answer['answerdata']['datafields'][$datafield_id]][] = $answer['user_id'];
+                }
+            }
+            ?>
+            <?= $this->render_partial('questionnaire/question_types/automated_data/_evaluation_part.php', [
+                'options' => $options,
+                'description' => $datafield['name'],
+                'answerdata' => $answerdata,
+            ]) ?>
+        <? endif ?>
+    <? endforeach ?>
+<? endif ?>
diff --git a/db/migrations/6.0.33_add_automated_data_questiontype.php b/db/migrations/6.0.33_add_automated_data_questiontype.php
new file mode 100644
index 00000000000..7a318fe269f
--- /dev/null
+++ b/db/migrations/6.0.33_add_automated_data_questiontype.php
@@ -0,0 +1,36 @@
+<?php
+
+class AddAutomatedDataQuestionType extends Migration
+{
+
+    public function description()
+    {
+        return 'Adds a new question type Automated Data. You can disable this question type by changing the config QUESTIONNAIRE_AUTOMATED_DATA_PERM.';
+    }
+
+    public function up()
+    {
+        $query = "INSERT IGNORE 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'        => 'QUESTIONNAIRE_AUTOMATED_DATA_PERM',
+            ':description' => 'Ab welchem Status (autor, tutor, dozent, admin, root) darf man den Fragetyp Automatik in Fragebögen einbauen?',
+            ':range'       => 'global',
+            ':type'        => 'string',
+            ':section'     => 'global',
+            ':value'       => 'autor'
+        ]);
+    }
+
+    public function down()
+    {
+        $query = "DELETE FROM `config`
+                  WHERE `field` = 'QUESTIONNAIRE_AUTOMATED_DATA_PERM' ";
+        DBManager::get()->exec($query);
+        $query = "DELETE FROM `config_values`
+                  WHERE `field` = 'QUESTIONNAIRE_AUTOMATED_DATA_PERM' ";
+        DBManager::get()->exec($query);
+    }
+}
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php
index ec3668d1b2c..c7a6c898a7f 100644
--- a/lib/classes/JsonApi/RouteMap.php
+++ b/lib/classes/JsonApi/RouteMap.php
@@ -158,6 +158,8 @@ class RouteMap
 
         $group->get('/studip/properties', Routes\Studip\PropertiesIndex::class);
 
+        $group->get('/datafields', Routes\DatafieldsIndex::class);
+
         if (\PluginManager::getInstance()->getPlugin(\CoursewareModule::class)) {
             $group->get('/public/courseware/{link_id}/courseware-structural-elements/{id}', Routes\Courseware\PublicStructuralElementsShow::class);
             $group->get('/public/courseware/{link_id}/courseware-structural-elements', Routes\Courseware\PublicStructuralElementsIndex::class);
diff --git a/lib/classes/JsonApi/Routes/DatafieldsIndex.php b/lib/classes/JsonApi/Routes/DatafieldsIndex.php
new file mode 100644
index 00000000000..b966c71ae2e
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/DatafieldsIndex.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace JsonApi\Routes;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use JsonApi\JsonApiController;
+
+/**
+ * List all the semesters.
+ */
+class DatafieldsIndex extends JsonApiController
+{
+    protected $allowedFilteringParameters = ['object_type'];
+    protected $allowedPagingParameters = ['offset', 'limit'];
+
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        [$offset, $limit] = $this->getOffsetAndLimit();
+
+        $params = $this->getQueryParameters();
+        $filtering = $params->getFilteringParameters();
+        if (isset($filtering['object_type'])) {
+            $datafields = \DataField::getDataFields($filtering['object_type']);
+        } else {
+            $datafields = \DataField::getDataFields();
+        }
+        $datafields = array_filter(
+            $datafields,
+            fn($field) => $field->accessAllowed()
+        );
+
+        return $this->getPaginatedContentResponse(
+            array_slice($datafields, $offset, $limit),
+            count($datafields)
+        );
+    }
+}
diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php
index 50d67606f00..c651b8774dc 100644
--- a/lib/classes/JsonApi/SchemaMap.php
+++ b/lib/classes/JsonApi/SchemaMap.php
@@ -35,6 +35,7 @@ class SchemaMap
             \CourseMember::class => Schemas\CourseMember::class,
             \CourseDate::class => Schemas\CourseEvent::class,
             \CourseExDate::class => Schemas\CourseEvent::class,
+            \DataField::class => Schemas\Datafield::class,
             \Degree::class => Schemas\Degree::class,
             \FeedbackElement::class => Schemas\FeedbackElement::class,
             \FeedbackEntry::class => Schemas\FeedbackEntry::class,
diff --git a/lib/classes/JsonApi/Schemas/Datafield.php b/lib/classes/JsonApi/Schemas/Datafield.php
new file mode 100644
index 00000000000..74b3b9c84a1
--- /dev/null
+++ b/lib/classes/JsonApi/Schemas/Datafield.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace JsonApi\Schemas;
+
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use JsonApi\Schemas\SchemaProvider;
+
+class Datafield extends SchemaProvider
+{
+    const TYPE = 'datafield';
+
+    public function getId($datafield): ?string
+    {
+        return $datafield->getId();
+    }
+
+    public function getAttributes($datafield, ContextInterface $context): iterable
+    {
+        return [
+            'name' => (string) $datafield->name,
+            'object_type' => $datafield->object_type,
+            'object_class' => $datafield->object_class,
+            'institut_id' => $datafield->institut_id,
+            'priority' => $datafield->priority,
+            'type' => $datafield->type,
+            'typeparam' => $datafield->typeparam,
+            'is_required' => (bool) $datafield->is_required,
+            'default_value' => $datafield->default_value,
+            'is_userfilter' => (bool) $datafield->is_userfilter,
+            'mkdate' => date('c', $datafield->mkdate),
+            'chdate' => date('c', $datafield->chdate)
+        ];
+    }
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function getRelationships($datafield, ContextInterface $context): iterable
+    {
+        return [];
+    }
+}
diff --git a/lib/models/QuestionnaireAutomatedData.php b/lib/models/QuestionnaireAutomatedData.php
new file mode 100644
index 00000000000..5b483920d33
--- /dev/null
+++ b/lib/models/QuestionnaireAutomatedData.php
@@ -0,0 +1,182 @@
+<?php
+
+class QuestionnaireAutomatedData extends QuestionnaireQuestion implements QuestionType
+{
+    public static function getIcon(bool $active = false) : Icon
+    {
+        return Icon::create(static::getIconShape(), $active ? Icon::ROLE_CLICKABLE : Icon::ROLE_INFO);
+    }
+
+    /**
+     * Returns the shape of the icon of this QuestionType
+     */
+    public static function getIconShape(): string
+    {
+        return 'question-automation';
+    }
+
+    public static function getName(): string
+    {
+        return _('Automatik');
+    }
+
+    public static function getEditingComponent(): array
+    {
+        if ($GLOBALS['perm']->have_perm(Config::get()->QUESTIONNAIRE_AUTOMATED_DATA_PERM)) {
+            return ['AutomatedDataEdit', ''];
+        } else {
+            //in this case the question_type is not allowed:
+            return [];
+        }
+    }
+
+    public function beforeStoringQuestiondata($questiondata)
+    {
+        return $questiondata;
+    }
+
+    public function getDisplayTemplate(): ?Flexi\Template
+    {
+        if ($GLOBALS['user']->id !== 'nobody') {
+            $factory = new Flexi\Factory(realpath(__DIR__.'/../../app/views'));
+            $template = $factory->open('questionnaire/question_types/automated_data/answer');
+            $template->set_attribute('question', $this);
+            return $template;
+        } else {
+            return null;
+        }
+    }
+
+    public function createAnswer(): QuestionnaireAnswer
+    {
+        $answer = $this->getMyAnswer();
+        $answerdata = [];
+        if ($GLOBALS['user']->id !== 'nobody') {
+            $user = User::findCurrent();
+            if ($this['questiondata']['geschlecht']) {
+                $answerdata['geschlecht'] = $user->geschlecht;
+            }
+            if ($this['questiondata']['studienfach']) {
+                $answerdata['studienfach'] = [];
+                foreach ($user->studycourses as $studycourse) {
+                    $answerdata['studienfach'][] = $studycourse->studycourse_name;
+                }
+            }
+            if ($this['questiondata']['studiengang']) {
+                $answerdata['studiengang'] = [];
+                foreach ($user->studycourses as $studycourse) {
+                    $answerdata['studiengang'][] = $studycourse->studycourse_name . ' ' . $studycourse->degree_name;
+                }
+            }
+            if ($this['questiondata']['studiengangfachsemester']) {
+                $answerdata['studiengangfachsemester'] = [];
+                foreach ($user->studycourses as $studycourse) {
+                    $answerdata['studiengangfachsemester'][] = $studycourse->studycourse_name . ' ' . $studycourse->degree_name . ' ' . $studycourse['semester'];
+                }
+            }
+            foreach ($this['questiondata']['datafields'] as $datafield_id) {
+                $datafieldentry = DatafieldEntryModel::findOneBySQL('range_id = :user_id AND datafield_id = :datafield_id', [
+                    'datafield_id' => $datafield_id,
+                    'user_id' => $user->getId()
+                ]);
+                $answerdata['datafields'][$datafield_id] = $datafieldentry['content'];
+            }
+        }
+
+        $answer->answerdata = $answerdata;
+        return $answer;
+    }
+
+    public function getUserIdsOfFilteredAnswer($answer_option): array
+    {
+        $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): Flexi\Template
+    {
+        $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\Factory(realpath(__DIR__.'/../../app/views'));
+        $template = $factory->open('questionnaire/question_types/automated_data/evaluation');
+        $template->set_attribute('question', $this);
+        $template->set_attribute('answers', $answers);
+        return $template;
+    }
+
+    public function getResultArray(): array
+    {
+        $output = [];
+
+        $options = [];
+        if ($this['questiondata']['geschlecht']) {
+            $options['geschlecht'] = _('Geschlecht');
+        }
+        if ($this['questiondata']['studienfach']) {
+            $options['studienfach'] = _('Studienfach');
+        }
+        if ($this['questiondata']['studiengang']) {
+            $options['studiengang'] = _('Studiengang');
+        }
+        if ($this['questiondata']['studiengangfachsemester']) {
+            $options['studiengangfachsemester'] = _('Studiengang und Fachsemester');
+        }
+        foreach ($this['questiondata']['datafields'] as $datafield_id) {
+            $datafield = DataField::find($datafield_id);
+            if ($datafield) {
+                $options[$datafield_id] = (string) $datafield['name'];
+            }
+        }
+
+        $map = [
+            0 => _('unbekannt'),
+            1 => _('männlich'),
+            2 => _('weiblich'),
+            3 => _('divers')
+        ];
+
+        foreach ($options 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 (isset($answerData[$key])) {
+                    if (is_array($answerData[$key])) {
+                        $answerOption[$userId] = implode('|', $answerData[$key]);
+                    } else {
+                        $answerOption[$userId] = $map[$answerData[$key]];
+                    }
+                } elseif(strlen($key) === 32 && isset($answerData['datafields']) && isset($answerData['datafields'][$key])) {
+                    $answerOption[$userId] = $answerData['datafields'][$key];
+                } else {
+                    $answerOption[$userId] = '';
+                }
+            }
+
+            $output[$option] = $answerOption;
+        }
+
+        return $output;
+    }
+}
diff --git a/public/assets/images/icons/blue/question-automation.svg b/public/assets/images/icons/blue/question-automation.svg
new file mode 100644
index 00000000000..de55c00dd68
--- /dev/null
+++ b/public/assets/images/icons/blue/question-automation.svg
@@ -0,0 +1 @@
+<svg data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="none" d="M0 0h64v64H0z" data-name="Viewbox 64x64"/><path d="M26.65 13H4v38h56V13zM8 17h11.48c.33 1.28.52 2.62.52 4 0 7.45-5.1 13.7-12 15.48zm0 30v-6.43c.36-.07.73-.16 1.08-.25l1.84 2.95c.24.38.72.54 1.13.36l2.25-.93c.42-.17.65-.62.55-1.06l-.78-3.39c.7-.41 1.37-.86 2.02-1.35l2.83 2.02c.37.26.87.22 1.19-.1l1.72-1.72c.32-.32.36-.82.1-1.19l-2.02-2.83c.49-.64.94-1.32 1.35-2.02l3.39.78c.44.1.89-.13 1.06-.55l.93-2.25c.17-.42.02-.9-.36-1.13l-2.95-1.84c.2-.78.36-1.57.47-2.38l3.43-.57c.44-.07.77-.46.77-.91v-2.44c0-.45-.33-.84-.77-.91l-3.43-.57c-.06-.44-.14-.87-.22-1.3h32.43v16.03s-.05.02-.08.04l-2.83 2.02c-.64-.49-1.31-.94-2.02-1.35l.78-3.39c.1-.44-.13-.89-.55-1.06l-2.25-.93a.915.915 0 0 0-1.13.36l-1.84 2.95c-.78-.2-1.57-.36-2.38-.47l-.57-3.43a.924.924 0 0 0-.91-.77h-2.44c-.45 0-.84.33-.91.77l-.57 3.43c-.81.11-1.6.27-2.38.47l-1.84-2.95a.91.91 0 0 0-1.13-.36l-2.25.93c-.42.17-.65.62-.55 1.06l.78 3.39c-.7.41-1.37.86-2.02 1.35l-2.83-2.02a.926.926 0 0 0-1.19.1l-1.72 1.72c-.32.32-.36.82-.1 1.19l2.02 2.83c-.49.64-.94 1.32-1.35 2.02l-3.39-.78c-.44-.1-.89.13-1.06.55l-.93 2.25c-.17.42-.02.9.36 1.13l2.95 1.84c-.09.36-.18.72-.25 1.08H8Zm48 0H25.52C27.3 40.1 33.54 35 41 35c6.89 0 12.75 4.37 15 10.48z" fill="#28497c" fill-rule="evenodd"/></svg>
\ No newline at end of file
diff --git a/public/assets/images/icons/blue/question-automation2.svg b/public/assets/images/icons/blue/question-automation2.svg
new file mode 100644
index 00000000000..ae65a055200
--- /dev/null
+++ b/public/assets/images/icons/blue/question-automation2.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path fill="none" d="M0 0h64v64H0z"/><path d="M7 47h22.346l.077 1.53-.074 1.47H4V37.635l1.936-1.597L7 35.46zm53-9.145-1.884-2.122-1.116.366V47H36.4l.058.526-3.303 1.529-.096.945H60zM18.8 14H4v16.992l1.413.996L7 31.274V17h16.771l-.261-1.246-4.672-.96zM60 29.058l-1.41.857-1.59.766V17H28.976l-.254-.987L28.415 14H60z" fill="#28497C"/><path d="M28.977 17c2.253 8.85 10.288 15.392 19.84 15.392 2.909 0 5.677-.607 8.183-1.71v5.417a22.3 22.3 0 0 1-4.311 1.089l-.96 4.67a30 30 0 0 1-5.826 0l-.96-4.67a25.7 25.7 0 0 1-5.426-1.454l-3.167 3.565a30 30 0 0 1-5.045-2.913l1.504-4.525a25.6 25.6 0 0 1-3.973-3.973l-4.525 1.505a30 30 0 0 1-2.913-5.046l3.566-3.166A22 22 0 0 1 23.772 17zM60 29.058v8.797l-1.884-2.121-1.116.365v-5.418l1.59-.766zM28.722 16.013 28.415 14H18.8l.039.794 4.67.96.263 1.246h5.205z" fill="#28497C"/><path d="M7 31.274a15.8 15.8 0 0 1 3.576-1.166l.931-3.518a22 22 0 0 1 5.7 0l.931 3.518a18.8 18.8 0 0 1 5.163 1.88l2.976-2.096a22 22 0 0 1 4.365 3.663l-1.548 3.294a18.8 18.8 0 0 1 2.748 4.758l3.626.307c.518 1.652.84 3.359.932 5.086h-7.054c-.767-7.598-7.19-13.537-14.989-13.537-2.685 0-5.207.704-7.357 1.997zM36.4 47h-7.054l.078 1.53-.075 1.47h3.71l.096-.945 3.303-1.529zM5.413 31.988 7 31.274v4.186l-1.065.578L4 37.636v-6.644z" fill="#28497C"/></svg>
\ No newline at end of file
diff --git a/resources/assets/stylesheets/scss/questionnaire.scss b/resources/assets/stylesheets/scss/questionnaire.scss
index fde7d32a2bf..f4d28e3219b 100644
--- a/resources/assets/stylesheets/scss/questionnaire.scss
+++ b/resources/assets/stylesheets/scss/questionnaire.scss
@@ -138,6 +138,10 @@ $width: 270px;
             text-align: center;
         }
     }
+
+    .automated-data-checkbox-list {
+        margin-top: 20px;
+    }
 }
 
 .questionnaire_results {
diff --git a/resources/vue/components/questionnaires/AutomatedDataEdit.vue b/resources/vue/components/questionnaires/AutomatedDataEdit.vue
new file mode 100644
index 00000000000..f37bba909f9
--- /dev/null
+++ b/resources/vue/components/questionnaires/AutomatedDataEdit.vue
@@ -0,0 +1,61 @@
+<template>
+    <div>
+        <studip-message-box type="info" :hideClose="false">
+            {{ $gettext('Die folgenden Daten können automatisch erfasst werden. Achtung: Diese Daten können zu einer Deanonymisierung der Teilnehmenden führen. Die Teilnehmenden sehen einen Hinweis, dass und welche Daten von ihnen erfasst werden.') }}
+        </studip-message-box>
+        <div class="automated-data-checkbox-list">
+            <label>
+                <input v-autofocus type="checkbox" v-model="val_clone.geschlecht" true-value="1" false-value="0">
+                {{ $gettext('Geschlecht') }}
+            </label>
+            <label>
+                <input type="checkbox" true-value="1" v-model="val_clone.studienfach" false-value="0">
+                {{$gettext('Studienfach')}}
+            </label>
+            <label>
+                <input type="checkbox" true-value="1" v-model="val_clone.studiengang" false-value="0">
+                {{$gettext('Studiengang')}}
+            </label>
+            <label>
+                <input type="checkbox" true-value="1" v-model="val_clone.studiengangfachsemester" false-value="0">
+                {{$gettext('Studiengang-Fachsemester')}}
+            </label>
+            <label v-for="datafield in datafields" :key="datafield.datafield_id">
+                <input type="checkbox" v-model="val_clone.datafields" :value="datafield.datafield_id">
+                {{ datafield.name }}
+            </label>
+        </div>
+    </div>
+</template>
+
+<script>
+import axios from 'axios';
+import { QuestionnaireComponent } from '../../mixins/QuestionnaireComponent';
+
+export default {
+    name: 'automated-data-edit',
+    mixins: [ QuestionnaireComponent ],
+    data() {
+        return {
+            datafields: []
+        }
+    },
+    created() {
+        this.setDefaultValues({
+            geschlecht: 0,
+            studienfach: 0,
+            studiengang: 0,
+            studiengangfachsemester: 0,
+            datafields: []
+        });
+        axios.get(STUDIP.URLHelper.getURL(`jsonapi.php/v1/datafields?filter[object_type]=user`)).then((response) => {
+            for (let studiengang of response.data.data) {
+                this.datafields.push({
+                    datafield_id: studiengang.id,
+                    name: studiengang.attributes.name
+                });
+            }
+        });
+    }
+}
+</script>
-- 
GitLab