Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rebased Keywords personality test to MOODLE_401_STABLE #515

Open
wants to merge 1 commit into
base: MOODLE_401_STABLE
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion classes/feedback_section_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
35 changes: 29 additions & 6 deletions classes/question/question.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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++;
Expand Down
10 changes: 10 additions & 0 deletions fbsections.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
3 changes: 2 additions & 1 deletion locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
108 changes: 86 additions & 22 deletions questionnaire.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
}
Expand Down Expand Up @@ -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;
}
}
}
}
}
Expand Down Expand Up @@ -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);
}
}
}
}
Expand Down
131 changes: 131 additions & 0 deletions tests/behat/keywords_personality_test.feature
Original file line number Diff line number Diff line change
@@ -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