Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • Differences Between: [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * Quiz statistics report class.
      19   *
      20   * @package   quiz_statistics
      21   * @copyright 2014 Open University
      22   * @author    James Pratt <me@jamiep.org>
      23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      24   */
      25  
      26  defined('MOODLE_INTERNAL') || die();
      27  
      28  require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_form.php');
      29  require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_table.php');
      30  require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_question_table.php');
      31  require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
      32  /**
      33   * The quiz statistics report provides summary information about each question in
      34   * a quiz, compared to the whole quiz. It also provides a drill-down to more
      35   * detailed information about each question.
      36   *
      37   * @copyright 2008 Jamie Pratt
      38   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      39   */
      40  class quiz_statistics_report extends quiz_default_report {
      41  
      42      /** @var context_module context of this quiz.*/
      43      protected $context;
      44  
      45      /** @var quiz_statistics_table instance of table class used for main questions stats table. */
      46      protected $table;
      47  
      48      /** @var \core\progress\base|null $progress Handles progress reporting or not. */
      49      protected $progress = null;
      50  
      51      /**
      52       * Display the report.
      53       */
      54      public function display($quiz, $cm, $course) {
      55          global $OUTPUT;
      56  
      57          raise_memory_limit(MEMORY_HUGE);
      58  
      59          $this->context = context_module::instance($cm->id);
      60  
      61          if (!quiz_has_questions($quiz->id)) {
      62              $this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
      63              echo quiz_no_questions_message($quiz, $cm, $this->context);
      64              return true;
      65          }
      66  
      67          // Work out the display options.
      68          $download = optional_param('download', '', PARAM_ALPHA);
      69          $everything = optional_param('everything', 0, PARAM_BOOL);
      70          $recalculate = optional_param('recalculate', 0, PARAM_BOOL);
      71          // A qid paramter indicates we should display the detailed analysis of a sub question.
      72          $qid = optional_param('qid', 0, PARAM_INT);
      73          $slot = optional_param('slot', 0, PARAM_INT);
      74          $variantno = optional_param('variant', null, PARAM_INT);
      75          $whichattempts = optional_param('whichattempts', $quiz->grademethod, PARAM_INT);
      76          $whichtries = optional_param('whichtries', question_attempt::LAST_TRY, PARAM_ALPHA);
      77  
      78          $pageoptions = array();
      79          $pageoptions['id'] = $cm->id;
      80          $pageoptions['mode'] = 'statistics';
      81  
      82          $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions);
      83  
      84          $mform = new quiz_statistics_settings_form($reporturl, compact('quiz'));
      85  
      86          $mform->set_data(array('whichattempts' => $whichattempts, 'whichtries' => $whichtries));
      87  
      88          if ($whichattempts != $quiz->grademethod) {
      89              $reporturl->param('whichattempts', $whichattempts);
      90          }
      91  
      92          if ($whichtries != question_attempt::LAST_TRY) {
      93              $reporturl->param('whichtries', $whichtries);
      94          }
      95  
      96          // Find out current groups mode.
      97          $currentgroup = $this->get_current_group($cm, $course, $this->context);
      98          $nostudentsingroup = false; // True if a group is selected and there is no one in it.
      99          if (empty($currentgroup)) {
     100              $currentgroup = 0;
     101              $groupstudents = array();
     102  
     103          } else if ($currentgroup == self::NO_GROUPS_ALLOWED) {
     104              $groupstudents = array();
     105              $nostudentsingroup = true;
     106  
     107          } else {
     108              // All users who can attempt quizzes and who are in the currently selected group.
     109              $groupstudents = get_users_by_capability($this->context,
     110                      array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
     111                      '', '', '', '', $currentgroup, '', false);
     112              if (!$groupstudents) {
     113                  $nostudentsingroup = true;
     114              }
     115          }
     116  
     117          $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
     118  
     119          // If recalculate was requested, handle that.
     120          if ($recalculate && confirm_sesskey()) {
     121              $this->clear_cached_data($qubaids);
     122              redirect($reporturl);
     123          }
     124  
     125          // Set up the main table.
     126          $this->table = new quiz_statistics_table();
     127          if ($everything) {
     128              $report = get_string('completestatsfilename', 'quiz_statistics');
     129          } else {
     130              $report = get_string('questionstatsfilename', 'quiz_statistics');
     131          }
     132          $courseshortname = format_string($course->shortname, true,
     133                  array('context' => context_course::instance($course->id)));
     134          $filename = quiz_report_download_filename($report, $courseshortname, $quiz->name);
     135          $this->table->is_downloading($download, $filename,
     136                  get_string('quizstructureanalysis', 'quiz_statistics'));
     137          $questions = $this->load_and_initialise_questions_for_calculations($quiz);
     138  
     139          // Print the page header stuff (if not downloading.
     140          if (!$this->table->is_downloading()) {
     141              $this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
     142          }
     143  
     144          if (!$nostudentsingroup) {
     145              // Get the data to be displayed.
     146              $progress = $this->get_progress_trace_instance();
     147              list($quizstats, $questionstats) =
     148                  $this->get_all_stats_and_analysis($quiz, $whichattempts, $whichtries, $groupstudents, $questions, $progress);
     149          } else {
     150              // Or create empty stats containers.
     151              $quizstats = new \quiz_statistics\calculated($whichattempts);
     152              $questionstats = new \core_question\statistics\questions\all_calculated_for_qubaid_condition();
     153          }
     154  
     155          // Set up the table, if there is data.
     156          if ($quizstats->s()) {
     157              $this->table->statistics_setup($quiz, $cm->id, $reporturl, $quizstats->s());
     158          }
     159  
     160          // Print the rest of the page header stuff (if not downloading.
     161          if (!$this->table->is_downloading()) {
     162  
     163              if (groups_get_activity_groupmode($cm)) {
     164                  groups_print_activity_menu($cm, $reporturl->out());
     165                  if ($currentgroup && !$groupstudents) {
     166                      $OUTPUT->notification(get_string('nostudentsingroup', 'quiz_statistics'));
     167                  }
     168              }
     169  
     170              if (!$this->table->is_downloading() && $quizstats->s() == 0) {
     171                  echo $OUTPUT->notification(get_string('noattempts', 'quiz'));
     172              }
     173  
     174              foreach ($questionstats->any_error_messages() as $errormessage) {
     175                  echo $OUTPUT->notification($errormessage);
     176              }
     177  
     178              // Print display options form.
     179              $mform->display();
     180          }
     181  
     182          if ($everything) { // Implies is downloading.
     183              // Overall report, then the analysis of each question.
     184              $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
     185              $this->download_quiz_info_table($quizinfo);
     186  
     187              if ($quizstats->s()) {
     188                  $this->output_quiz_structure_analysis_table($questionstats);
     189  
     190                  if ($this->table->is_downloading() == 'xhtml' && $quizstats->s() != 0) {
     191                      $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
     192                  }
     193  
     194                  $this->output_all_question_response_analysis($qubaids, $questions, $questionstats, $reporturl, $whichtries);
     195              }
     196  
     197              $this->table->export_class_instance()->finish_document();
     198  
     199          } else if ($qid) {
     200              // Report on an individual sub-question indexed questionid.
     201              if (is_null($questionstats->for_subq($qid, $variantno))) {
     202                  print_error('questiondoesnotexist', 'question');
     203              }
     204  
     205              $this->output_individual_question_data($quiz, $questionstats->for_subq($qid, $variantno));
     206              $this->output_individual_question_response_analysis($questionstats->for_subq($qid, $variantno)->question,
     207                                                                  $variantno,
     208                                                                  $questionstats->for_subq($qid, $variantno)->s,
     209                                                                  $reporturl,
     210                                                                  $qubaids,
     211                                                                  $whichtries);
     212              // Back to overview link.
     213              echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
     214                                get_string('backtoquizreport', 'quiz_statistics') . '</a>',
     215                                'boxaligncenter generalbox boxwidthnormal mdl-align');
     216          } else if ($slot) {
     217              // Report on an individual question indexed by position.
     218              if (!isset($questions[$slot])) {
     219                  print_error('questiondoesnotexist', 'question');
     220              }
     221  
     222              if ($variantno === null &&
     223                                  ($questionstats->for_slot($slot)->get_sub_question_ids()
     224                                  || $questionstats->for_slot($slot)->get_variants())) {
     225                  if (!$this->table->is_downloading()) {
     226                      $number = $questionstats->for_slot($slot)->question->number;
     227                      echo $OUTPUT->heading(get_string('slotstructureanalysis', 'quiz_statistics', $number), 3);
     228                  }
     229                  $this->table->define_baseurl(new moodle_url($reporturl, array('slot' => $slot)));
     230                  $this->table->format_and_add_array_of_rows($questionstats->structure_analysis_for_one_slot($slot));
     231              } else {
     232                  $this->output_individual_question_data($quiz, $questionstats->for_slot($slot, $variantno));
     233                  $this->output_individual_question_response_analysis($questions[$slot],
     234                                                                      $variantno,
     235                                                                      $questionstats->for_slot($slot, $variantno)->s,
     236                                                                      $reporturl,
     237                                                                      $qubaids,
     238                                                                      $whichtries);
     239              }
     240              if (!$this->table->is_downloading()) {
     241                  // Back to overview link.
     242                  echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
     243                          get_string('backtoquizreport', 'quiz_statistics') . '</a>',
     244                          'backtomainstats boxaligncenter generalbox boxwidthnormal mdl-align');
     245              } else {
     246                  $this->table->finish_output();
     247              }
     248  
     249          } else if ($this->table->is_downloading()) {
     250              // Downloading overview report.
     251              $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
     252              $this->download_quiz_info_table($quizinfo);
     253              if ($quizstats->s()) {
     254                  $this->output_quiz_structure_analysis_table($questionstats);
     255              }
     256              $this->table->finish_output();
     257  
     258          } else {
     259              // On-screen display of overview report.
     260              echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
     261              echo $this->output_caching_info($quizstats->timemodified, $quiz->id, $groupstudents, $whichattempts, $reporturl);
     262              echo $this->everything_download_options();
     263              $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
     264              echo $this->output_quiz_info_table($quizinfo);
     265              if ($quizstats->s()) {
     266                  echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'), 3);
     267                  $this->output_quiz_structure_analysis_table($questionstats);
     268                  $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
     269              }
     270          }
     271  
     272          return true;
     273      }
     274  
     275      /**
     276       * Display the statistical and introductory information about a question.
     277       * Only called when not downloading.
     278       *
     279       * @param object                                         $quiz         the quiz settings.
     280       * @param \core_question\statistics\questions\calculated $questionstat the question to report on.
     281       */
     282      protected function output_individual_question_data($quiz, $questionstat) {
     283          global $OUTPUT;
     284  
     285          // On-screen display. Show a summary of the question's place in the quiz,
     286          // and the question statistics.
     287          $datumfromtable = $this->table->format_row($questionstat);
     288  
     289          // Set up the question info table.
     290          $questioninfotable = new html_table();
     291          $questioninfotable->align = array('center', 'center');
     292          $questioninfotable->width = '60%';
     293          $questioninfotable->attributes['class'] = 'generaltable titlesleft';
     294  
     295          $questioninfotable->data = array();
     296          $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
     297          $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'),
     298                  $questionstat->question->name.'&nbsp;'.$datumfromtable['actions']);
     299  
     300          if ($questionstat->variant !== null) {
     301              $questioninfotable->data[] = array(get_string('variant', 'quiz_statistics'), $questionstat->variant);
     302  
     303          }
     304          $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'),
     305                  $datumfromtable['icon'] . '&nbsp;' .
     306                  question_bank::get_qtype($questionstat->question->qtype, false)->menu_name() . '&nbsp;' .
     307                  $datumfromtable['icon']);
     308          $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'),
     309                  $questionstat->positions);
     310  
     311          // Set up the question statistics table.
     312          $questionstatstable = new html_table();
     313          $questionstatstable->align = array('center', 'center');
     314          $questionstatstable->width = '60%';
     315          $questionstatstable->attributes['class'] = 'generaltable titlesleft';
     316  
     317          unset($datumfromtable['number']);
     318          unset($datumfromtable['icon']);
     319          $actions = $datumfromtable['actions'];
     320          unset($datumfromtable['actions']);
     321          unset($datumfromtable['name']);
     322          $labels = array(
     323              's' => get_string('attempts', 'quiz_statistics'),
     324              'facility' => get_string('facility', 'quiz_statistics'),
     325              'sd' => get_string('standarddeviationq', 'quiz_statistics'),
     326              'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
     327              'intended_weight' => get_string('intended_weight', 'quiz_statistics'),
     328              'effective_weight' => get_string('effective_weight', 'quiz_statistics'),
     329              'discrimination_index' => get_string('discrimination_index', 'quiz_statistics'),
     330              'discriminative_efficiency' =>
     331                                  get_string('discriminative_efficiency', 'quiz_statistics')
     332          );
     333          foreach ($datumfromtable as $item => $value) {
     334              $questionstatstable->data[] = array($labels[$item], $value);
     335          }
     336  
     337          // Display the various bits.
     338          echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'), 3);
     339          echo html_writer::table($questioninfotable);
     340          echo $this->render_question_text($questionstat->question);
     341          echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'), 3);
     342          echo html_writer::table($questionstatstable);
     343      }
     344  
     345      /**
     346       * Output question text in a box with urls appropriate for a preview of the question.
     347       *
     348       * @param object $question question data.
     349       * @return string HTML of question text, ready for display.
     350       */
     351      protected function render_question_text($question) {
     352          global $OUTPUT;
     353  
     354          $text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
     355                  $question->contextid, 'question', 'questiontext', $question->id,
     356                  $this->context->id, 'quiz_statistics');
     357  
     358          return $OUTPUT->box(format_text($text, $question->questiontextformat,
     359                  array('noclean' => true, 'para' => false, 'overflowdiv' => true)),
     360                  'questiontext boxaligncenter generalbox boxwidthnormal mdl-align');
     361      }
     362  
     363      /**
     364       * Display the response analysis for a question.
     365       *
     366       * @param object           $question  the question to report on.
     367       * @param int|null         $variantno the variant
     368       * @param int              $s
     369       * @param moodle_url       $reporturl the URL to redisplay this report.
     370       * @param qubaid_condition $qubaids
     371       * @param string           $whichtries
     372       */
     373      protected function output_individual_question_response_analysis($question, $variantno, $s, $reporturl, $qubaids,
     374                                                                      $whichtries = question_attempt::LAST_TRY) {
     375          global $OUTPUT;
     376  
     377          if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
     378              return;
     379          }
     380  
     381          $qtable = new quiz_statistics_question_table($question->id);
     382          $exportclass = $this->table->export_class_instance();
     383          $qtable->export_class_instance($exportclass);
     384          if (!$this->table->is_downloading()) {
     385              // Output an appropriate title.
     386              echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics'), 3);
     387  
     388          } else {
     389              // Work out an appropriate title.
     390              $a = clone($question);
     391              $a->variant = $variantno;
     392  
     393              if (!empty($question->number) && !is_null($variantno)) {
     394                  $questiontabletitle = get_string('analysisnovariant', 'quiz_statistics', $a);
     395              } else if (!empty($question->number)) {
     396                  $questiontabletitle = get_string('analysisno', 'quiz_statistics', $a);
     397              } else if (!is_null($variantno)) {
     398                  $questiontabletitle = get_string('analysisvariant', 'quiz_statistics', $a);
     399              } else {
     400                  $questiontabletitle = get_string('analysisnameonly', 'quiz_statistics', $a);
     401              }
     402  
     403              if ($this->table->is_downloading() == 'xhtml') {
     404                  $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
     405              }
     406  
     407              // Set up the table.
     408              $exportclass->start_table($questiontabletitle);
     409  
     410              if ($this->table->is_downloading() == 'xhtml') {
     411                  echo $this->render_question_text($question);
     412              }
     413          }
     414  
     415          $responesanalyser = new \core_question\statistics\responses\analyser($question, $whichtries);
     416          $responseanalysis = $responesanalyser->load_cached($qubaids, $whichtries);
     417  
     418          $qtable->question_setup($reporturl, $question, $s, $responseanalysis);
     419          if ($this->table->is_downloading()) {
     420              $exportclass->output_headers($qtable->headers);
     421          }
     422  
     423          // Where no variant no is specified the variant no is actually one.
     424          if ($variantno === null) {
     425              $variantno = 1;
     426          }
     427          foreach ($responseanalysis->get_subpart_ids($variantno) as $partid) {
     428              $subpart = $responseanalysis->get_analysis_for_subpart($variantno, $partid);
     429              foreach ($subpart->get_response_class_ids() as $responseclassid) {
     430                  $responseclass = $subpart->get_response_class($responseclassid);
     431                  $tabledata = $responseclass->data_for_question_response_table($subpart->has_multiple_response_classes(), $partid);
     432                  foreach ($tabledata as $row) {
     433                      $qtable->add_data_keyed($qtable->format_row($row));
     434                  }
     435              }
     436          }
     437  
     438          $qtable->finish_output(!$this->table->is_downloading());
     439      }
     440  
     441      /**
     442       * Output the table that lists all the questions in the quiz with their statistics.
     443       *
     444       * @param \core_question\statistics\questions\all_calculated_for_qubaid_condition $questionstats the stats for all questions in
     445       *                                                                                               the quiz including subqs and
     446       *                                                                                               variants.
     447       */
     448      protected function output_quiz_structure_analysis_table($questionstats) {
     449          $tooutput = array();
     450          $limitvariants = !$this->table->is_downloading();
     451          foreach ($questionstats->get_all_slots() as $slot) {
     452              // Output the data for these question statistics.
     453              $tooutput = array_merge($tooutput, $questionstats->structure_analysis_for_one_slot($slot, $limitvariants));
     454          }
     455          $this->table->format_and_add_array_of_rows($tooutput);
     456      }
     457  
     458      /**
     459       * Return HTML for table of overall quiz statistics.
     460       *
     461       * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
     462       * @return string the HTML.
     463       */
     464      protected function output_quiz_info_table($quizinfo) {
     465  
     466          $quizinfotable = new html_table();
     467          $quizinfotable->align = array('center', 'center');
     468          $quizinfotable->width = '60%';
     469          $quizinfotable->attributes['class'] = 'generaltable titlesleft';
     470          $quizinfotable->data = array();
     471  
     472          foreach ($quizinfo as $heading => $value) {
     473               $quizinfotable->data[] = array($heading, $value);
     474          }
     475  
     476          return html_writer::table($quizinfotable);
     477      }
     478  
     479      /**
     480       * Download the table of overall quiz statistics.
     481       *
     482       * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
     483       */
     484      protected function download_quiz_info_table($quizinfo) {
     485          global $OUTPUT;
     486  
     487          // XHTML download is a special case.
     488          if ($this->table->is_downloading() == 'xhtml') {
     489              echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
     490              echo $this->output_quiz_info_table($quizinfo);
     491              return;
     492          }
     493  
     494          // Reformat the data ready for output.
     495          $headers = array();
     496          $row = array();
     497          foreach ($quizinfo as $heading => $value) {
     498              $headers[] = $heading;
     499              $row[] = $value;
     500          }
     501  
     502          // Do the output.
     503          $exportclass = $this->table->export_class_instance();
     504          $exportclass->start_table(get_string('quizinformation', 'quiz_statistics'));
     505          $exportclass->output_headers($headers);
     506          $exportclass->add_data($row);
     507          $exportclass->finish_table();
     508      }
     509  
     510      /**
     511       * Output the HTML needed to show the statistics graph.
     512       *
     513       * @param $quizid
     514       * @param $currentgroup
     515       * @param $whichattempts
     516       */
     517      protected function output_statistics_graph($quizid, $currentgroup, $whichattempts) {
     518          global $PAGE;
     519  
     520          $output = $PAGE->get_renderer('mod_quiz');
     521          $imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
     522                                      compact('quizid', 'currentgroup', 'whichattempts'));
     523          $graphname = get_string('statisticsreportgraph', 'quiz_statistics');
     524          echo $output->graph($imageurl, $graphname);
     525      }
     526  
     527      /**
     528       * Get the quiz and question statistics, either by loading the cached results,
     529       * or by recomputing them.
     530       *
     531       * @param object $quiz               the quiz settings.
     532       * @param string $whichattempts      which attempts to use, represented internally as one of the constants as used in
     533       *                                   $quiz->grademethod ie.
     534       *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
     535       *                                   we calculate stats based on which attempts would affect the grade for each student.
     536       * @param string $whichtries         which tries to analyse for response analysis. Will be one of
     537       *                                   question_attempt::FIRST_TRY, LAST_TRY or ALL_TRIES.
     538       * @param array  $groupstudents      students in this group.
     539       * @param array  $questions          full question data.
     540       * @param \core\progress\base|null   $progress
     541       * @return array with 2 elements:    - $quizstats The statistics for overall attempt scores.
     542       *                                   - $questionstats \core_question\statistics\questions\all_calculated_for_qubaid_condition
     543       */
     544      public function get_all_stats_and_analysis($quiz, $whichattempts, $whichtries, $groupstudents, $questions, $progress = null) {
     545  
     546          if ($progress === null) {
     547              $progress = new \core\progress\null();
     548          }
     549  
     550          $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
     551  
     552          $qcalc = new \core_question\statistics\questions\calculator($questions, $progress);
     553  
     554          $quizcalc = new \quiz_statistics\calculator($progress);
     555  
     556          $progress->start_progress('', 3);
     557          if ($quizcalc->get_last_calculated_time($qubaids) === false) {
     558  
     559              // Recalculate now.
     560              $questionstats = $qcalc->calculate($qubaids);
     561              $progress->progress(1);
     562  
     563              $quizstats = $quizcalc->calculate($quiz->id, $whichattempts, $groupstudents, count($questions),
     564                                                $qcalc->get_sum_of_mark_variance());
     565              $progress->progress(2);
     566          } else {
     567              $quizstats = $quizcalc->get_cached($qubaids);
     568              $progress->progress(1);
     569              $questionstats = $qcalc->get_cached($qubaids);
     570              $progress->progress(2);
     571          }
     572  
     573          if ($quizstats->s()) {
     574              $subquestions = $questionstats->get_sub_questions();
     575              $this->analyse_responses_for_all_questions_and_subquestions($questions,
     576                                                                          $subquestions,
     577                                                                          $qubaids,
     578                                                                          $whichtries,
     579                                                                          $progress);
     580          }
     581          $progress->progress(3);
     582          $progress->end_progress();
     583  
     584          return array($quizstats, $questionstats);
     585      }
     586  
     587      /**
     588       * Appropriate instance depending if we want html output for the user or not.
     589       *
     590       * @return \core\progress\base child of \core\progress\base to handle the display (or not) of task progress.
     591       */
     592      protected function get_progress_trace_instance() {
     593          if ($this->progress === null) {
     594              if (!$this->table->is_downloading()) {
     595                  $this->progress = new \core\progress\display_if_slow(get_string('calculatingallstats', 'quiz_statistics'));
     596                  $this->progress->set_display_names();
     597              } else {
     598                  $this->progress = new \core\progress\null();
     599              }
     600          }
     601          return $this->progress;
     602      }
     603  
     604      /**
     605       * Analyse responses for all questions and sub questions in this quiz.
     606       *
     607       * @param object[] $questions as returned by self::load_and_initialise_questions_for_calculations
     608       * @param object[] $subquestions full question objects.
     609       * @param qubaid_condition $qubaids the question usages whose responses to analyse.
     610       * @param string $whichtries which tries to analyse \question_attempt::FIRST_TRY, LAST_TRY or ALL_TRIES.
     611       * @param null|\core\progress\base $progress Used to indicate progress of task.
     612       */
     613      protected function analyse_responses_for_all_questions_and_subquestions($questions, $subquestions, $qubaids,
     614                                                                              $whichtries, $progress = null) {
     615          if ($progress === null) {
     616              $progress = new \core\progress\null();
     617          }
     618  
     619          // Starting response analysis tasks.
     620          $progress->start_progress('', count($questions) + count($subquestions));
     621  
     622          $done = $this->analyse_responses_for_questions($questions, $qubaids, $whichtries, $progress);
     623  
     624          $this->analyse_responses_for_questions($subquestions, $qubaids, $whichtries, $progress, $done);
     625  
     626          // Finished all response analysis tasks.
     627          $progress->end_progress();
     628      }
     629  
     630      /**
     631       * Analyse responses for an array of questions or sub questions.
     632       *
     633       * @param object[] $questions  as returned by self::load_and_initialise_questions_for_calculations.
     634       * @param qubaid_condition $qubaids the question usages whose responses to analyse.
     635       * @param string $whichtries which tries to analyse \question_attempt::FIRST_TRY, LAST_TRY or ALL_TRIES.
     636       * @param null|\core\progress\base $progress Used to indicate progress of task.
     637       * @param int[] $done array keys are ids of questions that have been analysed before calling method.
     638       * @return array array keys are ids of questions that were analysed after this method call.
     639       */
     640      protected function analyse_responses_for_questions($questions, $qubaids, $whichtries, $progress = null, $done = array()) {
     641          $countquestions = count($questions);
     642          if (!$countquestions) {
     643              return array();
     644          }
     645          if ($progress === null) {
     646              $progress = new \core\progress\null();
     647          }
     648          $progress->start_progress('', $countquestions, $countquestions);
     649          foreach ($questions as $question) {
     650              $progress->increment_progress();
     651              if (question_bank::get_qtype($question->qtype, false)->can_analyse_responses()  && !isset($done[$question->id])) {
     652                  $responesstats = new \core_question\statistics\responses\analyser($question, $whichtries);
     653                  if ($responesstats->get_last_analysed_time($qubaids, $whichtries) === false) {
     654                      $responesstats->calculate($qubaids, $whichtries);
     655                  }
     656              }
     657              $done[$question->id] = 1;
     658          }
     659          $progress->end_progress();
     660          return $done;
     661      }
     662  
     663      /**
     664       * Return a little form for the user to request to download the full report, including quiz stats and response analysis for
     665       * all questions and sub-questions.
     666       *
     667       * @return string HTML.
     668       */
     669      protected function everything_download_options() {
     670          $downloadoptions = $this->table->get_download_menu();
     671  
     672          $downloadelements = new stdClass();
     673          $downloadelements->formatsmenu = html_writer::select($downloadoptions, 'download',
     674                  $this->table->defaultdownloadformat, false);
     675          $downloadelements->downloadbutton = '<input type="submit" value="' .
     676                  get_string('download') . '"/>';
     677  
     678          $output = '<form action="'. $this->table->baseurl .'" method="post">';
     679          $output .= '<div class="mdl-align">';
     680          $output .= '<input type="hidden" name="everything" value="1"/>';
     681          $output .= html_writer::tag('label', get_string('downloadeverything', 'quiz_statistics', $downloadelements));
     682          $output .= '</div></form>';
     683  
     684          return $output;
     685      }
     686  
     687      /**
     688       * Return HTML for a message that says when the stats were last calculated and a 'recalculate now' button.
     689       *
     690       * @param int    $lastcachetime  the time the stats were last cached.
     691       * @param int    $quizid         the quiz id.
     692       * @param array  $groupstudents  ids of students in the group or empty array if groups not used.
     693       * @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
     694       *                                   $quiz->grademethod ie.
     695       *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
     696       *                                   we calculate stats based on which attempts would affect the grade for each student.
     697       * @param moodle_url $reporturl url for this report
     698       * @return string HTML.
     699       */
     700      protected function output_caching_info($lastcachetime, $quizid, $groupstudents, $whichattempts, $reporturl) {
     701          global $DB, $OUTPUT;
     702  
     703          if (empty($lastcachetime)) {
     704              return '';
     705          }
     706  
     707          // Find the number of attempts since the cached statistics were computed.
     708          list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts, true);
     709          $count = $DB->count_records_sql("
     710                  SELECT COUNT(1)
     711                  FROM $fromqa
     712                  WHERE $whereqa
     713                  AND quiza.timefinish > {$lastcachetime}", $qaparams);
     714  
     715          if (!$count) {
     716              $count = 0;
     717          }
     718  
     719          // Generate the output.
     720          $a = new stdClass();
     721          $a->lastcalculated = format_time(time() - $lastcachetime);
     722          $a->count = $count;
     723  
     724          $recalcualteurl = new moodle_url($reporturl,
     725                  array('recalculate' => 1, 'sesskey' => sesskey()));
     726          $output = '';
     727          $output .= $OUTPUT->box_start(
     728                  'boxaligncenter generalbox boxwidthnormal mdl-align', 'cachingnotice');
     729          $output .= get_string('lastcalculated', 'quiz_statistics', $a);
     730          $output .= $OUTPUT->single_button($recalcualteurl,
     731                  get_string('recalculatenow', 'quiz_statistics'));
     732          $output .= $OUTPUT->box_end(true);
     733  
     734          return $output;
     735      }
     736  
     737      /**
     738       * Clear the cached data for a particular report configuration. This will trigger a re-computation the next time the report
     739       * is displayed.
     740       *
     741       * @param $qubaids qubaid_condition
     742       */
     743      protected function clear_cached_data($qubaids) {
     744          global $DB;
     745          $DB->delete_records('quiz_statistics', array('hashcode' => $qubaids->get_hash_code()));
     746          $DB->delete_records('question_statistics', array('hashcode' => $qubaids->get_hash_code()));
     747          $DB->delete_records('question_response_analysis', array('hashcode' => $qubaids->get_hash_code()));
     748      }
     749  
     750      /**
     751       * Load the questions in this quiz and add some properties to the objects needed in the reports.
     752       *
     753       * @param object $quiz the quiz.
     754       * @return array of questions for this quiz.
     755       */
     756      public function load_and_initialise_questions_for_calculations($quiz) {
     757          // Load the questions.
     758          $questions = quiz_report_get_significant_questions($quiz);
     759          $questionids = array();
     760          foreach ($questions as $question) {
     761              $questionids[] = $question->id;
     762          }
     763          $fullquestions = question_load_questions($questionids);
     764          foreach ($questions as $qno => $question) {
     765              $q = $fullquestions[$question->id];
     766              $q->maxmark = $question->maxmark;
     767              $q->slot = $qno;
     768              $q->number = $question->number;
     769              $questions[$qno] = $q;
     770          }
     771          return $questions;
     772      }
     773  
     774      /**
     775       * Output all response analysis for all questions, sub-questions and variants. For download in a number of formats.
     776       *
     777       * @param $qubaids
     778       * @param $questions
     779       * @param $questionstats
     780       * @param $reporturl
     781       * @param $whichtries string
     782       */
     783      protected function output_all_question_response_analysis($qubaids,
     784                                                               $questions,
     785                                                               $questionstats,
     786                                                               $reporturl,
     787                                                               $whichtries = question_attempt::LAST_TRY) {
     788          foreach ($questions as $slot => $question) {
     789              if (question_bank::get_qtype(
     790                  $question->qtype, false)->can_analyse_responses()
     791              ) {
     792                  if ($questionstats->for_slot($slot)->get_variants()) {
     793                      foreach ($questionstats->for_slot($slot)->get_variants() as $variantno) {
     794                          $this->output_individual_question_response_analysis($question,
     795                                                                              $variantno,
     796                                                                              $questionstats->for_slot($slot, $variantno)->s,
     797                                                                              $reporturl,
     798                                                                              $qubaids,
     799                                                                              $whichtries);
     800                      }
     801                  } else {
     802                      $this->output_individual_question_response_analysis($question,
     803                                                                          null,
     804                                                                          $questionstats->for_slot($slot)->s,
     805                                                                          $reporturl,
     806                                                                          $qubaids,
     807                                                                          $whichtries);
     808                  }
     809              } else if ($subqids = $questionstats->for_slot($slot)->get_sub_question_ids()) {
     810                  foreach ($subqids as $subqid) {
     811                      if ($variants = $questionstats->for_subq($subqid)->get_variants()) {
     812                          foreach ($variants as $variantno) {
     813                              $this->output_individual_question_response_analysis(
     814                                  $questionstats->for_subq($subqid, $variantno)->question,
     815                                  $variantno,
     816                                  $questionstats->for_subq($subqid, $variantno)->s,
     817                                  $reporturl,
     818                                  $qubaids,
     819                                  $whichtries);
     820                          }
     821                      } else {
     822                          $this->output_individual_question_response_analysis(
     823                              $questionstats->for_subq($subqid)->question,
     824                              null,
     825                              $questionstats->for_subq($subqid)->s,
     826                              $reporturl,
     827                              $qubaids,
     828                              $whichtries);
     829  
     830                      }
     831                  }
     832              }
     833          }
     834      }
     835  }
    

    Search This Site: