CMS  Version 3.9
questionnaire_data_view.inc
Go to the documentation of this file.
1 <?php
8 /*
9  *
10  * Description: Class for displaying aggregate data for
11  * questionnaire responses.
12  *
13  * Parameters:
14  * $obj - class that links to all the responses (e.g., Survey)
15  *
16  * Steps:
17  *
18  * 1) Get the questionnaire linked to obj.
19  *
20  * 2) Get each of the questions. For each question, get the aggregation
21  * renderer defined for that question type.
22  *
23  * 3) Get all the answers through the obj datamodel - these will be only
24  * the answers of submitted responses (not in progress or not started)
25  *
26  * 4) Get the aggregate data. Save the aggregate data from multi choice,
27  * ratings, checklist, and select field in optionAnswers; save the free text
28  * and short text in textAnswers.
29  *
30  * 5) Build a data view, grouped by question, for each display type and
31  * draw the views.
32  */
33 
34 Fakoli::using("survey", "questionnaire");
35 Fakoli::usingFeature("grouped_data_view");
36 
38 {
39  var $obj;
40  var $survey;
42  var $answers;
43  var $emptyMessage = "There are no results.";
49 
51  {
52  $this->mgr = $mgr;
53  $this->survey = $this->mgr->item;
54  $qPk = $this->mgr->getQuestionKey();
55 
56  $this->questionTypes = QuestionType::getQuestionTypeList();
57 
58  $this->questions = $this->mgr->getQuestions();
59 
60  if(count($this->questions) > 0)
61  $answers = $this->mgr->getAnswers();
62 
63  if(count($answers) > 0)
64  {
65  $this->responseCount = $this->mgr->getResponseCount();
66  $this->answers = regroupList($answers, $qPk);
67 
68  foreach($this->questions as $question)
69  {
70  $answers = $this->answers[$question->$qPk];
71  $this->getAggregateData($question, $answers);
72  }
73  }
74 
75  $this->buildOptionDataView();
76  $this->buildTextDataView();
77  }
78 
79  function getAggregateData($question, $answers)
80  {
81  $qPk = $this->mgr->getQuestionKey();
82 
83  $questionType = new QuestionType($question->question_type_id);
84 
85  switch($questionType->class_name)
86  {
87  case MultipleChoiceView:
88 
89  $this->optionAnswers[$question->$qPk] = $this->getSingleSelectQuestionResultData($question, $answers);
90  break;
91 
92  case RatingView:
93 
94  $this->optionAnswers[$question->$qPk] = $this->getRatingsQuestionResultData($question, $answers);
95  break;
96 
97  case ShortTextView:
98 
99  $this->textAnswers[$question->$qPk] = $answers;
100  break;
101 
102  case FreeTextView:
103 
104  $this->textAnswers[$question->$qPk] = $answers;
105  break;
106 
107  case CheckListView:
108 
109  $this->optionAnswers[$question->$qPk] = $this->getCheckListQuestionResultData($question, $answers);
110  break;
111 
112  case SelectFieldView:
113 
114  $this->optionAnswers[$question->$qPk] = $this->getSingleSelectQuestionResultData($question, $answers);
115  break;
116 
117  case HeadingView:
118  break;
119 
120  default:
121 
122  trace("getAggregateData: Unknown question type {$questionType->class_name}", 2);
123  die("QuestionniareDataView getAggregateData: Unknown question type {$questionType->class_name}");
124  }
125 
126  }
127 
128  /*
129  * Use this function for multiple choice, and select field, get
130  * the count of answers for each option in the question option list.
131  */
133  {
134  $qPk = $this->mgr->getQuestionKey();
135  $options = explode("\n", $question->options);
136  $idx = 1;
137 
138  if(count($options) > 0)
139  {
140  if(count($answers) > 0)
141  $answerValues = regroupList($answers, "value");
142  else
143  $answerValues = array();
144  $optionAnswers = array();
145 
146  foreach($options as $option)
147  {
148  $count = count($answerValues[$idx]);
149  $optionAnswer = new OptionAnswer($question->$qPk, $idx, $option, $count);
150  array_push($optionAnswers, $optionAnswer);
151  $idx++;
152 
153  }
154  return $optionAnswers;
155  }
156  }
157 
158  /*
159  * Use this for ratings - each radio button is not labeled in
160  * the question option field so we need to create a label
161  * for each in the results data display.
162  */
164  {
165  $qPk = $this->mgr->getQuestionKey();
166  if(count($answers) > 0)
167  $answerValues = regroupList($answers, "value");
168 
169  $optionAnswers = array();
170  $count = array();
171  $optionText = $this->getOptionText($question->options);
172  $steps = count($optionText);
173 
174  for($idx = 1; $idx <= $steps; ++$idx)
175  {
176  //echo "idx is $idx<br/>";
177  // store for calculating mean and median
178  $counts[$idx] = count($answerValues[$idx]);
179  $optionAnswer = new OptionAnswer($question->$qPk, $idx, $optionText[$idx], $counts[$idx]);
180  array_push($optionAnswers, $optionAnswer);
181  }
182 
183  $mean = new OptionAnswer($question->$qPk, ++$idx, "Mean", $this->getMean($counts));
184  //$median = new OptionAnswer($question->$qPk, ++$idx, "Median", $this->getMedian($optionAnswers));
185  array_push($optionAnswers, $mean);
186  //array_push($optionAnswers, $median);
187 
188  return $optionAnswers;
189  }
190 
192  {
193  $optionText = array();
194 
195  list($from, $to, $steps) = explode("\n", $options);
196  if (!$from) $from = "Lowest";
197  if (!$to) $to = "Highest";
198  if (!$steps) $steps = 5;
199 
200  for($idx = 1; $idx <= $steps; ++$idx)
201  {
202  if($idx > 1 AND $idx < $steps)
203  $optionText[$idx] = $idx;
204  else
205  $optionText[$idx] = ($idx == 1) ? $from : $to;
206  }
207  return $optionText;
208  }
209 
210  function getMean($counts)
211  {
212  $total = 0;
213  $num = 0;
214 
215  foreach($counts as $value => $count)
216  {
217  $total += $value * $count;
218  $num += $count;
219  }
220 
221  $mean = ($num > 0) ? round($total / $num, 2) : 0;
222  return $mean;
223  }
224 
225  /*
226  * The median will either be the middle value in the array if the count is odd
227  * or the average of the center two numbers if the count is even.
228  */
230  {
231  $median = 0.0;
232  $index_1 = 0;
233  $index_2 = 0;
234 
235  // determine if odd or even
236  $no_elements = count($optionAnswers);
237  //echo "no elements is $no_elements<br/>";
238  $odd = $no_elements % 2;
239 
240  //odd take the middle number
241  if ($odd == 1)
242  {
243  //determine the middle
244  $the_index_1 = $no_elements / 2;
245 
246  //cast to integer
247  settype($the_index_1, "integer");
248 
249  //calculate the median
250  $median = $optionAnswers[$the_index_1]->option;
251  }
252  else
253  {
254  //determine the two middle numbers
255  $the_index_1 = $no_elements / 2;
256  $the_index_2 = $the_index_1 - 1;
257 
258  //calculate the median
259  $median = ($optionAnswers[$the_index_1] + $optionAnswers[$the_index_2]) / 2;
260  }
261 
262  return $median;
263  }
264 
265  /*
266  * For checklists, we need to get the array of answers
267  * from the value field b/c users can choose more than one.
268  * We store the answer comma delimited so explode and loop
269  * through to get the count.
270  */
272  {
273  $qPk = $this->mgr->getQuestionKey();
274  $options = explode("\n", $question->options);
275  $idx = 1;
276 
277  // Explode and record the count of each value answer
278  $answerCounts = $this->getCheckListAnswerCounts($answers);
279  if(count($options) > 0)
280  {
281  $optionAnswers = array();
282 
283  foreach($options as $option)
284  {
285  $count = $answerCounts[$idx];
286  if (!isset($count)) $count = 0;
287  $optionAnswer = new OptionAnswer($question->$qPk, $idx, $option, $count);
288  array_push($optionAnswers, $optionAnswer);
289  $idx++;
290  }
291  }
292  return $optionAnswers;
293  }
294 
296  {
297  if(count($answers) == 0)
298  return;
299 
300  $count = array();
301 
302  foreach($answers as $answer)
303  {
304  $values = $answer->value;
305 
306  // include 0 value, first checkbox
307  if(strlen($values)==1 AND is_numeric($values))
308  $values = array($values);
309  elseif(strlen($values)==0)
310  $values = "";
311  else
312  $values = explode(",", $values);
313 
314  foreach($values as $value => $idx)
315  {
316  $count[$idx] += 1;
317  }
318  }
319  return $count;
320  }
321 
322  function writeScript()
323  {
324  $script = $this->optionDataView->writeScript();
325  $script .= $this->textDataView->writeScript();
326  return $script;
327  }
328 
329 
331  {
332  $dataView = new GroupedDataListView($this->optionAnswers, "OptionDataList");
333  $dataView->column("Option", "{option}", false, "width: 90%")
334  ->column("Count", "{count}", false, "width: 10%");
335  $dataView->groupBy($this->questions, array($this, getGroupTitle));
336  $dataView->mode = 'tree';
337  $dataView->emptyMessage = "There are no aggregated data results.";
338  $dataView->excelFile = "option_surveyData.xls";
339  $this->optionDataView = $dataView;
340  }
341 
342 
344  {
346  return "{$question->question_number}. {$question->question} " . $questionTypes[$question->question_type];
347  }
348 
349 
350  function buildTextDataView()
351  {
352  $table = new GroupedDataListView($this->textAnswers, "AnswerTextList");
353  $table->column("Answer Text", array(QuestionnaireDataView, formatAnswer));
354  $table->groupBy($this->questions, array(QuestionnaireDataView, formatQuestion));
355  $table->mode = 'tree';
356  $table->emptyMessage = "There are no text results.";
357  $table->excelFile = "text_surveyData.xls";
358 
359  $this->textDataView = $table;
360  }
361 
362  // question number is a pseudo field for some question dataitem objects
363  static function formatQuestion($question)
364  {
365  return $question->question_number . ". " . $question->question;
366  }
367 
368  static function formatAnswer($answer)
369  {
370  return ($answer->value) ? $answer->value : "Not Answered";
371  }
372 
373 
374  function drawView()
375  {
376  echo $this->getHeading();
377  $this->optionDataView->drawView();
378  echo "<h4>Free Text Responses</h4>";
379  $this->textDataView->drawView();
380  }
381 
382  /*
383  * Applications that don't want to see the the number of
384  * unsubmitted surveys, should return 0 recipient count from
385  * the results manager to omit that portion of the heading.
386  *
387  * This is the case for surveys where there is not a set
388  * of recipients who receive a request.
389  */
390  function getHeading()
391  {
392  $fn = array($this->mgr, formatResultsHeading);
393  if (is_callable($fn))
394  {
395  return call_user_func($fn);
396  }
397 
398  $recipientCount = $this->mgr->getRecipientCount();
399 
400  $countText = ($this->responseCount) ? $this->responseCount : "0";
401  $text = "<h3>Aggregated Survey Answers based on $countText ";
402  $text .= ($recipientCount) ? "out of $recipientCount " : "";
403  $text .= "Respondents</h3>\n";
404 
405  return $text;
406  }
407 }
408 
409 ?>
static usingFeature()
Uses the specified framework feature(s).
Definition: core.inc:388
static using()
Import the datamodels, views and manifest for the specified component(s).
Definition: core.inc:116
static getQuestionTypeList()
getCheckListQuestionResultData($question, $answers)
getAggregateData($question, $answers)
static getCheckListAnswerCounts($answers)
getRatingsQuestionResultData($question, $answers)
getSingleSelectQuestionResultData($question, $answers)
$question
$questionTypes