Skip to content

Commit 8ed9ad7

Browse files
toanlamttoanlam
andauthored
Add free text (Other) to rate/scale. (#604)
* Questionnaire: amend the 'Scale' question so that if you have 'Other', there's a text field underneath * Questionnaire: List each Other choice individually on the summary page. --------- Co-authored-by: toanlam <[email protected]>
1 parent 22e9b97 commit 8ed9ad7

File tree

6 files changed

+226
-37
lines changed

6 files changed

+226
-37
lines changed

classes/question/rate.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,22 @@ protected function question_survey_display($response, $descendantsdata, $blankqu
336336
if ($this->osgood_rate_scale()) {
337337
list($content, $contentright) = array_merge(preg_split('/[|]/', $content), array(' '));
338338
}
339-
$cols[] = ['colstyle' => 'text-align: '.$textalign.';',
340-
'coltext' => format_text($content, FORMAT_HTML, ['noclean' => true]).'&nbsp;'];
339+
if ($choice->is_other_choice()) {
340+
$othertext = $choice->other_choice_display();
341+
$oname = $cid . '_qother';
342+
$oid = $cid . '-other';
343+
$odata = isset($response->answers[$this->id][$cid]) ? $response->answers[$this->id][$cid]->value : '';
344+
if (isset($odata)) {
345+
$ovalue = stripslashes($odata);
346+
}
347+
$content = $othertext;
348+
$cols[] = ['oname' => $oname, 'oid' => $oid, 'ovalue' => $ovalue,
349+
'colstyle' => 'text-align: ' . $textalign . ';',
350+
'coltext' => format_text($content, FORMAT_HTML, ['noclean' => true]) . '&nbsp;'];
351+
} else {
352+
$cols[] = ['colstyle' => 'text-align: '.$textalign.';',
353+
'coltext' => format_text($content, FORMAT_HTML, ['noclean' => true]) . '&nbsp;'];
354+
}
341355

342356
$bg = 'c0 raterow';
343357
$hasnotansweredchoice = false;
@@ -512,6 +526,12 @@ protected function response_survey_display($response) {
512526
if ($this->osgood_rate_scale()) {
513527
list($content, $contentright) = array_merge(preg_split('/[|]/', $content), array(' '));
514528
}
529+
if ($choice->is_other_choice()) {
530+
$content = $choice->other_choice_display();
531+
if (isset($response->answers[$this->id][$cid]->otheresponse)) {
532+
$rowobj->othercontent = $response->answers[$this->id][$cid]->otheresponse;
533+
}
534+
}
515535
$rowobj->content = format_text($content, FORMAT_HTML, ['noclean' => true]).'&nbsp;';
516536
$bg = 'c0';
517537
$cols = [];

classes/responsetype/answer/answer.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,25 @@ class answer {
4343
/** @var string $value The value of this response (if applicable). */
4444
public $value;
4545

46+
/** @var string $value The other value of this response (if applicable). */
47+
public $otheresponse;
48+
4649
/**
4750
* Answer constructor.
4851
* @param null $id
4952
* @param null $responseid
5053
* @param null $questionid
5154
* @param null $choiceid
55+
* @param null $otheresponse
5256
* @param null $value
5357
*/
54-
public function __construct($id = null, $responseid = null, $questionid = null, $choiceid = null, $value = null) {
58+
public function __construct($id = null, $responseid = null, $questionid = null, $choiceid = null, $value = null, $otheresponse = null) {
5559
$this->id = $id;
5660
$this->responseid = $responseid;
5761
$this->questionid = $questionid;
5862
$this->choiceid = $choiceid;
5963
$this->value = $value;
64+
$this->otheresponse = $otheresponse;
6065
}
6166

6267
/**
@@ -78,6 +83,6 @@ public static function create_from_data($answerdata) {
7883
}
7984

8085
return new answer($answerdata['id'], $answerdata['responseid'], $answerdata['questionid'], $answerdata['choiceid'],
81-
$answerdata['value']);
86+
$answerdata['value'], $answerdata['otheresponse']);
8287
}
8388
}

classes/responsetype/rank.php

Lines changed: 136 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ public function insert_response($responsedata) {
132132
$record->choice_id = $answer->choiceid;
133133
$record->rankvalue = $answer->value;
134134
$resid = $DB->insert_record(static::response_table(), $record);
135+
if (isset($responsedata->{$answer->choiceid . '_qother'})) {
136+
$otherrecord = new \stdClass();
137+
$otherrecord->response_id = $response->id;
138+
$otherrecord->question_id = $this->question->id;
139+
$otherrecord->choice_id = $answer->choiceid;
140+
$otherrecord->response = $responsedata->{$answer->choiceid . '_qother'};
141+
$DB->insert_record('questionnaire_response_other', $otherrecord);
142+
}
135143
}
136144
}
137145
return $resid;
@@ -148,18 +156,29 @@ public function get_results($rids=false, $anonymous=false) {
148156
global $DB;
149157

150158
$rsql = '';
159+
$params = [];
151160
if (!empty($rids)) {
152161
list($rsql, $params) = $DB->get_in_or_equal($rids);
153162
$rsql = ' AND response_id ' . $rsql;
154163
}
164+
// Get other choices.
165+
$otherrecs = $this->get_other_choice($rsql, $params);
155166

156-
$select = 'question_id=' . $this->question->id . ' AND content NOT LIKE \'!other%\' ORDER BY id ASC';
167+
$select = 'question_id=' . $this->question->id . ' ORDER BY id ASC';
157168
if ($rows = $DB->get_records_select('questionnaire_quest_choice', $select)) {
158169
foreach ($rows as $row) {
159-
$this->counts[$row->content] = new \stdClass();
160170
$nbna = $DB->count_records(static::response_table(), array('question_id' => $this->question->id,
161-
'choice_id' => $row->id, 'rankvalue' => '-1'));
162-
$this->counts[$row->content]->nbna = $nbna;
171+
'choice_id' => $row->id, 'rankvalue' => '-1'));
172+
if (\mod_questionnaire\question\choice::content_is_other_choice($row->content) && !empty($otherrecs)) {
173+
foreach (array_keys($otherrecs) as $key) {
174+
$this->counts[$key] = new \stdClass();
175+
$this->counts[$key]->nbna = $nbna;
176+
$this->counts[$key]->content = $row->content;
177+
}
178+
} else {
179+
$this->counts[$row->content] = new \stdClass();
180+
$this->counts[$row->content]->nbna = $nbna;
181+
}
163182
}
164183
}
165184

@@ -193,14 +212,30 @@ public function get_results($rids=false, $anonymous=false) {
193212
}
194213

195214
$sql = "SELECT c.id, c.content, a.average, a.num
196-
FROM {questionnaire_quest_choice} c
197-
INNER JOIN
215+
FROM {questionnaire_quest_choice} c
216+
INNER JOIN
198217
(SELECT c2.id, AVG(a2.rankvalue) AS average, COUNT(a2.response_id) AS num
199-
FROM {questionnaire_quest_choice} c2, {".static::response_table()."} a2
200-
WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id AND a2.rankvalue >= 0{$rsql}
201-
GROUP BY c2.id) a ON a.id = c.id
202-
order by c.id";
218+
FROM {questionnaire_quest_choice} c2, {".static::response_table()."} a2
219+
WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id
220+
AND a2.rankvalue >= 0 AND c2.content NOT LIKE '!other%'{$rsql}
221+
GROUP BY c2.id) a ON a.id = c.id
222+
ORDER BY c.id";
203223
$results = $DB->get_records_sql($sql, array_merge(array($this->question->id, $this->question->id), $params));
224+
225+
// Handle 'other...'.
226+
if ($otherrecs) {
227+
$i = 1;
228+
foreach ($otherrecs as $rec) {
229+
$results['other'.$i] = new \stdClass();
230+
$results['other'.$i]->id = $rec->cid;
231+
$results['other'.$i]->content = $rec->response;
232+
$results['other'.$i]->average = $rec->average;
233+
$results['other'.$i]->num = $rec->num;
234+
$results['other'.$i]->isother = true;
235+
$i++;
236+
}
237+
}
238+
204239
if (!empty ($rankvalue)) {
205240
foreach ($results as $key => $result) {
206241
if (isset($value[$key])) {
@@ -217,24 +252,68 @@ public function get_results($rids=false, $anonymous=false) {
217252
// Case where scaleitems is less than possible choices.
218253
} else {
219254
$sql = "SELECT c.id, c.content, a.sum, a.num
220-
FROM {questionnaire_quest_choice} c
221-
INNER JOIN
255+
FROM {questionnaire_quest_choice} c
256+
INNER JOIN
222257
(SELECT c2.id, SUM(a2.rankvalue) AS sum, COUNT(a2.response_id) AS num
223-
FROM {questionnaire_quest_choice} c2, {".static::response_table()."} a2
224-
WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id AND a2.rankvalue >= 0{$rsql}
225-
GROUP BY c2.id) a ON a.id = c.id";
258+
FROM {questionnaire_quest_choice} c2, {".static::response_table()."} a2
259+
WHERE c2.question_id = ? AND a2.question_id = ? AND a2.choice_id = c2.id
260+
AND a2.rankvalue >= 0 AND c2.content NOT LIKE '!other%'{$rsql}
261+
GROUP BY c2.id) a ON a.id = c.id";
262+
226263
$results = $DB->get_records_sql($sql, array_merge(array($this->question->id, $this->question->id), $params));
264+
265+
if ($otherrecs) {
266+
$i = 1;
267+
foreach ($otherrecs as $rec) {
268+
$results['other'.$i] = new \stdClass();
269+
$results['other'.$i]->id = $rec->cid;
270+
$results['other'.$i]->content = $rec->response;
271+
$results['other'.$i]->sum = $rec->average;
272+
$results['other'.$i]->num = $rec->num;
273+
$results['other'.$i]->isother = true;
274+
$i++;
275+
}
276+
}
227277
// Formula to calculate the best ranking order.
228278
$nbresponses = count($rids);
229279
foreach ($results as $key => $result) {
230-
$result->average = ($result->sum + ($nbresponses - $result->num) * ($this->length + 1)) / $nbresponses;
280+
if (isset($this->length)) {
281+
$result->average = ($result->sum + ($nbresponses - $result->num) * ($this->length + 1)) / $nbresponses;
282+
} else {
283+
$result->average = ($result->sum + ($nbresponses - $result->num) * 1 ) / $nbresponses;
284+
}
231285
$results[$result->content] = $result;
232286
unset($results[$key]);
233287
}
234288
return $results;
235289
}
236290
}
237291

292+
/**
293+
* Get a list of other available choices.
294+
*
295+
* @param string $rsql
296+
* @param array $params
297+
* @return array
298+
*/
299+
public function get_other_choice(string $rsql, array $params): array {
300+
global $DB;
301+
$osql = "SELECT ro.response, AVG(a.rankvalue) AS average, COUNT(a.response_id) AS num, c.id as cid
302+
FROM {questionnaire_quest_choice} c
303+
INNER JOIN {" . static::response_table() . "} a ON a.choice_id = c.id
304+
AND a.rankvalue >= 0
305+
AND a.question_id = c.question_id{$rsql}
306+
INNER JOIN {questionnaire_response_other} ro ON ro.choice_id = c.id
307+
AND ro.response_id = a.response_id
308+
AND ro.question_id = c.question_id
309+
WHERE c.question_id = ?
310+
AND c.content = '!other'
311+
AND ro.response <> ''
312+
GROUP BY ro.response, cid
313+
ORDER BY cid";
314+
return $DB->get_records_sql($osql, array_merge($params, [$this->question->id]));
315+
}
316+
238317
/**
239318
* Provide the feedback scores for all requested response id's. This should be provided only by questions that provide feedback.
240319
* @param array $rids
@@ -417,9 +496,17 @@ public static function response_answers_by_question($rid) {
417496
global $DB;
418497

419498
$answers = [];
420-
$sql = 'SELECT id, response_id as responseid, question_id as questionid, choice_id as choiceid, rankvalue as value ' .
421-
'FROM {' . static::response_table() .'} ' .
422-
'WHERE response_id = ? ';
499+
$sql = 'SELECT r.id,
500+
r.response_id AS responseid,
501+
r.question_id AS questionid,
502+
r.choice_id AS choiceid,
503+
r.rankvalue AS value,
504+
rt.response AS otheresponse
505+
FROM {' . static::response_table() . '} r
506+
LEFT JOIN {questionnaire_response_other} rt ON rt.choice_id = r.choice_id
507+
AND r.question_id = rt.question_id
508+
AND r.response_id = rt.response_id
509+
WHERE r.response_id = ?';
423510
$records = $DB->get_records_sql($sql, [$rid]);
424511
foreach ($records as $record) {
425512
$answers[$record->questionid][$record->choiceid] = answer\answer::create_from_data($record);
@@ -637,6 +724,11 @@ private function mkresavg($sort, $stravgvalue='') {
637724
$content = $contents->text;
638725
}
639726
}
727+
if (isset($contentobj->content) &&
728+
\mod_questionnaire\question\choice::content_other_choice_display($contentobj->content)) {
729+
$othertext = \mod_questionnaire\question\choice::content_other_choice_display($contentobj->content);
730+
$content = $othertext . ' ' . clean_text($content);
731+
}
640732
if ($osgood) {
641733
$choicecol1 = new \stdClass();
642734
$choicecol1->width = $header1->width;
@@ -761,15 +853,21 @@ private function mkrescount($rids, $rows, $sort) {
761853
$rsql = ' AND response_id ' . $rsql;
762854
}
763855

764-
array_unshift($params, $this->question->id); // This is question_id.
765-
$sql = 'SELECT r.id, c.content, r.rankvalue, c.id AS choiceid ' .
766-
'FROM {questionnaire_quest_choice} c , ' .
767-
'{questionnaire_response_rank} r ' .
768-
'WHERE c.question_id = ?' .
769-
' AND r.question_id = c.question_id' .
770-
' AND r.choice_id = c.id ' .
771-
$rsql .
772-
' ORDER BY choiceid, rankvalue ASC';
856+
// This is question_id.
857+
array_push($params, $this->question->id);
858+
$sql = "SELECT r.id,
859+
CASE
860+
WHEN c.content = '!other' THEN o.response
861+
ELSE c.content
862+
END as content, r.rankvalue, c.id AS choiceid
863+
FROM {questionnaire_quest_choice} c
864+
INNER JOIN {" . static::response_table() . "} r ON r.question_id = c.question_id
865+
AND r.choice_id = c.id{$rsql}
866+
LEFT JOIN {questionnaire_response_other} o ON o.choice_id = c.id
867+
AND o.response_id = r.response_id
868+
AND o.question_id = c.question_id
869+
WHERE c.question_id = ? AND (c.content != '!other' OR (o.response IS NOT NULL AND o.response <> ''))
870+
ORDER BY choiceid, rankvalue ASC";
773871
$choices = $DB->get_records_sql($sql, $params);
774872

775873
// Sort rows (results) by average value.
@@ -799,10 +897,10 @@ private function mkrescount($rids, $rows, $sort) {
799897
if (!empty($this->question->nameddegrees)) {
800898
$rankvalue = array_flip(array_keys($this->question->nameddegrees));
801899
}
802-
foreach ($rows as $row) {
900+
foreach ($rows as $key => $row) {
803901
$choiceid = $row->id;
804902
foreach ($choices as $choice) {
805-
if ($choice->choiceid == $choiceid) {
903+
if ($choice->choiceid == $choiceid && $choice->content === $key) {
806904
$n = 0;
807905
for ($i = 1; $i <= $nbranks; $i++) {
808906
if ((isset($rankvalue[$choice->rankvalue]) && ($rankvalue[$choice->rankvalue] == ($i - 1))) ||
@@ -893,17 +991,23 @@ private function mkrescount($rids, $rows, $sort) {
893991
// Ensure there are two bits of content.
894992
list($content, $contentright) = array_merge(preg_split('/[|]/', $content), array(' '));
895993
$header = reset($pagetags->totals->headers);
994+
if (isset($rows[$content]) && isset($rows[$content]->isother) && $rows[$content]->isother) {
995+
$content = get_string('other', 'questionnaire') . ' ' . $content;
996+
}
896997
$totalcols[] = (object)['align' => $header->align,
897-
'text' => format_text($content, FORMAT_HTML, ['noclean' => true])];
998+
'text' => format_text($content, FORMAT_HTML, ['noclean' => true, 'filter' => false])];
898999
} else {
8991000
// Eliminate potentially short-named choices.
9001001
$contents = questionnaire_choice_values($content);
9011002
if ($contents->modname) {
9021003
$content = $contents->text;
9031004
}
1005+
if (isset($rows[$content]) && isset($rows[$content]->isother) && $rows[$content]->isother) {
1006+
$content = get_string('other', 'questionnaire') . ' ' . $content;
1007+
}
9041008
$header = reset($pagetags->totals->headers);
9051009
$totalcols[] = (object)['align' => $header->align,
906-
'text' => format_text($content, FORMAT_HTML, ['noclean' => true])];
1010+
'text' => format_text($content, FORMAT_HTML, ['noclean' => true, 'filter' => false])];
9071011
}
9081012
// Display ranks/rates numbers.
9091013
$maxrank = max($rank);

templates/question_rate.mustache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
<td {{#colstyle}}style="{{.}}"{{/colstyle}}{{#colclass}} class="{{.}}"{{/colclass}}{{#coltitle}} title="{{.}}"{{/coltitle}}
141141
{{#colinput}} onclick="if('{{id}}'.length>0) document.getElementById('{{id}}').click()"{{/colinput}}>
142142
{{#coltext}}{{{.}}}{{/coltext}}
143+
{{#oname}}<input size="25" name="{{oname}}" id="{{oid}}" value="" type="text" />{{/oname}}
143144
{{#colhiddentext}}<span class="accesshide">{{{.}}}</span>{{/colhiddentext}}
144145
{{#colinput}}<input type="radio" name="{{name}}" id="{{id}}" value="{{value}}"{{#checked}} checked="checked"{{/checked}}{{#disabled}} disabled="disabled"{{/disabled}}{{#onclick}} onclick="{{.}}"{{/onclick}}>
145146
{{#label}}<label for="{{id}}" class="accesshide">{{{.}}}</label>{{/label}}{{/colinput}}

templates/response_rate.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
</tr>
7171
{{#rows}}
7272
<tr>
73-
<td style="text-align:{{textalign}}">{{{content}}}</td>
73+
<td style="text-align:{{textalign}}">{{{content}}} {{#othercontent}} <span class="response text">{{.}}</span>{{/othercontent}}</td>
7474
{{#cols}}
7575
{{#checked}}
7676
<td style="text-align:center;" class="selected">

0 commit comments

Comments
 (0)