From 0d595d8ce038724ac98e4e1a52396fcd6e9880c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michaela=20Br=C3=BCckner?= Date: Mon, 2 Feb 2026 15:49:36 +0100 Subject: separate questions from design elements, re #6193 --- app/views/questionnaire/edit.php | 6 +- lib/classes/QuestionElements.php | 118 +++++++++++++++++++++ lib/classes/QuestionType.php | 6 ++ lib/models/BlankLine.php | 5 + lib/models/Divider.php | 5 + lib/models/Freetext.php | 5 + lib/models/Headline.php | 7 ++ lib/models/LikertScale.php | 5 + lib/models/QuestionnaireAutomatedData.php | 5 + lib/models/QuestionnaireInfo.php | 5 + lib/models/RangeScale.php | 5 + lib/models/Vote.php | 5 + .../apps/questionnaires/QuestionnaireEditor.vue | 29 ++++- .../vue/components/questionnaires/DividerEdit.vue | 5 +- 14 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 lib/classes/QuestionElements.php diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php index 9fdffab..3061ebf 100644 --- a/app/views/questionnaire/edit.php +++ b/app/views/questionnaire/edit.php @@ -7,7 +7,8 @@ $questiontypes['Vote'] = [ 'name' => Vote::getName(), 'type' => Vote::class, 'icon' => Vote::getIconShape(), - 'component' => Vote::getEditingComponent() + 'component' => Vote::getEditingComponent(), + 'is_design_element' => false, ]; foreach (get_declared_classes() as $class) { if ( @@ -19,7 +20,8 @@ foreach (get_declared_classes() as $class) { 'name' => $class::getName(), 'type' => $class, 'icon' => $class::getIconShape(), - 'component' => $class::getEditingComponent() + 'component' => $class::getEditingComponent(), + 'is_design_element' => $class::isDesignElement() ]; } } diff --git a/lib/classes/QuestionElements.php b/lib/classes/QuestionElements.php new file mode 100644 index 0000000..023a679 --- /dev/null +++ b/lib/classes/QuestionElements.php @@ -0,0 +1,118 @@ +questionnaire. + */ +interface QuestionElements { + + /** + * Returns a specific icon for this type of question. Note this is not bound to the + * object but called staticly. + * @return Icon the specific icon for this type of question + */ + 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" + * This name is not showed to the participant of the questionnaire, but to the editor. + * It might get displayed like "add another Frage to questionnaire", where 'Frage' + * is the name of the type. + * @return string : the name of this type of question. + */ + static public function getName(); + + /** + * 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 + */ + static public function getEditingComponent(); + + /** + * 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 beforeStoringQuestiondata($questiondata); + + /** + * Display the question to the user. This template will be embedded into a + * html
-tag. Maybe more questions will appear in that form and maybe + * more questions of the same type. This is important to know, so that you + * prefix all the input-fields. + * + * Wrong: "> + * + * Right: getId()]['a'] ? " checked" : "" ?>"> + * + * Try to prefix all your input variables at least with the id of the question, + * so that they will never conflict with other variables. + * + * @return Flexi\Template + */ + public function getDisplayTemplate(); + + /** + * Uses current user and Request-variables to create an answer for the question. + * Return this answer. It does not necessarily be stored to the database! + * @return QuestionnaireAnswer or derived + */ + 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. + * this is used to show only a subset of results to the user for + * visible evaluation of the results. If the questionnaire is anonymous + * just do nothing. + * + * @return Flexi\Template + */ + public function getResultTemplate($only_user_ids = null); + + /** + * This method is called to generate a csv-export from a whole questionnaire. + * The returned array looks like this: + * array( + * 'Answer 1' => array('e7a0a84b161f3e8c09b4a0a2e8a58147' => "1", '7e81ec247c151c02ffd479511e24cc03' => "0"), + * 'Answer 2' => array('e7a0a84b161f3e8c09b4a0a2e8a58147' => "1", '7e81ec247c151c02ffd479511e24cc03' => "1") + * ) + * This is a two-dimensional array. The first array provides a set of answers. The values of + * this array are themselves arrays with the user_ids as indexes and the user's + * answers as the values. With this construction you can evaluate single-choice tests + * as well as multiple-choice tests. + * @return array : indexed with user_id and valued with the value of the answer. + * If your QuestionType allows more than one value (i.e. multiple-choice) + * you might need to serialize it. + */ + public function getResultArray(); + + /** + * A method to be called after the questionnaire has ended. + * @return void + */ + public function onEnding(); +} diff --git a/lib/classes/QuestionType.php b/lib/classes/QuestionType.php index 03019fe..e4f5747 100644 --- a/lib/classes/QuestionType.php +++ b/lib/classes/QuestionType.php @@ -40,6 +40,12 @@ interface QuestionType { static public function getEditingComponent(); /** + * + */ + + static public function isDesignElement(); + + /** * 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. diff --git a/lib/models/BlankLine.php b/lib/models/BlankLine.php index 6bd297a..fea985f 100644 --- a/lib/models/BlankLine.php +++ b/lib/models/BlankLine.php @@ -16,6 +16,11 @@ */ class BlankLine extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return true; + } + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info'); diff --git a/lib/models/Divider.php b/lib/models/Divider.php index 8a17fed..eae2cec 100644 --- a/lib/models/Divider.php +++ b/lib/models/Divider.php @@ -16,6 +16,11 @@ */ class Divider extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return true; + } + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info'); diff --git a/lib/models/Freetext.php b/lib/models/Freetext.php index 395836a..d2f8f16 100644 --- a/lib/models/Freetext.php +++ b/lib/models/Freetext.php @@ -19,6 +19,11 @@ require_once 'lib/classes/QuestionType.php'; */ class Freetext extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return false; + } + /** * Returns the Icon-object to this QuestionType. * @param bool $active: true if Icon should be clickable, false for black info-icon. diff --git a/lib/models/Headline.php b/lib/models/Headline.php index 757567b..84a4194 100644 --- a/lib/models/Headline.php +++ b/lib/models/Headline.php @@ -16,6 +16,13 @@ */ class Headline extends QuestionnaireQuestion implements QuestionType { + + public static function isDesignElement() + { + return true; + } + + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info'); diff --git a/lib/models/LikertScale.php b/lib/models/LikertScale.php index cc17c3d..5ffddf0 100644 --- a/lib/models/LikertScale.php +++ b/lib/models/LikertScale.php @@ -16,6 +16,11 @@ */ class LikertScale extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return false; + } + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info'); diff --git a/lib/models/QuestionnaireAutomatedData.php b/lib/models/QuestionnaireAutomatedData.php index cb58b7f..10375ee 100644 --- a/lib/models/QuestionnaireAutomatedData.php +++ b/lib/models/QuestionnaireAutomatedData.php @@ -17,6 +17,11 @@ */ class QuestionnaireAutomatedData extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return false; + } + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? Icon::ROLE_CLICKABLE : Icon::ROLE_INFO); diff --git a/lib/models/QuestionnaireInfo.php b/lib/models/QuestionnaireInfo.php index f0b277a..ce54ee6 100644 --- a/lib/models/QuestionnaireInfo.php +++ b/lib/models/QuestionnaireInfo.php @@ -16,6 +16,11 @@ */ class QuestionnaireInfo extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return true; + } + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? 'clickable' : 'info'); diff --git a/lib/models/RangeScale.php b/lib/models/RangeScale.php index de51114..2ed49bf 100644 --- a/lib/models/RangeScale.php +++ b/lib/models/RangeScale.php @@ -16,6 +16,11 @@ */ class RangeScale extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return false; + } + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? Icon::ROLE_CLICKABLE : Icon::ROLE_INFO); diff --git a/lib/models/Vote.php b/lib/models/Vote.php index 9941f7e..7c0b32b 100644 --- a/lib/models/Vote.php +++ b/lib/models/Vote.php @@ -16,6 +16,11 @@ */ class Vote extends QuestionnaireQuestion implements QuestionType { + public static function isDesignElement() + { + return false; + } + public static function getIcon(bool $active = false) : Icon { return Icon::create(static::getIconShape(), $active ? Icon::ROLE_CLICKABLE : Icon::ROLE_INFO); diff --git a/resources/vue/apps/questionnaires/QuestionnaireEditor.vue b/resources/vue/apps/questionnaires/QuestionnaireEditor.vue index 26b2f32..702de52 100644 --- a/resources/vue/apps/questionnaires/QuestionnaireEditor.vue +++ b/resources/vue/apps/questionnaires/QuestionnaireEditor.vue @@ -75,8 +75,19 @@
+ {{ $gettext('Fragetypen') }}
- +
+ {{ $gettext('Designelemente') }} +
+