From 653fcaba5a06b8098f9bdb98c051c35a567a4513 Mon Sep 17 00:00:00 2001 From: Rasmus Fuhse Date: Fri, 16 Dec 2022 13:13:32 +0000 Subject: =?UTF-8?q?Resolve=20"Evaluationen=20mit=20Frageb=C3=B6gen"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #703 Merge request studip/studip!363 --- app/controllers/jsupdater.php | 1 + app/controllers/questionnaire.php | 291 +++++++++-------- .../_answer_description_container.php | 18 ++ .../questionnaire/_overview_questionnaire.php | 55 +++- app/views/questionnaire/add_to_context.php | 4 +- app/views/questionnaire/context.php | 18 +- app/views/questionnaire/edit.php | 237 ++++++++++---- app/views/questionnaire/evaluate.php | 27 +- app/views/questionnaire/overview.php | 3 +- .../question_types/freetext/freetext_answer.php | 15 +- .../question_types/freetext/freetext_edit.php | 18 -- .../freetext/freetext_evaluation.php | 19 +- .../questionnaire/question_types/info/info.php | 22 ++ .../question_types/likert/likert_answer.php | 51 +++ .../question_types/likert/likert_evaluation.php | 70 +++++ .../rangescale/rangescale_answer.php | 51 +++ .../rangescale/rangescale_evaluation.php | 70 +++++ .../questionnaire/question_types/test/_answer.php | 18 -- .../question_types/test/test_edit.php | 62 ---- .../question_types/test/test_evaluation.php | 122 ------- .../question_types/vote/vote_answer.php | 29 +- .../question_types/vote/vote_edit.php | 59 ---- .../question_types/vote/vote_evaluation.php | 49 ++- db/migrations/5.3.14_revamp_questionnaires.php | 124 ++++++++ lib/classes/QuestionType.interface.php | 39 ++- lib/models/Freetext.php | 75 ++--- lib/models/LikertScale.php | 115 +++++++ lib/models/Questionnaire.php | 29 +- lib/models/QuestionnaireAnswer.php | 4 + lib/models/QuestionnaireInfo.php | 68 ++++ lib/models/QuestionnaireQuestion.php | 25 +- lib/models/RangeScale.php | 114 +++++++ lib/models/Test.php | 189 ----------- lib/models/Vote.php | 103 +++--- lib/modules/CoreAdmin.class.php | 3 +- lib/modules/EvaluationsWidget.php | 4 +- lib/navigation/AdminNavigation.php | 2 +- lib/navigation/ContentsNavigation.php | 2 + lib/navigation/StartNavigation.php | 15 +- public/assets/images/icons/black/likert.svg | 10 + public/assets/images/icons/black/rangescale.svg | 15 + public/assets/images/icons/blue/likert.svg | 10 + public/assets/images/icons/blue/rangescale.svg | 15 + public/assets/images/icons/white/likert.svg | 10 + public/assets/images/icons/white/rangescale.svg | 15 + .../assets/javascripts/bootstrap/questionnaire.js | 75 +---- resources/assets/javascripts/lib/questionnaire.js | 349 +++++++++++++++++++-- resources/assets/stylesheets/scss/buttons.scss | 11 + .../assets/stylesheets/scss/questionnaire.scss | 194 +++++++++++- resources/assets/stylesheets/studip.scss | 13 + .../vue/components/questionnaires/FreetextEdit.vue | 52 +++ .../vue/components/questionnaires/InputArray.vue | 177 +++++++++++ .../vue/components/questionnaires/LikertEdit.vue | 225 +++++++++++++ .../questionnaires/QuestionnaireInfoEdit.vue | 54 ++++ .../components/questionnaires/RangescaleEdit.vue | 242 ++++++++++++++ .../vue/components/questionnaires/VoteEdit.vue | 70 +++++ 56 files changed, 2743 insertions(+), 1014 deletions(-) create mode 100644 app/views/questionnaire/_answer_description_container.php delete mode 100644 app/views/questionnaire/question_types/freetext/freetext_edit.php create mode 100644 app/views/questionnaire/question_types/info/info.php create mode 100644 app/views/questionnaire/question_types/likert/likert_answer.php create mode 100644 app/views/questionnaire/question_types/likert/likert_evaluation.php create mode 100644 app/views/questionnaire/question_types/rangescale/rangescale_answer.php create mode 100644 app/views/questionnaire/question_types/rangescale/rangescale_evaluation.php delete mode 100644 app/views/questionnaire/question_types/test/_answer.php delete mode 100644 app/views/questionnaire/question_types/test/test_edit.php delete mode 100644 app/views/questionnaire/question_types/test/test_evaluation.php delete mode 100644 app/views/questionnaire/question_types/vote/vote_edit.php create mode 100644 db/migrations/5.3.14_revamp_questionnaires.php create mode 100644 lib/models/LikertScale.php create mode 100644 lib/models/QuestionnaireInfo.php create mode 100644 lib/models/RangeScale.php delete mode 100644 lib/models/Test.php create mode 100644 public/assets/images/icons/black/likert.svg create mode 100644 public/assets/images/icons/black/rangescale.svg create mode 100644 public/assets/images/icons/blue/likert.svg create mode 100644 public/assets/images/icons/blue/rangescale.svg create mode 100644 public/assets/images/icons/white/likert.svg create mode 100644 public/assets/images/icons/white/rangescale.svg create mode 100644 resources/vue/components/questionnaires/FreetextEdit.vue create mode 100644 resources/vue/components/questionnaires/InputArray.vue create mode 100644 resources/vue/components/questionnaires/LikertEdit.vue create mode 100644 resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue create mode 100644 resources/vue/components/questionnaires/RangescaleEdit.vue create mode 100644 resources/vue/components/questionnaires/VoteEdit.vue diff --git a/app/controllers/jsupdater.php b/app/controllers/jsupdater.php index a706337..b098b71 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 3daf79f..04230cf 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 0000000..6e066c8 --- /dev/null +++ b/app/views/questionnaire/_answer_description_container.php @@ -0,0 +1,18 @@ + +
+
+ asImg(20) ?> +
+
+ questiondata['mandatory']) && $vote->questiondata['mandatory']) : ?> + asImg(20, ['class' => 'text-bottom', 'alt' => '']) ?> + + + questiondata['description']) ?> +
+
diff --git a/app/views/questionnaire/_overview_questionnaire.php b/app/views/questionnaire/_overview_questionnaire.php index 4bf2897..f028785 100644 --- a/app/views/questionnaire/_overview_questionnaire.php +++ b/app/views/questionnaire/_overview_questionnaire.php @@ -1,23 +1,12 @@ + countAnswers() ?> - + - - 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()])." "; - } - ?> - @@ -47,7 +36,8 @@ - name) ?> + + IMPORTANT_SEMNUMBER ? $course->veranstaltungsnummer." " : "") . $course['name'] . ' ('.$course->semester_text.')') ?> @@ -77,18 +67,21 @@ - countAnswers() ?> isRunning() && $countedAnswers) : ?> asImg(20, ['title' => _('Der Fragebogen wurde gestartet und kann nicht mehr bearbeitet werden.')]) ?> - + asImg(20) ?> - + asImg(20) ?> @@ -96,6 +89,12 @@ $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'), Icon::create('pause', 'clickable') @@ -114,6 +113,28 @@ ['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'), Icon::create('file-excel', 'clickable') diff --git a/app/views/questionnaire/add_to_context.php b/app/views/questionnaire/add_to_context.php index 5fdf836..885aad5 100644 --- a/app/views/questionnaire/add_to_context.php +++ b/app/views/questionnaire/add_to_context.php @@ -8,13 +8,13 @@ $icons = [
Context::getType(), 'range_id' => Context::get()->id]) ?>" - data-dialog="size=default"> + data-dialog="size=big"> asImg(50) ?> name) ?> "statusgruppe", 'range_id' => $statusgruppe->getId()]) ?>" - data-dialog="size=default"> + data-dialog="size=big"> asImg(50) ?> name) ?> diff --git a/app/views/questionnaire/context.php b/app/views/questionnaire/context.php index 7d7294b..3b601c1 100644 --- a/app/views/questionnaire/context.php +++ b/app/views/questionnaire/context.php @@ -28,22 +28,24 @@ - render() ?> + render() ?>

    @@ -54,11 +56,11 @@ - $statusgruppe->getId()]) ?>"> - course['name'].": ".$statusgruppe->name) ?> - - asimg("20px", ['class' => "text-bottom", 'title' => _("Zuweisung zur Veranstaltung aufheben.")]) ?> - + $statusgruppe->getId()]) ?>"> + course['name'].": ".$statusgruppe->name) ?> + + asimg("20px", ['class' => "text-bottom", 'title' => _("Zuweisung zur Veranstaltung aufheben.")]) ?> + diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php index 0cd4af6..df3677b 100644 --- a/app/views/questionnaire/edit.php +++ b/app/views/questionnaire/edit.php @@ -1,76 +1,187 @@ + 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() + ]; +} +?>
    isNew() ? $questionnaire->getId() : "")) ?>" - method="post" enctype="multipart/form-data" + method="post" + enctype="multipart/form-data" class="questionnaire_edit default" - data-secure="true"> - - - "> - "> - -
    - - -
    + data-questiontypes="" + data-questionnaire_data="" + data-questions_data="" + data-range_type="" + data-range_id="" + + :data-secure="activateFormSecure"> -
    - questions as $index => $question) : ?> - render_partial("questionnaire/_question.php", compact("question")) ?> - -
    - - - - + +
    - + 'STUDIP.Questionnaire.Editor.submit(); return false;']) ?>
    + diff --git a/app/views/questionnaire/evaluate.php b/app/views/questionnaire/evaluate.php index 7a8def7..186ef2b 100644 --- a/app/views/questionnaire/evaluate.php +++ b/app/views/questionnaire/evaluate.php @@ -1,9 +1,27 @@ -
    +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; + } + } +} + +?> +
    resultsVisible()) : ?> questions as $question) : ?> -
    - getResultTemplate() ?> +
    + getResultTemplate($only_user_ids, $filtered[$questionnaire->getId()]['question_id'] === $question->getId() ? $filtered[$questionnaire->getId()]['filterForAnswer'] : null) ?> render(['anonAnswers' => $anonAnswers ?? '']) : _("Ergebnisse konnten nicht ausgewertet werden.") ?>
    @@ -51,6 +69,9 @@ isEditable() && !$questionnaire->isRunning()) : ?> getId())) ?> + resultsVisible()) : ?> + "STUDIP.Questionnaire.exportEvaluationAsPDF(); return false;"]) ?> + isEditable() && $questionnaire->isRunning()) : ?> getId())) ?> diff --git a/app/views/questionnaire/overview.php b/app/views/questionnaire/overview.php index 8529d09..e99ef8d 100644 --- a/app/views/questionnaire/overview.php +++ b/app/views/questionnaire/overview.php @@ -1,5 +1,6 @@
    " method="post"> + @@ -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 0b2643d..13a0b3e 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 @@ etask; - +/** + * @var QuestionnaireQuestion $vote + */ $answer = $vote->getMyAnswer(); $answerdata = $answer['answerdata'] ? $answer['answerdata']->getArrayCopy() : []; ?> 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 e9be20d..0000000 --- a/app/views/questionnaire/question_types/freetext/freetext_edit.php +++ /dev/null @@ -1,18 +0,0 @@ -etask; -?> - - - - diff --git a/app/views/questionnaire/question_types/freetext/freetext_evaluation.php b/app/views/questionnaire/question_types/freetext/freetext_evaluation.php index 6877a72..26608b0 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 @@ etask; +/** + * @var QuestionnaireQuestion $vote + * @var QuestionnaireAnswer[] $answers + */ ?> -

    - description) ?> -

    - +
    +
    + asImg(20) ?> +
    +
    + questiondata['description']) ?> +
    +
      -answers as $answer) : ?> +
    • 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 0000000..04bae4f --- /dev/null +++ b/app/views/questionnaire/question_types/info/info.php @@ -0,0 +1,22 @@ + + +
      +
      + asImg(20) ?> +
      +
      + questiondata['url']) && trim($vote->questiondata['url'])) : ?> + + + questiondata['description']) && trim($vote->questiondata['description'])) : ?> +
      + questiondata['description']) ?> +
      + +
      +
      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 0000000..aa2d9b0 --- /dev/null +++ b/app/views/questionnaire/question_types/likert/likert_answer.php @@ -0,0 +1,51 @@ +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() : []; +?> +
      questiondata['mandatory']) && $vote->questiondata['mandatory'] ? ' class="mandatory"' : "" ?>> + render_partial('questionnaire/_answer_description_container', ['vote' => $vote, 'iconshape' => 'likert']) ?> + + + +
    + + + + + + + + + + + + + + $answer) : ?> + + + + + +
    + + value=""> +
    +
    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 0000000..4ddb12a --- /dev/null +++ b/app/views/questionnaire/question_types/likert/likert_evaluation.php @@ -0,0 +1,70 @@ +questiondata['options']; +?> + +
    +
    + asImg(20) ?> +
    +
    + questiondata) && isset($vote->questiondata['description']) ? $vote->questiondata['description'] : '') ?> +
    +
    + + + + + + + + + + + + questionnaire->countAnswers() ?> + questiondata['statements'] as $key => $statement) : ?> + + + + $option) : ?> + user->getFullName('full'); + } + } + } + $color = 'hsl(0 0% '.round(70 + (30 * (1 - ($hits / $countAnswers ?? 1)) )).'%)'; + ?> + + + + + +
    + + 0 ? 'title="'.htmlReady(implode(', ', $names)).'"' : ''?>> + + + asImg(16, ['class' => 'text-bottom']) ?> + % + + + + % + + +
    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 0000000..3ff00fe --- /dev/null +++ b/app/views/questionnaire/question_types/rangescale/rangescale_answer.php @@ -0,0 +1,51 @@ +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() : []; +?> +
    questiondata['mandatory']) && $vote->questiondata['mandatory'] ? ' class="mandatory"' : "" ?>> + render_partial('questionnaire/_answer_description_container', ['vote' => $vote, 'iconshape' => 'rangescale']) ?> + + + + + + + + questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?> + + + + + + + + + + questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?> + + + + + +
    + + value=""> +
    +
    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 0000000..57f2de6 --- /dev/null +++ b/app/views/questionnaire/question_types/rangescale/rangescale_evaluation.php @@ -0,0 +1,70 @@ +questiondata['minimum'], $vote->questiondata['maximum']); +?> + +
    +
    + asImg(20) ?> +
    +
    + questiondata['description']) ?> +
    +
    + + + + + + questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?> + + + + + + questionnaire->countAnswers() ?> + questiondata['statements'] as $key => $statement) : ?> + + + + + user->getFullName('full'); + } + } + } + $color = 'hsl(0 0% '.round(70 + (30 * (1 - ($hits / $countAnswers ?? 1)) )).'%)'; + ?> + + + + + +
    + + 0 ? 'title="'.htmlReady(implode(', ', $names)).'"' : ''?>> + + + asImg(16, ['class' => 'text-bottom']) ?> + % + + + + % + + +
    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 15f6ba7..0000000 --- a/app/views/questionnaire/question_types/test/_answer.php +++ /dev/null @@ -1,18 +0,0 @@ -
  • - _('Antwort verschieben'), 'class' => 'move' ]) ?> - - 0)) ? 'checked' : '' ?>> - - - - _('Antwort löschen')])->asImg(20, ['class' => 'text-bottom delete']) ?> - _('Antwort hinzufügen')])->asImg(20, ['class' => 'text-bottom add']) ?> -
  • 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 f013fb3..0000000 --- a/app/views/questionnaire/question_types/test/test_edit.php +++ /dev/null @@ -1,62 +0,0 @@ -etask; ?> - - - -render_partial('questionnaire/question_types/test/_answer', [ 'vote' => $vote, 'answer' => [] ]) ?> -
      - 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']) - ] - ); ?> -
    - -
    - -
    - - - - - - - - - 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 6c84e66..0000000 --- a/app/views/questionnaire/question_types/test/test_evaluation.php +++ /dev/null @@ -1,122 +0,0 @@ -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()); -?> - -

    - asImg(20, ['class' => 'text-bottom']) ?> - description) ?> -

    - -answers) > 0 && $numTaskAnswers > 0) : ?> -
    - - - -id, $users) || is_array($anonAnswers)) : ?> -correctAnswered('anonymous', $anonAnswers) : $vote->correctAnswered()?> -
    - - - - - -
    - - - - - questionnaire->countAnswers() ?> - findBy('user_id', $GLOBALS['user']->id)->first()->answerdata ?> - getArrayCopy() ?> - $answer) : ?> - - - - - - - - - - - -
    '); background-repeat: no-repeat;" width="50%"> - - - asImg( ['class' => 'text-bottom']) ?> - - 0) : ?> - _('Diese Antwort ist richtig')])->asImg( ['class' => 'text-bottom']) ?> - - _('Eine falsche Antwort')])->asImg( ['class' => 'text-bottom']) ?> - - - (% | /) - - questionnaire['anonymous'] && $results[$key]) : ?> - - - - $user_id) : ?> - - findOneBy('user_id', $user_id); ?> - - - - username)->getImageTag( - Avatar::SMALL, - ['title' => $user->getFullname('no_title')] - ) ?> - - getFullname('no_title')) ?> - - - - - -
    diff --git a/app/views/questionnaire/question_types/vote/vote_answer.php b/app/views/questionnaire/question_types/vote/vote_answer.php index b42ad03..23f14f4 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; - -$taskAnswers = $etask->task['answers']; -$indexMap = count($taskAnswers) ? range(0, count($taskAnswers) - 1) : []; -if ($etask->options['randomize']) { +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() : []; ?> -
    options['mandatory']) && $etask->options['mandatory'] ? ' class="mandatory"' : "" ?>> -

    - asImg(20, ['class' => 'text-bottom']) ?> - options['mandatory']) && $etask->options['mandatory']) : ?> - asImg(20, ['class' => 'text-bottom', 'title' => _("Pflichtantwort")]) ?> - - description) ?> -

    +
    questiondata['mandatory']) && $vote->questiondata['mandatory'] ? ' class="mandatory"' : "" ?>> + render_partial('questionnaire/_answer_description_container', ['vote' => $vote, 'iconshape' => 'vote']) ?>