diff --git a/classes/feedback_section_form.php b/classes/feedback_section_form.php index 740d5604..3f4261ff 100644 --- a/classes/feedback_section_form.php +++ b/classes/feedback_section_form.php @@ -43,6 +43,7 @@ public function definition() { $feedbacksection = $this->_customdata->feedbacksection; $validquestions = $this->_customdata->validquestions; $survey = $this->_customdata->survey; + $canselectquestions = $this->_customdata->canselectquestions; $feedbacksections = $questionnaire->survey->feedbacksections; $this->_feedbacks = $feedbacksection->sectionfeedback; $this->context = $questionnaire->context; @@ -89,7 +90,7 @@ public function definition() { $mform->setDefault('feedbacknotes', $questionnaire->survey->feedbacknotes); $mform->addHelpButton('sectionheading', 'feedbackheading', 'questionnaire'); - if ($questionnaire->survey->feedbacksections > 0) { + if ($questionnaire->survey->feedbacksections > 0 && $canselectquestions) { // Sections. if ($survey->feedbacksections > 1) { $mform->addElement('header', 'fbsection_' . $feedbacksection->id, diff --git a/classes/question/question.php b/classes/question/question.php index 5207a529..ff123ef1 100644 --- a/classes/question/question.php +++ b/classes/question/question.php @@ -512,6 +512,26 @@ public function valid_feedback() { return false; } + /** + * True if the question supports feedback and has keywords instead of score value (for DISC personality test). + * Override if the default logic is not enough. + * @return bool + */ + public function has_keywords() { + if ($this->supports_feedback() && $this->has_choices() && $this->required() && !empty($this->name)) { + foreach ($this->choices as $choice) { + if ($choice->value !== null) { + // D param means no digits. + $r = preg_match_all("/(\D+)/", $choice->value, $matches); + if ($r) { + return true; + } + } + } + } + return false; + } + /** * Provide the feedback scores for all requested response id's. This should be provided only by questions that provide feedback. * @param array $rids @@ -534,7 +554,7 @@ public function get_feedback_maxscore() { if ($this->valid_feedback()) { $maxscore = 0; foreach ($this->choices as $choice) { - if (isset($choice->value) && ($choice->value != null)) { + if (isset($choice->value) && ($choice->value != null) && is_numeric($choice->value)) { if ($choice->value > $maxscore) { $maxscore = $choice->value; } @@ -1373,8 +1393,8 @@ public function form_update($formdata, $questionnaire) { $choicerecord->id = $ekey; $choicerecord->question_id = $this->qid; $choicerecord->content = trim($newchoices[$nidx]); - $r = preg_match_all("/^(\d{1,2})(=.*)$/", $newchoices[$nidx], $matches); - // This choice has been attributed a "score value" OR this is a rate question type. + $r = preg_match_all("/^(\d{1,2}|\D.*)=(.*)$/", $newchoices[$nidx], $matches); + // This choice has been attributed a "score value" or a DISC keyword OR this is a rate question type. if ($r) { $newscore = $matches[1][0]; $choicerecord->value = $newscore; @@ -1394,10 +1414,13 @@ public function form_update($formdata, $questionnaire) { $choicerecord = new \stdClass(); $choicerecord->question_id = $this->qid; $choicerecord->content = trim($newchoices[$nidx]); - $r = preg_match_all("/^(\d{1,2})(=.*)$/", $choicerecord->content, $matches); - // This choice has been attributed a "score value" OR this is a rate question type. + $r = preg_match_all("/^(\d{1,2}|\D.*)=(.*)$/", $choicerecord->content, $matches); + // This choice has been attributed a "score value" or a DISC keyword OR this is a rate question type. if ($r) { - $choicerecord->value = $matches[1][0]; + $newscore = $matches[1][0]; + $choicerecord->value = $newscore; + } else { // No score value for this choice. + $choicerecord->value = null; } $this->add_choice($choicerecord); $nidx++; diff --git a/fbsections.php b/fbsections.php index 13f3a8e7..cc20209f 100644 --- a/fbsections.php +++ b/fbsections.php @@ -111,9 +111,19 @@ } } +// Do not display Feedback questions option if this is a DISC questionnaire. +$canselectquestions = true; +foreach ($questionnaire->questions as $question) { + if ($question->has_keywords()) { + $canselectquestions = false; + break; + } +} + $customdata = new stdClass(); $customdata->feedbacksection = $feedbacksection; $customdata->validquestions = $validquestions; +$customdata->canselectquestions = $canselectquestions; $customdata->survey = $questionnaire->survey; $customdata->sectionselect = $DB->get_records_menu('questionnaire_fb_sections', ['surveyid' => $questionnaire->survey->id], 'section', 'id,sectionlabel'); diff --git a/locallib.php b/locallib.php index 4fce8590..55f861be 100644 --- a/locallib.php +++ b/locallib.php @@ -114,7 +114,8 @@ function questionnaire_choice_values($content) { } // Check for score value first (used e.g. by personality test feature). - $r = preg_match_all("/^(\d{1,2}=)(.*)$/", $content, $matches); + // JR added accept string in case of DISC personality test with keywords. + $r = preg_match_all("/^(\d{1,2}|\D.*)=(.*)$/", $content, $matches); if ($r) { $content = $matches[2][0]; } diff --git a/questionnaire.class.php b/questionnaire.class.php index 738c4c07..da8a206e 100644 --- a/questionnaire.class.php +++ b/questionnaire.class.php @@ -3736,16 +3736,51 @@ public function response_analysis($rid, $resps, $compare, $isgroupmember, $allre // Calculate max score per question in questionnaire. $qmax = []; $maxtotalscore = 0; + $nbquestionswithkeywords = 0; + // Calculate max score per questionnaire by adding nb of questions with keywords. + $thisquestionnairehaskeywords = false; foreach ($this->questions as $question) { - $qid = $question->id; - if ($question->valid_feedback()) { + if ($question->has_keywords()) { + $thisquestionnairehaskeywords = true; + $maxtotalscore++; + $nbquestionswithkeywords++; + } + } + if (!$thisquestionnairehaskeywords) { + foreach ($this->questions as $question) { + $qid = $question->id; $qmax[$qid] = $question->get_feedback_maxscore(); $maxtotalscore += $qmax[$qid]; // Get all the feedback scores for this question. $responsescores[$qid] = $question->get_feedback_scores($rids); } + } else { + foreach ($this->questions as $question) { + $qid = $question->id; + if ($question->has_keywords() ) { + // Get all the feedback scores (actually keywords) for this question. + $responsescores[$qid] = $question->get_feedback_scores($rids); + } + } + } + + /** + * Returns the number of responses containing the keyword in specified response. + * @param array $responsescores The array of response scores. + * @param string $keyword + * @param int $rid + * @return number + */ + function countscore($responsescores, $keyword, $rid) { + $count = 0; + foreach ($responsescores as $subarray) { + if (isset($subarray[$rid]) && $subarray[$rid]->score === $keyword) { + $count++; + } + } + return $count; } - // Just in case no values have been entered in the various questions possible answers field. + if ($maxtotalscore === 0) { return ''; } @@ -3774,7 +3809,11 @@ public function response_analysis($rid, $resps, $compare, $isgroupmember, $allre } // Only add current score if conditions below are met. if ($groupmode == 0 || $isgroupmember || (!$isgroupmember && $rrid != $rid) || $allresponses) { - $allqscore[$qid] += $response->score; + if (!empty($responsescore)) { + if (!$thisquestionnairehaskeywords) { + $allqscore[$qid] += $response->score; + } + } } } } @@ -3887,27 +3926,52 @@ public function response_analysis($rid, $resps, $compare, $isgroupmember, $allre if (($filteredsections != null) && !in_array($section, $filteredsections)) { continue; } - foreach ($fbsections as $key => $fbsection) { - if ($fbsection->section == $section) { - $feedbacksectionid = $key; - $scorecalculation = section::decode_scorecalculation($fbsection->scorecalculation); - if (empty($scorecalculation) && !is_array($scorecalculation)) { - $scorecalculation = []; + if (!$thisquestionnairehaskeywords) { + foreach ($fbsections as $key => $fbsection) { + if ($fbsection->section == $section) { + $feedbacksectionid = $key; + $scorecalculation = section::decode_scorecalculation($fbsection->scorecalculation); + if (empty($scorecalculation) && !is_array($scorecalculation)) { + $scorecalculation = []; + } + $sectionheading = $fbsection->sectionheading; + $imageid = $fbsection->id; + $chartlabels[$section] = $fbsection->sectionlabel; + } + } + foreach ($scorecalculation as $qid => $key) { + // Just in case a question pertaining to a section has been deleted or made not required + // after being included in scorecalculation. + if (isset($qscore[$qid])) { + $key = ($key == 0) ? 1 : $key; + $score[$section] += round($qscore[$qid] * $key); + $maxscore[$section] += round($qmax[$qid] * $key); + if ($compare || $allresponses) { + $allscore[$section] += round($allqscore[$qid] * $key); + } + } + } + } else { + foreach ($fbsections as $key => $fbsection) { + if ($fbsection->section == $section) { + $feedbacksectionid = $key; + $sectionheading = $fbsection->sectionheading; + $imageid = $fbsection->id; + $chartlabels[$section] = $fbsection->sectionlabel; + $score[$section] = countscore($responsescores, $fbsection->sectionlabel, $rid); } - $sectionheading = $fbsection->sectionheading; - $imageid = $fbsection->id; - $chartlabels[$section] = $fbsection->sectionlabel; } + // Set maxscore for all sections to nb of questions with keywords. + $maxscore[$section] = $nbquestionswithkeywords; } - foreach ($scorecalculation as $qid => $key) { - // Just in case a question pertaining to a section has been deleted or made not required - // after being included in scorecalculation. - if (isset($qscore[$qid])) { - $key = ($key == 0) ? 1 : $key; - $score[$section] += round($qscore[$qid] * $key); - $maxscore[$section] += round($qmax[$qid] * $key); - if ($compare || $allresponses) { - $allscore[$section] += round($allqscore[$qid] * $key); + + if ($thisquestionnairehaskeywords && ($compare || $allresponses)) { + foreach ($rids as $key => $rid) { + foreach ($fbsections as $key => $fbsection) { + if ($fbsection->section == $section) { + $keyword = $fbsection->sectionlabel; + $allscore[$section] += countscore($responsescores, $keyword, $rid); + } } } } diff --git a/tests/behat/keywords_personality_test.feature b/tests/behat/keywords_personality_test.feature new file mode 100644 index 00000000..44b3ad10 --- /dev/null +++ b/tests/behat/keywords_personality_test.feature @@ -0,0 +1,131 @@ +@mod @mod_questionnaire +Feature: In questionnaire, keywords (DISC) personality tests can be constructed using feedback on specific question responses and questions can be + assigned to multiple sections. + In order to define a DISC personality test (Dominance, Inducement, Submission, and Compliance). + As a teacher + I must add the required question types and complete the feedback options with more than one section per question. + + @javascript + Scenario: Create a questionnaire with a feeback question types and add more than one feedback section. + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + And the following "activities" exist: + | activity | name | description | course | idnumber | resume | navigate | + | questionnaire | Test questionnaire | Test questionnaire description | C1 | questionnaire0 | 1 | 1 | + And the "multilang" filter is "on" + And the "multilang" filter applies to "content and headings" + And I am on the "Test questionnaire" "mod_questionnaire > questions" page logged in as "teacher1" + Then I should see "Add questions" + And I add a "Radio Buttons" question and I fill the form with: + | Question Name | Q1 | + | Yes | y | + | Horizontal | Checked | + | Question Text | When faced with a challenge, how do you react? | + | Possible answers | Dominant=Take charge immediately.,Conscientious=Carefully assess the situation before acting.,Steady=Seek guidance and support from others.,Influential=Avoid confrontation and hope the issue resolves itself. | + Then I should see "[Radio Buttons] (Q1)" + And I add a "Radio Buttons" question and I fill the form with: + | Question Name | Q2 | + | Yes | y | + | Horizontal | Checked | + | Question Text | How do you approach social situations? | + | Possible answers | Influential=Eagerly initiate conversations and take the lead.,Conscientious=Observe and listen before contributing.,Steady=Form close connections with a few individuals.,Dominant=Prefer to spend time alone or with a small group of close friends. | + Then I should see "[Radio Buttons] (Q2)" + And I add a "Radio Buttons" question and I fill the form with: + | Question Name | Q3 | + | Yes | y | + | Horizontal | Checked | + | Question Text | How do you handle unexpected changes to your plans? | + | Possible answers | Conscientious=Feel uneasy and take time to adjust.,Steady=Seek support and guidance from others.,Influential=Quickly adapt and find alternative solutions.,Dominant=Stick to the original plan and hope for the best. | + Then I should see "[Radio Buttons] (Q3)" + And I add a "Radio Buttons" question and I fill the form with: + | Question Name | Q4 | + | Yes | y | + | Horizontal | Checked | + | Question Text | How do you express your emotions? | + | Possible answers | Steady=Carefully and considerately.,Dominant=Privately and discreetly.,Influential=Openly and passionately.,Conscientious=Thoughtfully and logically. | + Then I should see "[Radio Buttons] (Q4)" + And I follow "Feedback" + And I should see "Feedback options" + And I set the field "id_feedbacksections" to "Feedback sections" + And I set the field "id_feedbackscores" to "Yes" + And I set the field "id_feedbacknotes" to "These are the main Feedback notes" + And I press "Save settings and edit Feedback Sections" + Then I should see "[New section] section heading" + And I should not see "[New section] section questions" + And I set the field "id_sectionlabel" to "Conscientious" + And I set the field "id_sectionheading" to "Conscientious" + And I press "Save changes" + And I follow "Conscientious section messages" + And I set the field "id_feedbacktext_0" to "Feedback 1 100%" + And I set the field "id_feedbackboundaries_0" to "50" + And I set the field "id_feedbacktext_1" to "Feedback 1 50%" + And I set the field "id_feedbackboundaries_1" to "20" + And I set the field "id_feedbacktext_2" to "Feedback 1 20%" + And I press "Save changes" + And I set the field "id_newsectionlabel" to "Dominant" + And I press "Add new section" + And I set the field "id_sectionheading" to "Dominant" + And I press "Save changes" + And I follow "Dominant section messages" + And I set the field "id_feedbacktext_0" to "Feedback 2 100%" + And I set the field "id_feedbackboundaries_0" to "50" + And I set the field "id_feedbacktext_1" to "Feedback 2 50%" + And I set the field "id_feedbackboundaries_1" to "20" + And I set the field "id_feedbacktext_2" to "Feedback 2 20%" + And I press "Save changes" + And I set the field "id_newsectionlabel" to "Influential" + And I press "Add new section" + And I set the field "id_sectionheading" to "Influential" + And I press "Save changes" + And I follow "Influential section messages" + And I set the field "id_feedbacktext_0" to "Feedback 3 100%" + And I set the field "id_feedbackboundaries_0" to "50" + And I set the field "id_feedbacktext_1" to "Feedback 3 50%" + And I set the field "id_feedbackboundaries_1" to "20" + And I set the field "id_feedbacktext_2" to "Feedback 3 20%" + And I press "Save changes" + And I set the field "id_newsectionlabel" to "Steady" + And I press "Add new section" + And I set the field "id_sectionheading" to "Steady" + And I press "Save changes" + And I follow "Steady section messages" + And I set the field "id_feedbacktext_0" to "Feedback 4 100%" + And I set the field "id_feedbackboundaries_0" to "50" + And I set the field "id_feedbacktext_1" to "Feedback 4 50%" + And I set the field "id_feedbackboundaries_1" to "20" + And I set the field "id_feedbacktext_2" to "Feedback 4 20%" + And I press "Save changes" + And I log out + +# Scenario: Student completes feedback questions. + And I log in as "student1" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + And I navigate to "Answer the questions..." in current page administration + Then I should see "When faced with a challenge, how do you react?" + And I click on "Take charge immediately" "radio" + And I click on "Eagerly initiate conversations and take the lead." "radio" + And I click on "Quickly adapt and find alternative solutions." "radio" + And I click on "Thoughtfully and logically." "radio" + And I press "Submit questionnaire" + Then I should see "Thank you for completing this Questionnaire." + And I press "Continue" + Then I should see "View your response(s)" + Then I should see "Conscientious" + And I should see "25%" + And I should see "Dominant" + And I should see "25%" + And I should see "Influential" + And I should see "50%" + And I should see "Steady" + And I should see "0%" + And I log out