diff --git a/mod/quiz/classes/local/reports/attempts_report.php b/mod/quiz/classes/local/reports/attempts_report.php
new file mode 100644
index 00000000000..54254f812ed
--- /dev/null
+++ b/mod/quiz/classes/local/reports/attempts_report.php
@@ -0,0 +1,383 @@
+.
+
+namespace mod_quiz\local\reports;
+
+use coding_exception;
+use context_module;
+use moodle_url;
+use stdClass;
+use table_sql;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/tablelib.php');
+
+
+/**
+ * Base class for quiz reports that are basically a table with one row for each attempt.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class attempts_report extends report_base {
+ /** @var int default page size for reports. */
+ const DEFAULT_PAGE_SIZE = 30;
+
+ /** @var string constant used for the options, means all users with attempts. */
+ const ALL_WITH = 'all_with';
+ /** @var string constant used for the options, means only enrolled users with attempts. */
+ const ENROLLED_WITH = 'enrolled_with';
+ /** @var string constant used for the options, means only enrolled users without attempts. */
+ const ENROLLED_WITHOUT = 'enrolled_without';
+ /** @var string constant used for the options, means all enrolled users. */
+ const ENROLLED_ALL = 'enrolled_any';
+
+ /** @var string the mode this report is. */
+ protected $mode;
+
+ /** @var context_module the quiz context. */
+ protected $context;
+
+ /** @var attempts_report_options_form The settings form to use. */
+ protected $form;
+
+ /** @var string SQL fragment for selecting the attempt that gave the final grade,
+ * if applicable. */
+ protected $qmsubselect;
+
+ /** @var boolean caches the results of {@link should_show_grades()}. */
+ protected $showgrades = null;
+
+ /**
+ * Initialise various aspects of this report.
+ *
+ * @param string $mode
+ * @param string $formclass
+ * @param object $quiz
+ * @param object $cm
+ * @param object $course
+ * @return array with four elements:
+ * 0 => integer the current group id (0 for none).
+ * 1 => \core\dml\sql_join Contains joins, wheres, params for all the students in this course.
+ * 2 => \core\dml\sql_join Contains joins, wheres, params for all the students in the current group.
+ * 3 => \core\dml\sql_join Contains joins, wheres, params for all the students to show in the report.
+ * Will be the same as either element 1 or 2.
+ */
+ public function init($mode, $formclass, $quiz, $cm, $course) {
+ $this->mode = $mode;
+
+ $this->context = context_module::instance($cm->id);
+
+ list($currentgroup, $studentsjoins, $groupstudentsjoins, $allowedjoins) = $this->get_students_joins(
+ $cm, $course);
+
+ $this->qmsubselect = quiz_report_qm_filter_select($quiz);
+
+ $this->form = new $formclass($this->get_base_url(),
+ array('quiz' => $quiz, 'currentgroup' => $currentgroup, 'context' => $this->context));
+
+ return array($currentgroup, $studentsjoins, $groupstudentsjoins, $allowedjoins);
+ }
+
+ /**
+ * Get the base URL for this report.
+ * @return moodle_url the URL.
+ */
+ protected function get_base_url() {
+ return new moodle_url('/mod/quiz/report.php',
+ array('id' => $this->context->instanceid, 'mode' => $this->mode));
+ }
+
+ /**
+ * Get sql fragments (joins) which can be used to build queries that
+ * will select an appropriate set of students to show in the reports.
+ *
+ * @param object $cm the course module.
+ * @param object $course the course settings.
+ * @return array with four elements:
+ * 0 => integer the current group id (0 for none).
+ * 1 => \core\dml\sql_join Contains joins, wheres, params for all the students in this course.
+ * 2 => \core\dml\sql_join Contains joins, wheres, params for all the students in the current group.
+ * 3 => \core\dml\sql_join Contains joins, wheres, params for all the students to show in the report.
+ * Will be the same as either element 1 or 2.
+ */
+ protected function get_students_joins($cm, $course = null) {
+ $currentgroup = $this->get_current_group($cm, $course, $this->context);
+
+ $empty = new \core\dml\sql_join();
+ if ($currentgroup == self::NO_GROUPS_ALLOWED) {
+ return array($currentgroup, $empty, $empty, $empty);
+ }
+
+ $studentsjoins = get_enrolled_with_capabilities_join($this->context, '',
+ array('mod/quiz:attempt', 'mod/quiz:reviewmyattempts'));
+
+ if (empty($currentgroup)) {
+ return array($currentgroup, $studentsjoins, $empty, $studentsjoins);
+ }
+
+ // We have a currently selected group.
+ $groupstudentsjoins = get_enrolled_with_capabilities_join($this->context, '',
+ array('mod/quiz:attempt', 'mod/quiz:reviewmyattempts'), $currentgroup);
+
+ return array($currentgroup, $studentsjoins, $groupstudentsjoins, $groupstudentsjoins);
+ }
+
+ /**
+ * Outputs the things you commonly want at the top of a quiz report.
+ *
+ * Calls through to {@link print_header_and_tabs()} and then
+ * outputs the standard group selector, number of attempts summary,
+ * and messages to cover common cases when the report can't be shown.
+ *
+ * @param stdClass $cm the course_module information.
+ * @param stdClass $course the course settings.
+ * @param stdClass $quiz the quiz settings.
+ * @param attempts_report_options $options the current report settings.
+ * @param int $currentgroup the current group.
+ * @param bool $hasquestions whether there are any questions in the quiz.
+ * @param bool $hasstudents whether there are any relevant students.
+ */
+ protected function print_standard_header_and_messages($cm, $course, $quiz,
+ $options, $currentgroup, $hasquestions, $hasstudents) {
+ global $OUTPUT;
+
+ $this->print_header_and_tabs($cm, $course, $quiz, $this->mode);
+
+ if (groups_get_activity_groupmode($cm)) {
+ // Groups are being used, so output the group selector if we are not downloading.
+ groups_print_activity_menu($cm, $options->get_url());
+ }
+
+ // Print information on the number of existing attempts.
+ if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm, true, $currentgroup)) {
+ echo '
' . $strattemptnum . '
';
+ }
+
+ if (!$hasquestions) {
+ echo quiz_no_questions_message($quiz, $cm, $this->context);
+ } else if ($currentgroup == self::NO_GROUPS_ALLOWED) {
+ echo $OUTPUT->notification(get_string('notingroup'));
+ } else if (!$hasstudents) {
+ echo $OUTPUT->notification(get_string('nostudentsyet'));
+ } else if ($currentgroup && !$this->hasgroupstudents) {
+ echo $OUTPUT->notification(get_string('nostudentsingroup'));
+ }
+ }
+
+ /**
+ * Add all the user-related columns to the $columns and $headers arrays.
+ * @param table_sql $table the table being constructed.
+ * @param array $columns the list of columns. Added to.
+ * @param array $headers the columns headings. Added to.
+ */
+ protected function add_user_columns($table, &$columns, &$headers) {
+ global $CFG;
+ if (!$table->is_downloading() && $CFG->grade_report_showuserimage) {
+ $columns[] = 'picture';
+ $headers[] = '';
+ }
+ if (!$table->is_downloading()) {
+ $columns[] = 'fullname';
+ $headers[] = get_string('name');
+ } else {
+ $columns[] = 'lastname';
+ $headers[] = get_string('lastname');
+ $columns[] = 'firstname';
+ $headers[] = get_string('firstname');
+ }
+
+ $extrafields = \core_user\fields::get_identity_fields($this->context);
+ foreach ($extrafields as $field) {
+ $columns[] = $field;
+ $headers[] = \core_user\fields::get_display_name($field);
+ }
+ }
+
+ /**
+ * Set the display options for the user-related columns in the table.
+ * @param table_sql $table the table being constructed.
+ */
+ protected function configure_user_columns($table) {
+ $table->column_suppress('picture');
+ $table->column_suppress('fullname');
+
+ $extrafields = \core_user\fields::get_identity_fields($this->context);
+ foreach ($extrafields as $field) {
+ $table->column_suppress($field);
+ }
+
+ $table->column_class('picture', 'picture');
+ $table->column_class('lastname', 'bold');
+ $table->column_class('firstname', 'bold');
+ $table->column_class('fullname', 'bold');
+ }
+
+ /**
+ * Add the state column to the $columns and $headers arrays.
+ * @param array $columns the list of columns. Added to.
+ * @param array $headers the columns headings. Added to.
+ */
+ protected function add_state_column(&$columns, &$headers) {
+ $columns[] = 'state';
+ $headers[] = get_string('attemptstate', 'quiz');
+ }
+
+ /**
+ * Add all the time-related columns to the $columns and $headers arrays.
+ * @param array $columns the list of columns. Added to.
+ * @param array $headers the columns headings. Added to.
+ */
+ protected function add_time_columns(&$columns, &$headers) {
+ $columns[] = 'timestart';
+ $headers[] = get_string('startedon', 'quiz');
+
+ $columns[] = 'timefinish';
+ $headers[] = get_string('timecompleted', 'quiz');
+
+ $columns[] = 'duration';
+ $headers[] = get_string('attemptduration', 'quiz');
+ }
+
+ /**
+ * Add all the grade and feedback columns, if applicable, to the $columns
+ * and $headers arrays.
+ * @param object $quiz the quiz settings.
+ * @param bool $usercanseegrades whether the user is allowed to see grades for this quiz.
+ * @param array $columns the list of columns. Added to.
+ * @param array $headers the columns headings. Added to.
+ * @param bool $includefeedback whether to include the feedbacktext columns
+ */
+ protected function add_grade_columns($quiz, $usercanseegrades, &$columns, &$headers, $includefeedback = true) {
+ if ($usercanseegrades) {
+ $columns[] = 'sumgrades';
+ $headers[] = get_string('grade', 'quiz') . '/' .
+ quiz_format_grade($quiz, $quiz->grade);
+ }
+
+ if ($includefeedback && quiz_has_feedback($quiz)) {
+ $columns[] = 'feedbacktext';
+ $headers[] = get_string('feedback', 'quiz');
+ }
+ }
+
+ /**
+ * Set up the table.
+ * @param table_sql $table the table being constructed.
+ * @param array $columns the list of columns.
+ * @param array $headers the columns headings.
+ * @param moodle_url $reporturl the URL of this report.
+ * @param attempts_report_options $options the display options.
+ * @param bool $collapsible whether to allow columns in the report to be collapsed.
+ */
+ protected function set_up_table_columns($table, $columns, $headers, $reporturl,
+ attempts_report_options $options, $collapsible) {
+ $table->define_columns($columns);
+ $table->define_headers($headers);
+ $table->sortable(true, 'uniqueid');
+
+ $table->define_baseurl($options->get_url());
+
+ $this->configure_user_columns($table);
+
+ $table->no_sorting('feedbacktext');
+ $table->column_class('sumgrades', 'bold');
+
+ $table->set_attribute('id', 'attempts');
+
+ $table->collapsible($collapsible);
+ }
+
+ /**
+ * Process any submitted actions.
+ * @param object $quiz the quiz settings.
+ * @param object $cm the cm object for the quiz.
+ * @param int $currentgroup the currently selected group.
+ * @param \core\dml\sql_join $groupstudentsjoins (joins, wheres, params) the students in the current group.
+ * @param \core\dml\sql_join $allowedjoins (joins, wheres, params) the users whose attempt this user is allowed to modify.
+ * @param moodle_url $redirecturl where to redircet to after a successful action.
+ */
+ protected function process_actions($quiz, $cm, $currentgroup, \core\dml\sql_join $groupstudentsjoins,
+ \core\dml\sql_join $allowedjoins, $redirecturl) {
+ if (empty($currentgroup) || $this->hasgroupstudents) {
+ if (optional_param('delete', 0, PARAM_BOOL) && confirm_sesskey()) {
+ if ($attemptids = optional_param_array('attemptid', array(), PARAM_INT)) {
+ require_capability('mod/quiz:deleteattempts', $this->context);
+ $this->delete_selected_attempts($quiz, $cm, $attemptids, $allowedjoins);
+ redirect($redirecturl);
+ }
+ }
+ }
+ }
+
+ /**
+ * Delete the quiz attempts
+ * @param object $quiz the quiz settings. Attempts that don't belong to
+ * this quiz are not deleted.
+ * @param object $cm the course_module object.
+ * @param array $attemptids the list of attempt ids to delete.
+ * @param \core\dml\sql_join $allowedjoins (joins, wheres, params) This list of userids that are visible in the report.
+ * Users can only delete attempts that they are allowed to see in the report.
+ * Empty means all users.
+ */
+ protected function delete_selected_attempts($quiz, $cm, $attemptids, \core\dml\sql_join $allowedjoins) {
+ global $DB;
+
+ foreach ($attemptids as $attemptid) {
+ if (empty($allowedjoins->joins)) {
+ $sql = "SELECT quiza.*
+ FROM {quiz_attempts} quiza
+ JOIN {user} u ON u.id = quiza.userid
+ WHERE quiza.id = :attemptid";
+ } else {
+ $sql = "SELECT quiza.*
+ FROM {quiz_attempts} quiza
+ JOIN {user} u ON u.id = quiza.userid
+ {$allowedjoins->joins}
+ WHERE {$allowedjoins->wheres} AND quiza.id = :attemptid";
+ }
+ $params = $allowedjoins->params + array('attemptid' => $attemptid);
+ $attempt = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE);
+ if (!$attempt || $attempt->quiz != $quiz->id || $attempt->preview != 0) {
+ // Ensure the attempt exists, belongs to this quiz and belongs to
+ // a student included in the report. If not skip.
+ continue;
+ }
+
+ // Set the course module id before calling quiz_delete_attempt().
+ $quiz->cmid = $cm->id;
+ quiz_delete_attempt($attempt, $quiz);
+ }
+ }
+
+ /**
+ * Get information about which students to show in the report.
+ * @param object $cm the coures module.
+ * @param object $course the course settings.
+ * @return array with four elements:
+ * 0 => integer the current group id (0 for none).
+ * 1 => array ids of all the students in this course.
+ * 2 => array ids of all the students in the current group.
+ * 3 => array ids of all the students to show in the report. Will be the
+ * same as either element 1 or 2.
+ * @deprecated since Moodle 3.2 Please use get_students_joins() instead.
+ */
+ protected function load_relevant_students($cm, $course = null) {
+ $msg = 'The function load_relevant_students() is deprecated. Please use get_students_joins() instead.';
+ throw new coding_exception($msg);
+ }
+}
diff --git a/mod/quiz/classes/local/reports/attempts_report_options.php b/mod/quiz/classes/local/reports/attempts_report_options.php
new file mode 100644
index 00000000000..7bd2d951bf3
--- /dev/null
+++ b/mod/quiz/classes/local/reports/attempts_report_options.php
@@ -0,0 +1,285 @@
+.
+
+namespace mod_quiz\local\reports;
+
+use context_module;
+use moodle_url;
+use quiz_attempt;
+use stdClass;
+use mod_quiz\local\reports\attempts_report;
+
+/**
+ * Base class for the options that control what is visible in an {@see attempts_report}.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class attempts_report_options {
+
+ /** @var string the report mode. */
+ public $mode;
+
+ /** @var object the settings for the quiz being reported on. */
+ public $quiz;
+
+ /** @var object the course module objects for the quiz being reported on. */
+ public $cm;
+
+ /** @var object the course settings for the course the quiz is in. */
+ public $course;
+
+ /**
+ * @var array form field name => corresponding quiz_attempt:: state constant.
+ */
+ protected static $statefields = array(
+ 'stateinprogress' => quiz_attempt::IN_PROGRESS,
+ 'stateoverdue' => quiz_attempt::OVERDUE,
+ 'statefinished' => quiz_attempt::FINISHED,
+ 'stateabandoned' => quiz_attempt::ABANDONED,
+ );
+
+ /**
+ * @var string attempts_report::ALL_WITH, attempts_report::ENROLLED_WITH,
+ * attempts_report::ENROLLED_WITHOUT or attempts_report::ENROLLED_ALL
+ */
+ public $attempts = attempts_report::ENROLLED_WITH;
+
+ /** @var int the currently selected group. 0 if no group is selected. */
+ public $group = 0;
+
+ /**
+ * @var array|null of quiz_attempt::IN_PROGRESS, etc. constants. null means
+ * no restriction.
+ */
+ public $states = array(quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE,
+ quiz_attempt::FINISHED, quiz_attempt::ABANDONED);
+
+ /**
+ * @var bool whether to show all finished attmepts, or just the one that gave
+ * the final grade for the user.
+ */
+ public $onlygraded = false;
+
+ /** @var int Number of attempts to show per page. */
+ public $pagesize = attempts_report::DEFAULT_PAGE_SIZE;
+
+ /** @var string whether the data should be downloaded in some format, or '' to display it. */
+ public $download = '';
+
+ /** @var bool whether the current user has permission to see grades. */
+ public $usercanseegrades;
+
+ /** @var bool whether the report table should have a column of checkboxes. */
+ public $checkboxcolumn = false;
+
+ /**
+ * Constructor.
+ * @param string $mode which report these options are for.
+ * @param object $quiz the settings for the quiz being reported on.
+ * @param object $cm the course module objects for the quiz being reported on.
+ * @param object $coures the course settings for the coures this quiz is in.
+ */
+ public function __construct($mode, $quiz, $cm, $course) {
+ $this->mode = $mode;
+ $this->quiz = $quiz;
+ $this->cm = $cm;
+ $this->course = $course;
+
+ $this->usercanseegrades = quiz_report_should_show_grades($quiz, context_module::instance($cm->id));
+ }
+
+ /**
+ * Get the URL parameters required to show the report with these options.
+ * @return array URL parameter name => value.
+ */
+ protected function get_url_params() {
+ $params = array(
+ 'id' => $this->cm->id,
+ 'mode' => $this->mode,
+ 'attempts' => $this->attempts,
+ 'onlygraded' => $this->onlygraded,
+ );
+
+ if ($this->states) {
+ $params['states'] = implode('-', $this->states);
+ }
+
+ if (groups_get_activity_groupmode($this->cm, $this->course)) {
+ $params['group'] = $this->group;
+ }
+ return $params;
+ }
+
+ /**
+ * Get the URL to show the report with these options.
+ * @return moodle_url the URL.
+ */
+ public function get_url() {
+ return new moodle_url('/mod/quiz/report.php', $this->get_url_params());
+ }
+
+ /**
+ * Process the data we get when the settings form is submitted. This includes
+ * updating the fields of this class, and updating the user preferences
+ * where appropriate.
+ * @param object $fromform The data from $mform->get_data() from the settings form.
+ */
+ public function process_settings_from_form($fromform) {
+ $this->setup_from_form_data($fromform);
+ $this->resolve_dependencies();
+ $this->update_user_preferences();
+ }
+
+ /**
+ * Set up this preferences object using optional_param (using user_preferences
+ * to set anything not specified by the params.
+ */
+ public function process_settings_from_params() {
+ $this->setup_from_user_preferences();
+ $this->setup_from_params();
+ $this->resolve_dependencies();
+ }
+
+ /**
+ * Get the current value of the settings to pass to the settings form.
+ */
+ public function get_initial_form_data() {
+ $toform = new stdClass();
+ $toform->attempts = $this->attempts;
+ $toform->onlygraded = $this->onlygraded;
+ $toform->pagesize = $this->pagesize;
+
+ if ($this->states) {
+ foreach (self::$statefields as $field => $state) {
+ $toform->$field = in_array($state, $this->states);
+ }
+ }
+
+ return $toform;
+ }
+
+ /**
+ * Set the fields of this object from the form data.
+ * @param object $fromform The data from $mform->get_data() from the settings form.
+ */
+ public function setup_from_form_data($fromform) {
+ $this->attempts = $fromform->attempts;
+ $this->group = groups_get_activity_group($this->cm, true);
+ $this->onlygraded = !empty($fromform->onlygraded);
+ $this->pagesize = $fromform->pagesize;
+
+ $this->states = array();
+ foreach (self::$statefields as $field => $state) {
+ if (!empty($fromform->$field)) {
+ $this->states[] = $state;
+ }
+ }
+ }
+
+ /**
+ * Set the fields of this object from the URL parameters.
+ */
+ public function setup_from_params() {
+ $this->attempts = optional_param('attempts', $this->attempts, PARAM_ALPHAEXT);
+ $this->group = groups_get_activity_group($this->cm, true);
+ $this->onlygraded = optional_param('onlygraded', $this->onlygraded, PARAM_BOOL);
+ $this->pagesize = optional_param('pagesize', $this->pagesize, PARAM_INT);
+
+ $states = optional_param('states', '', PARAM_ALPHAEXT);
+ if (!empty($states)) {
+ $this->states = explode('-', $states);
+ }
+
+ $this->download = optional_param('download', $this->download, PARAM_ALPHA);
+ }
+
+ /**
+ * Set the fields of this object from the user's preferences.
+ * (For those settings that are backed by user-preferences).
+ */
+ public function setup_from_user_preferences() {
+ $this->pagesize = get_user_preferences('quiz_report_pagesize', $this->pagesize);
+ }
+
+ /**
+ * Update the user preferences so they match the settings in this object.
+ * (For those settings that are backed by user-preferences).
+ */
+ public function update_user_preferences() {
+ set_user_preference('quiz_report_pagesize', $this->pagesize);
+ }
+
+ /**
+ * Check the settings, and remove any 'impossible' combinations.
+ */
+ public function resolve_dependencies() {
+ if ($this->group) {
+ // Default for when a group is selected.
+ if ($this->attempts === null || $this->attempts == attempts_report::ALL_WITH) {
+ $this->attempts = attempts_report::ENROLLED_WITH;
+ }
+
+ } else if (!$this->group && $this->course->id == SITEID) {
+ // Force report on front page to show all, unless a group is selected.
+ $this->attempts = attempts_report::ALL_WITH;
+
+ } else if (!in_array($this->attempts, array(attempts_report::ALL_WITH, attempts_report::ENROLLED_WITH,
+ attempts_report::ENROLLED_WITHOUT, attempts_report::ENROLLED_ALL))) {
+ $this->attempts = attempts_report::ENROLLED_WITH;
+ }
+
+ $cleanstates = array();
+ foreach (self::$statefields as $state) {
+ if (in_array($state, $this->states)) {
+ $cleanstates[] = $state;
+ }
+ }
+ $this->states = $cleanstates;
+ if (count($this->states) == count(self::$statefields)) {
+ // If all states have been selected, then there is no constraint
+ // required in the SQL, so clear the array.
+ $this->states = null;
+ }
+
+ if (!quiz_report_can_filter_only_graded($this->quiz)) {
+ // A grading mode like 'average' has been selected, so we cannot do
+ // the show the attempt that gave the final grade thing.
+ $this->onlygraded = false;
+ }
+
+ if ($this->attempts == attempts_report::ENROLLED_WITHOUT) {
+ $this->states = null;
+ $this->onlygraded = false;
+ }
+
+ if (!$this->is_showing_finished_attempts()) {
+ $this->onlygraded = false;
+ }
+
+ if ($this->pagesize < 1) {
+ $this->pagesize = attempts_report::DEFAULT_PAGE_SIZE;
+ }
+ }
+
+ /**
+ * Whether the options are such that finished attempts are being shown.
+ * @return boolean
+ */
+ protected function is_showing_finished_attempts() {
+ return $this->states === null || in_array(quiz_attempt::FINISHED, $this->states);
+ }
+}
diff --git a/mod/quiz/classes/local/reports/attempts_report_options_form.php b/mod/quiz/classes/local/reports/attempts_report_options_form.php
new file mode 100644
index 00000000000..a5549089a92
--- /dev/null
+++ b/mod/quiz/classes/local/reports/attempts_report_options_form.php
@@ -0,0 +1,115 @@
+.
+
+namespace mod_quiz\local\reports;
+
+use html_writer;
+use MoodleQuickForm;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+
+/**
+ * Base class for the settings form for {@see attempts_report}s.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class attempts_report_options_form extends \moodleform {
+
+ protected function definition() {
+ $mform = $this->_form;
+
+ $mform->addElement('header', 'preferencespage',
+ get_string('reportwhattoinclude', 'quiz'));
+
+ $this->standard_attempt_fields($mform);
+ $this->other_attempt_fields($mform);
+
+ $mform->addElement('header', 'preferencesuser',
+ get_string('reportdisplayoptions', 'quiz'));
+
+ $this->standard_preference_fields($mform);
+ $this->other_preference_fields($mform);
+
+ $mform->addElement('submit', 'submitbutton',
+ get_string('showreport', 'quiz'));
+ }
+
+ protected function standard_attempt_fields(MoodleQuickForm $mform) {
+
+ $mform->addElement('select', 'attempts', get_string('reportattemptsfrom', 'quiz'), array(
+ attempts_report::ENROLLED_WITH => get_string('reportuserswith', 'quiz'),
+ attempts_report::ENROLLED_WITHOUT => get_string('reportuserswithout', 'quiz'),
+ attempts_report::ENROLLED_ALL => get_string('reportuserswithorwithout', 'quiz'),
+ attempts_report::ALL_WITH => get_string('reportusersall', 'quiz'),
+ ));
+
+ $stategroup = array(
+ $mform->createElement('advcheckbox', 'stateinprogress', '',
+ get_string('stateinprogress', 'quiz')),
+ $mform->createElement('advcheckbox', 'stateoverdue', '',
+ get_string('stateoverdue', 'quiz')),
+ $mform->createElement('advcheckbox', 'statefinished', '',
+ get_string('statefinished', 'quiz')),
+ $mform->createElement('advcheckbox', 'stateabandoned', '',
+ get_string('stateabandoned', 'quiz')),
+ );
+ $mform->addGroup($stategroup, 'stateoptions',
+ get_string('reportattemptsthatare', 'quiz'), array(' '), false);
+ $mform->setDefault('stateinprogress', 1);
+ $mform->setDefault('stateoverdue', 1);
+ $mform->setDefault('statefinished', 1);
+ $mform->setDefault('stateabandoned', 1);
+ $mform->disabledIf('stateinprogress', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('stateoverdue', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('statefinished', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('stateabandoned', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
+
+ if (quiz_report_can_filter_only_graded($this->_customdata['quiz'])) {
+ $gm = html_writer::tag('span',
+ quiz_get_grading_option_name($this->_customdata['quiz']->grademethod),
+ array('class' => 'highlight'));
+ $mform->addElement('advcheckbox', 'onlygraded', '',
+ get_string('reportshowonlyfinished', 'quiz', $gm));
+ $mform->disabledIf('onlygraded', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('onlygraded', 'statefinished', 'notchecked');
+ }
+ }
+
+ protected function other_attempt_fields(MoodleQuickForm $mform) {
+ }
+
+ protected function standard_preference_fields(MoodleQuickForm $mform) {
+ $mform->addElement('text', 'pagesize', get_string('pagesize', 'quiz'));
+ $mform->setType('pagesize', PARAM_INT);
+ }
+
+ protected function other_preference_fields(MoodleQuickForm $mform) {
+ }
+
+ public function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+
+ if ($data['attempts'] != attempts_report::ENROLLED_WITHOUT && !(
+ $data['stateinprogress'] || $data['stateoverdue'] || $data['statefinished'] || $data['stateabandoned'])) {
+ $errors['stateoptions'] = get_string('reportmustselectstate', 'quiz');
+ }
+
+ return $errors;
+ }
+}
diff --git a/mod/quiz/classes/local/reports/attempts_report_table.php b/mod/quiz/classes/local/reports/attempts_report_table.php
new file mode 100644
index 00000000000..16a8e783fa5
--- /dev/null
+++ b/mod/quiz/classes/local/reports/attempts_report_table.php
@@ -0,0 +1,707 @@
+.
+
+namespace mod_quiz\local\reports;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/tablelib.php');
+
+use coding_exception;
+use context;
+use html_writer;
+use moodle_url;
+use popup_action;
+use question_state;
+use qubaid_condition;
+use qubaid_join;
+use qubaid_list;
+use question_engine_data_mapper;
+use quiz_attempt;
+use stdClass;
+
+/**
+ * Base class for the table used by a {@see attempts_report}.
+ *
+ * @package mod_quiz
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class attempts_report_table extends \table_sql {
+ public $useridfield = 'userid';
+
+ /** @var moodle_url the URL of this report. */
+ protected $reporturl;
+
+ /** @var array the display options. */
+ protected $displayoptions;
+
+ /**
+ * @var array information about the latest step of each question.
+ * Loaded by {@link load_question_latest_steps()}, if applicable.
+ */
+ protected $lateststeps = null;
+
+ /** @var object the quiz settings for the quiz we are reporting on. */
+ protected $quiz;
+
+ /** @var context the quiz context. */
+ protected $context;
+
+ /** @var string HTML fragment to select the first/best/last attempt, if appropriate. */
+ protected $qmsubselect;
+
+ /** @var object attempts_report_options the options affecting this report. */
+ protected $options;
+
+ /** @var \core\dml\sql_join Contains joins, wheres, params to find students
+ * in the currently selected group, if applicable.
+ */
+ protected $groupstudentsjoins;
+
+ /** @var \core\dml\sql_join Contains joins, wheres, params to find the students in the course. */
+ protected $studentsjoins;
+
+ /** @var object the questions that comprise this quiz.. */
+ protected $questions;
+
+ /** @var bool whether to include the column with checkboxes to select each attempt. */
+ protected $includecheckboxes;
+
+ /** @var string The toggle group name for the checkboxes in the checkbox column. */
+ protected $togglegroup = 'quiz-attempts';
+
+ /**
+ * Constructor
+ * @param string $uniqueid
+ * @param object $quiz
+ * @param context $context
+ * @param string $qmsubselect
+ * @param attempts_report_options $options
+ * @param \core\dml\sql_join $groupstudentsjoins Contains joins, wheres, params
+ * @param \core\dml\sql_join $studentsjoins Contains joins, wheres, params
+ * @param array $questions
+ * @param moodle_url $reporturl
+ */
+ public function __construct($uniqueid, $quiz, $context, $qmsubselect,
+ attempts_report_options $options, \core\dml\sql_join $groupstudentsjoins, \core\dml\sql_join $studentsjoins,
+ $questions, $reporturl) {
+ parent::__construct($uniqueid);
+ $this->quiz = $quiz;
+ $this->context = $context;
+ $this->qmsubselect = $qmsubselect;
+ $this->groupstudentsjoins = $groupstudentsjoins;
+ $this->studentsjoins = $studentsjoins;
+ $this->questions = $questions;
+ $this->includecheckboxes = $options->checkboxcolumn;
+ $this->reporturl = $reporturl;
+ $this->options = $options;
+ }
+
+ /**
+ * Generate the display of the checkbox column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_checkbox($attempt) {
+ global $OUTPUT;
+
+ if ($attempt->attempt) {
+ $checkbox = new \core\output\checkbox_toggleall($this->togglegroup, false, [
+ 'id' => "attemptid_{$attempt->attempt}",
+ 'name' => 'attemptid[]',
+ 'value' => $attempt->attempt,
+ 'label' => get_string('selectattempt', 'quiz'),
+ 'labelclasses' => 'accesshide',
+ ]);
+ return $OUTPUT->render($checkbox);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Generate the display of the user's picture column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_picture($attempt) {
+ global $OUTPUT;
+ $user = new stdClass();
+ $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
+ $user = username_load_fields_from_object($user, $attempt, null, $additionalfields);
+ $user->id = $attempt->userid;
+ return $OUTPUT->user_picture($user);
+ }
+
+ /**
+ * Generate the display of the user's full name column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_fullname($attempt) {
+ $html = parent::col_fullname($attempt);
+ if ($this->is_downloading() || empty($attempt->attempt)) {
+ return $html;
+ }
+
+ return $html . html_writer::empty_tag('br') . html_writer::link(
+ new moodle_url('/mod/quiz/review.php', array('attempt' => $attempt->attempt)),
+ get_string('reviewattempt', 'quiz'), array('class' => 'reviewlink'));
+ }
+
+ /**
+ * Generate the display of the attempt state column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_state($attempt) {
+ if (!is_null($attempt->attempt)) {
+ return quiz_attempt::state_name($attempt->state);
+ } else {
+ return '-';
+ }
+ }
+
+ /**
+ * Generate the display of the start time column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_timestart($attempt) {
+ if ($attempt->attempt) {
+ return userdate($attempt->timestart, $this->strtimeformat);
+ } else {
+ return '-';
+ }
+ }
+
+ /**
+ * Generate the display of the finish time column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_timefinish($attempt) {
+ if ($attempt->attempt && $attempt->timefinish) {
+ return userdate($attempt->timefinish, $this->strtimeformat);
+ } else {
+ return '-';
+ }
+ }
+
+ /**
+ * Generate the display of the time taken column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_duration($attempt) {
+ if ($attempt->timefinish) {
+ return format_time($attempt->timefinish - $attempt->timestart);
+ } else {
+ return '-';
+ }
+ }
+
+ /**
+ * Generate the display of the feedback column.
+ * @param object $attempt the table row being output.
+ * @return string HTML content to go inside the td.
+ */
+ public function col_feedbacktext($attempt) {
+ if ($attempt->state != quiz_attempt::FINISHED) {
+ return '-';
+ }
+
+ $feedback = quiz_report_feedback_for_grade(
+ quiz_rescale_grade($attempt->sumgrades, $this->quiz, false),
+ $this->quiz->id, $this->context);
+
+ if ($this->is_downloading()) {
+ $feedback = strip_tags($feedback);
+ }
+
+ return $feedback;
+ }
+
+ public function get_row_class($attempt) {
+ if ($this->qmsubselect && $attempt->gradedattempt) {
+ return 'gradedattempt';
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Make a link to review an individual question in a popup window.
+ *
+ * @param string $data HTML fragment. The text to make into the link.
+ * @param object $attempt data for the row of the table being output.
+ * @param int $slot the number used to identify this question within this usage.
+ */
+ public function make_review_link($data, $attempt, $slot) {
+ global $OUTPUT, $CFG;
+
+ $flag = '';
+ if ($this->is_flagged($attempt->usageid, $slot)) {
+ $flag = $OUTPUT->pix_icon('i/flagged', get_string('flagged', 'question'),
+ 'moodle', array('class' => 'questionflag'));
+ }
+
+ $feedbackimg = '';
+ $state = $this->slot_state($attempt, $slot);
+ if ($state && $state->is_finished() && $state != question_state::$needsgrading) {
+ $feedbackimg = $this->icon_for_fraction($this->slot_fraction($attempt, $slot));
+ }
+
+ $output = html_writer::tag('span', $feedbackimg . html_writer::tag('span',
+ $data, array('class' => $state->get_state_class(true))) . $flag, array('class' => 'que'));
+
+ $reviewparams = array('attempt' => $attempt->attempt, 'slot' => $slot);
+ if (isset($attempt->try)) {
+ $reviewparams['step'] = $this->step_no_for_try($attempt->usageid, $slot, $attempt->try);
+ }
+ $url = new moodle_url('/mod/quiz/reviewquestion.php', $reviewparams);
+ $output = $OUTPUT->action_link($url, $output,
+ new popup_action('click', $url, 'reviewquestion',
+ array('height' => 450, 'width' => 650)),
+ array('title' => get_string('reviewresponse', 'quiz')));
+
+ if (!empty($CFG->enableplagiarism)) {
+ require_once($CFG->libdir . '/plagiarismlib.php');
+ $output .= plagiarism_get_links([
+ 'context' => $this->context->id,
+ 'component' => 'qtype_'.$this->questions[$slot]->qtype,
+ 'cmid' => $this->context->instanceid,
+ 'area' => $attempt->usageid,
+ 'itemid' => $slot,
+ 'userid' => $attempt->userid]);
+ }
+ return $output;
+ }
+
+ /**
+ * @param object $attempt the row data
+ * @param int $slot
+ * @return question_state
+ */
+ protected function slot_state($attempt, $slot) {
+ $stepdata = $this->lateststeps[$attempt->usageid][$slot];
+ return question_state::get($stepdata->state);
+ }
+
+ /**
+ * @param int $questionusageid
+ * @param int $slot
+ * @return bool
+ */
+ protected function is_flagged($questionusageid, $slot) {
+ $stepdata = $this->lateststeps[$questionusageid][$slot];
+ return $stepdata->flagged;
+ }
+
+
+ /**
+ * @param object $attempt the row data
+ * @param int $slot
+ * @return float
+ */
+ protected function slot_fraction($attempt, $slot) {
+ $stepdata = $this->lateststeps[$attempt->usageid][$slot];
+ return $stepdata->fraction;
+ }
+
+ /**
+ * Return an appropriate icon (green tick, red cross, etc.) for a grade.
+ * @param float $fraction grade on a scale 0..1.
+ * @return string html fragment.
+ */
+ protected function icon_for_fraction($fraction) {
+ global $OUTPUT;
+
+ $feedbackclass = question_state::graded_state_for_fraction($fraction)->get_feedback_class();
+ return $OUTPUT->pix_icon('i/grade_' . $feedbackclass, get_string($feedbackclass, 'question'),
+ 'moodle', array('class' => 'icon'));
+ }
+
+ /**
+ * Load any extra data after main query. At this point you can call {@link get_qubaids_condition} to get the condition that
+ * limits the query to just the question usages shown in this report page or alternatively for all attempts if downloading a
+ * full report.
+ */
+ protected function load_extra_data() {
+ $this->lateststeps = $this->load_question_latest_steps();
+ }
+
+ /**
+ * Load information about the latest state of selected questions in selected attempts.
+ *
+ * The results are returned as an two dimensional array $qubaid => $slot => $dataobject
+ *
+ * @param qubaid_condition|null $qubaids used to restrict which usages are included
+ * in the query. See {@link qubaid_condition}.
+ * @return array of records. See the SQL in this function to see the fields available.
+ */
+ protected function load_question_latest_steps(qubaid_condition $qubaids = null) {
+ if ($qubaids === null) {
+ $qubaids = $this->get_qubaids_condition();
+ }
+ $dm = new question_engine_data_mapper();
+ $latesstepdata = $dm->load_questions_usages_latest_steps(
+ $qubaids, array_keys($this->questions));
+
+ $lateststeps = array();
+ foreach ($latesstepdata as $step) {
+ $lateststeps[$step->questionusageid][$step->slot] = $step;
+ }
+
+ return $lateststeps;
+ }
+
+ /**
+ * Does this report require loading any more data after the main query. After the main query then
+ * you can use $this->get
+ *
+ * @return bool should {@link query_db()} call {@link load_extra_data}?
+ */
+ protected function requires_extra_data() {
+ return $this->requires_latest_steps_loaded();
+ }
+
+ /**
+ * Does this report require the detailed information for each question from the
+ * question_attempts_steps table?
+ * @return bool should {@link load_extra_data} call {@link load_question_latest_steps}?
+ */
+ protected function requires_latest_steps_loaded() {
+ return false;
+ }
+
+ /**
+ * Is this a column that depends on joining to the latest state information?
+ * If so, return the corresponding slot. If not, return false.
+ * @param string $column a column name
+ * @return int false if no, else a slot.
+ */
+ protected function is_latest_step_column($column) {
+ return false;
+ }
+
+ /**
+ * Get any fields that might be needed when sorting on date for a particular slot.
+ *
+ * Note: these values are only used for sorting. The values displayed are taken
+ * from $this->lateststeps loaded in load_extra_data().
+ *
+ * @param int $slot the slot for the column we want.
+ * @param string $alias the table alias for latest state information relating to that slot.
+ * @return string definitions of extra fields to add to the SELECT list of the query.
+ */
+ protected function get_required_latest_state_fields($slot, $alias) {
+ return '';
+ }
+
+ /**
+ * Contruct all the parts of the main database query.
+ * @param \core\dml\sql_join $allowedstudentsjoins (joins, wheres, params) defines allowed users for the report.
+ * @return array with 4 elements ($fields, $from, $where, $params) that can be used to
+ * build the actual database query.
+ */
+ public function base_sql(\core\dml\sql_join $allowedstudentsjoins) {
+ global $DB;
+
+ // Please note this uniqueid column is not the same as quiza.uniqueid.
+ $fields = 'DISTINCT ' . $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') . ' AS uniqueid,';
+
+ if ($this->qmsubselect) {
+ $fields .= "\n(CASE WHEN $this->qmsubselect THEN 1 ELSE 0 END) AS gradedattempt,";
+ }
+
+ $userfieldsapi = \core_user\fields::for_identity($this->context)->with_name()
+ ->excluding('id', 'idnumber', 'picture', 'imagealt', 'institution', 'department', 'email');
+ $userfields = $userfieldsapi->get_sql('u', true, '', '', false);
+
+ $fields .= '
+ quiza.uniqueid AS usageid,
+ quiza.id AS attempt,
+ u.id AS userid,
+ u.idnumber,
+ u.picture,
+ u.imagealt,
+ u.institution,
+ u.department,
+ u.email,' . $userfields->selects . ',
+ quiza.state,
+ quiza.sumgrades,
+ quiza.timefinish,
+ quiza.timestart,
+ CASE WHEN quiza.timefinish = 0 THEN null
+ WHEN quiza.timefinish > quiza.timestart THEN quiza.timefinish - quiza.timestart
+ ELSE 0 END AS duration';
+ // To explain that last bit, timefinish can be non-zero and less
+ // than timestart when you have two load-balanced servers with very
+ // badly synchronised clocks, and a student does a really quick attempt.
+
+ // This part is the same for all cases. Join the users and quiz_attempts tables.
+ $from = " {user} u";
+ $from .= "\n{$userfields->joins}";
+ $from .= "\nLEFT JOIN {quiz_attempts} quiza ON
+ quiza.userid = u.id AND quiza.quiz = :quizid";
+ $params = array_merge($userfields->params, ['quizid' => $this->quiz->id]);
+
+ if ($this->qmsubselect && $this->options->onlygraded) {
+ $from .= " AND (quiza.state <> :finishedstate OR $this->qmsubselect)";
+ $params['finishedstate'] = quiz_attempt::FINISHED;
+ }
+
+ switch ($this->options->attempts) {
+ case attempts_report::ALL_WITH:
+ // Show all attempts, including students who are no longer in the course.
+ $where = 'quiza.id IS NOT NULL AND quiza.preview = 0';
+ break;
+ case attempts_report::ENROLLED_WITH:
+ // Show only students with attempts.
+ $from .= "\n" . $allowedstudentsjoins->joins;
+ $where = "quiza.preview = 0 AND quiza.id IS NOT NULL AND " . $allowedstudentsjoins->wheres;
+ $params = array_merge($params, $allowedstudentsjoins->params);
+ break;
+ case attempts_report::ENROLLED_WITHOUT:
+ // Show only students without attempts.
+ $from .= "\n" . $allowedstudentsjoins->joins;
+ $where = "quiza.id IS NULL AND " . $allowedstudentsjoins->wheres;
+ $params = array_merge($params, $allowedstudentsjoins->params);
+ break;
+ case attempts_report::ENROLLED_ALL:
+ // Show all students with or without attempts.
+ $from .= "\n" . $allowedstudentsjoins->joins;
+ $where = "(quiza.preview = 0 OR quiza.preview IS NULL) AND " . $allowedstudentsjoins->wheres;
+ $params = array_merge($params, $allowedstudentsjoins->params);
+ break;
+ }
+
+ if ($this->options->states) {
+ list($statesql, $stateparams) = $DB->get_in_or_equal($this->options->states,
+ SQL_PARAMS_NAMED, 'state');
+ $params += $stateparams;
+ $where .= " AND (quiza.state $statesql OR quiza.state IS NULL)";
+ }
+
+ return array($fields, $from, $where, $params);
+ }
+
+ /**
+ * A chance for subclasses to modify the SQL after the count query has been generated,
+ * and before the full query is constructed.
+ * @param string $fields SELECT list.
+ * @param string $from JOINs part of the SQL.
+ * @param string $where WHERE clauses.
+ * @param array $params Query params.
+ * @return array with 4 elements ($fields, $from, $where, $params) as from base_sql.
+ */
+ protected function update_sql_after_count($fields, $from, $where, $params) {
+ return [$fields, $from, $where, $params];
+ }
+
+ /**
+ * Set up the SQL queries (count rows, and get data).
+ *
+ * @param \core\dml\sql_join $allowedjoins (joins, wheres, params) defines allowed users for the report.
+ */
+ public function setup_sql_queries($allowedjoins) {
+ list($fields, $from, $where, $params) = $this->base_sql($allowedjoins);
+
+ // The WHERE clause is vital here, because some parts of tablelib.php will expect to
+ // add bits like ' AND x = 1' on the end, and that needs to leave to valid SQL.
+ $this->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params);
+
+ list($fields, $from, $where, $params) = $this->update_sql_after_count($fields, $from, $where, $params);
+ $this->set_sql($fields, $from, $where, $params);
+ }
+
+ /**
+ * Add the information about the latest state of the question with slot
+ * $slot to the query.
+ *
+ * The extra information is added as a join to a
+ * 'table' with alias qa$slot, with columns that are a union of
+ * the columns of the question_attempts and question_attempts_states tables.
+ *
+ * @param int $slot the question to add information for.
+ */
+ protected function add_latest_state_join($slot) {
+ $alias = 'qa' . $slot;
+
+ $fields = $this->get_required_latest_state_fields($slot, $alias);
+ if (!$fields) {
+ return;
+ }
+
+ // This condition roughly filters the list of attempts to be considered.
+ // It is only used in a subselect to help crappy databases (see MDL-30122)
+ // therefore, it is better to use a very simple join, which may include
+ // too many records, than to do a super-accurate join.
+ $qubaids = new qubaid_join("{quiz_attempts} {$alias}quiza", "{$alias}quiza.uniqueid",
+ "{$alias}quiza.quiz = :{$alias}quizid", array("{$alias}quizid" => $this->sql->params['quizid']));
+
+ $dm = new question_engine_data_mapper();
+ list($inlineview, $viewparams) = $dm->question_attempt_latest_state_view($alias, $qubaids);
+
+ $this->sql->fields .= ",\n$fields";
+ $this->sql->from .= "\nLEFT JOIN $inlineview ON " .
+ "$alias.questionusageid = quiza.uniqueid AND $alias.slot = :{$alias}slot";
+ $this->sql->params[$alias . 'slot'] = $slot;
+ $this->sql->params = array_merge($this->sql->params, $viewparams);
+ }
+
+ /**
+ * Get an appropriate qubaid_condition for loading more data about the
+ * attempts we are displaying.
+ * @return qubaid_condition
+ */
+ protected function get_qubaids_condition() {
+ if (is_null($this->rawdata)) {
+ throw new coding_exception(
+ 'Cannot call get_qubaids_condition until the main data has been loaded.');
+ }
+
+ if ($this->is_downloading()) {
+ // We want usages for all attempts.
+ return new qubaid_join("(
+ SELECT DISTINCT quiza.uniqueid
+ FROM " . $this->sql->from . "
+ WHERE " . $this->sql->where . "
+ ) quizasubquery", 'quizasubquery.uniqueid',
+ "1 = 1", $this->sql->params);
+ }
+
+ $qubaids = array();
+ foreach ($this->rawdata as $attempt) {
+ if ($attempt->usageid > 0) {
+ $qubaids[] = $attempt->usageid;
+ }
+ }
+
+ return new qubaid_list($qubaids);
+ }
+
+ public function query_db($pagesize, $useinitialsbar = true) {
+ $doneslots = array();
+ foreach ($this->get_sort_columns() as $column => $notused) {
+ $slot = $this->is_latest_step_column($column);
+ if ($slot && !in_array($slot, $doneslots)) {
+ $this->add_latest_state_join($slot);
+ $doneslots[] = $slot;
+ }
+ }
+
+ parent::query_db($pagesize, $useinitialsbar);
+
+ if ($this->requires_extra_data()) {
+ $this->load_extra_data();
+ }
+ }
+
+ public function get_sort_columns() {
+ // Add attemptid as a final tie-break to the sort. This ensures that
+ // Attempts by the same student appear in order when just sorting by name.
+ $sortcolumns = parent::get_sort_columns();
+ $sortcolumns['quiza.id'] = SORT_ASC;
+ return $sortcolumns;
+ }
+
+ public function wrap_html_start() {
+ if ($this->is_downloading() || !$this->includecheckboxes) {
+ return;
+ }
+
+ $url = $this->options->get_url();
+ $url->param('sesskey', sesskey());
+
+ echo '';
+ }
+
+ /**
+ * Output any submit buttons required by the $this->includecheckboxes form.
+ */
+ protected function submit_buttons() {
+ global $PAGE;
+ if (has_capability('mod/quiz:deleteattempts', $this->context)) {
+ $deletebuttonparams = [
+ 'type' => 'submit',
+ 'class' => 'btn btn-secondary mr-1',
+ 'id' => 'deleteattemptsbutton',
+ 'name' => 'delete',
+ 'value' => get_string('deleteselected', 'quiz_overview'),
+ 'data-action' => 'toggle',
+ 'data-togglegroup' => $this->togglegroup,
+ 'data-toggle' => 'action',
+ 'disabled' => true
+ ];
+ echo html_writer::empty_tag('input', $deletebuttonparams);
+ $PAGE->requires->event_handler('#deleteattemptsbutton', 'click', 'M.util.show_confirm_dialog',
+ array('message' => get_string('deleteattemptcheck', 'quiz')));
+ }
+ }
+
+ /**
+ * Generates the contents for the checkbox column header.
+ *
+ * It returns the HTML for a master \core\output\checkbox_toggleall component that selects/deselects all quiz attempts.
+ *
+ * @param string $columnname The name of the checkbox column.
+ * @return string
+ */
+ public function checkbox_col_header(string $columnname) {
+ global $OUTPUT;
+
+ // Make sure to disable sorting on this column.
+ $this->no_sorting($columnname);
+
+ // Build the select/deselect all control.
+ $selectallid = $this->uniqueid . '-selectall-attempts';
+ $selectalltext = get_string('selectall', 'quiz');
+ $deselectalltext = get_string('selectnone', 'quiz');
+ $mastercheckbox = new \core\output\checkbox_toggleall($this->togglegroup, true, [
+ 'id' => $selectallid,
+ 'name' => $selectallid,
+ 'value' => 1,
+ 'label' => $selectalltext,
+ 'labelclasses' => 'accesshide',
+ 'selectall' => $selectalltext,
+ 'deselectall' => $deselectalltext,
+ ]);
+
+ return $OUTPUT->render($mastercheckbox);
+ }
+}
diff --git a/mod/quiz/classes/local/reports/report_base.php b/mod/quiz/classes/local/reports/report_base.php
new file mode 100644
index 00000000000..0ac278a2512
--- /dev/null
+++ b/mod/quiz/classes/local/reports/report_base.php
@@ -0,0 +1,94 @@
+.
+
+namespace mod_quiz\local\reports;
+
+use context;
+use context_module;
+use stdClass;
+
+/**
+ * Base class for quiz report plugins.
+ *
+ * Doesn't do anything on its own -- it needs to be extended.
+ * This class displays quiz reports. Because it is called from
+ * within /mod/quiz/report.php you can assume that the page header
+ * and footer are taken care of.
+ *
+ * This file can refer to itself as report.php to pass variables
+ * to itself - all these will also be globally available. You must
+ * pass "id=$cm->id" or q=$quiz->id", and "mode=reportname".
+ *
+ * @package mod_quiz
+ * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class report_base {
+ const NO_GROUPS_ALLOWED = -2;
+
+ /**
+ * Override this function to displays the report.
+ * @param stdClass $cm the course-module for this quiz.
+ * @param stdClass $course the coures we are in.
+ * @param stdClass $quiz this quiz.
+ */
+ public abstract function display($cm, $course, $quiz);
+
+ /**
+ * Initialise some parts of $PAGE and start output.
+ *
+ * @param stdClass $cm the course_module information.
+ * @param stdClass $course the course settings.
+ * @param stdClass $quiz the quiz settings.
+ * @param string $reportmode the report name.
+ */
+ public function print_header_and_tabs($cm, $course, $quiz, $reportmode = 'overview') {
+ global $PAGE, $OUTPUT, $CFG;
+
+ // Print the page header.
+ $PAGE->set_title($quiz->name);
+ $PAGE->set_heading($course->fullname);
+ echo $OUTPUT->header();
+ $context = context_module::instance($cm->id);
+ if (!$PAGE->has_secondary_navigation()) {
+ echo $OUTPUT->heading(format_string($quiz->name, true, array('context' => $context)));
+ }
+ if (!empty($CFG->enableplagiarism)) {
+ require_once($CFG->libdir . '/plagiarismlib.php');
+ echo plagiarism_update_status($course, $cm);
+ }
+ }
+
+ /**
+ * Get the current group for the user user looking at the report.
+ *
+ * @param object $cm the course_module information.
+ * @param object $course the course settings.
+ * @param context $context the quiz context.
+ * @return int the current group id, if applicable. 0 for all users,
+ * NO_GROUPS_ALLOWED if the user cannot see any group.
+ */
+ public function get_current_group($cm, $course, $context) {
+ $groupmode = groups_get_activity_groupmode($cm, $course);
+ $currentgroup = groups_get_activity_group($cm, true);
+
+ if ($groupmode == SEPARATEGROUPS && !$currentgroup && !has_capability('moodle/site:accessallgroups', $context)) {
+ $currentgroup = self::NO_GROUPS_ALLOWED;
+ }
+
+ return $currentgroup;
+ }
+}
diff --git a/mod/quiz/db/renamedclasses.php b/mod/quiz/db/renamedclasses.php
index ea77f6b5de7..4904067348e 100644
--- a/mod/quiz/db/renamedclasses.php
+++ b/mod/quiz/db/renamedclasses.php
@@ -46,4 +46,9 @@ $renamedclasses = [
'mod_quiz\adminpresets\adminpresets_review_setting',
'mod_quiz\adminpresets\adminpresets_mod_quiz_admin_setting_user_image' =>
'mod_quiz\adminpresets\adminpresets_user_image_setting',
+ 'quiz_default_report' => 'mod_quiz\local\reports\report_base',
+ 'quiz_attempts_report' => 'mod_quiz\local\reports\attempts_report',
+ 'mod_quiz_attempts_report_form' => 'mod_quiz\local\reports\attempts_report_options_form',
+ 'mod_quiz_attempts_report_options' => 'mod_quiz\local\reports\attempts_report_options',
+ 'quiz_attempts_report_table' => 'mod_quiz\local\reports\attempts_report_table',
];
diff --git a/mod/quiz/report/attemptsreport.php b/mod/quiz/report/attemptsreport.php
index 3ee0fa3d0c3..df1c42fe9f1 100644
--- a/mod/quiz/report/attemptsreport.php
+++ b/mod/quiz/report/attemptsreport.php
@@ -15,371 +15,11 @@
// along with Moodle. If not, see .
/**
- * The file defines a base class that can be used to build a report like the
- * overview or responses report, that has one row per attempt.
+ * File only retained to prevent fatal errors in code that tries to require/include this.
*
- * @package mod_quiz
- * @copyright 2010 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @todo MDL-76612 delete this file as part of Moodle 4.6 development.
+ * @deprecated This file is no longer required in Moodle 4.2+.
*/
-
-
defined('MOODLE_INTERNAL') || die();
-require_once($CFG->libdir.'/tablelib.php');
-
-
-/**
- * Base class for quiz reports that are basically a table with one row for each attempt.
- *
- * @copyright 2010 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class quiz_attempts_report extends quiz_default_report {
- /** @var int default page size for reports. */
- const DEFAULT_PAGE_SIZE = 30;
-
- /** @var string constant used for the options, means all users with attempts. */
- const ALL_WITH = 'all_with';
- /** @var string constant used for the options, means only enrolled users with attempts. */
- const ENROLLED_WITH = 'enrolled_with';
- /** @var string constant used for the options, means only enrolled users without attempts. */
- const ENROLLED_WITHOUT = 'enrolled_without';
- /** @var string constant used for the options, means all enrolled users. */
- const ENROLLED_ALL = 'enrolled_any';
-
- /** @var string the mode this report is. */
- protected $mode;
-
- /** @var context_module the quiz context. */
- protected $context;
-
- /** @var mod_quiz_attempts_report_form The settings form to use. */
- protected $form;
-
- /** @var string SQL fragment for selecting the attempt that gave the final grade,
- * if applicable. */
- protected $qmsubselect;
-
- /** @var boolean caches the results of {@link should_show_grades()}. */
- protected $showgrades = null;
-
- /**
- * Initialise various aspects of this report.
- *
- * @param string $mode
- * @param string $formclass
- * @param object $quiz
- * @param object $cm
- * @param object $course
- * @return array with four elements:
- * 0 => integer the current group id (0 for none).
- * 1 => \core\dml\sql_join Contains joins, wheres, params for all the students in this course.
- * 2 => \core\dml\sql_join Contains joins, wheres, params for all the students in the current group.
- * 3 => \core\dml\sql_join Contains joins, wheres, params for all the students to show in the report.
- * Will be the same as either element 1 or 2.
- */
- public function init($mode, $formclass, $quiz, $cm, $course) {
- $this->mode = $mode;
-
- $this->context = context_module::instance($cm->id);
-
- list($currentgroup, $studentsjoins, $groupstudentsjoins, $allowedjoins) = $this->get_students_joins(
- $cm, $course);
-
- $this->qmsubselect = quiz_report_qm_filter_select($quiz);
-
- $this->form = new $formclass($this->get_base_url(),
- array('quiz' => $quiz, 'currentgroup' => $currentgroup, 'context' => $this->context));
-
- return array($currentgroup, $studentsjoins, $groupstudentsjoins, $allowedjoins);
- }
-
- /**
- * Get the base URL for this report.
- * @return moodle_url the URL.
- */
- protected function get_base_url() {
- return new moodle_url('/mod/quiz/report.php',
- array('id' => $this->context->instanceid, 'mode' => $this->mode));
- }
-
- /**
- * Get sql fragments (joins) which can be used to build queries that
- * will select an appropriate set of students to show in the reports.
- *
- * @param object $cm the course module.
- * @param object $course the course settings.
- * @return array with four elements:
- * 0 => integer the current group id (0 for none).
- * 1 => \core\dml\sql_join Contains joins, wheres, params for all the students in this course.
- * 2 => \core\dml\sql_join Contains joins, wheres, params for all the students in the current group.
- * 3 => \core\dml\sql_join Contains joins, wheres, params for all the students to show in the report.
- * Will be the same as either element 1 or 2.
- */
- protected function get_students_joins($cm, $course = null) {
- $currentgroup = $this->get_current_group($cm, $course, $this->context);
-
- $empty = new \core\dml\sql_join();
- if ($currentgroup == self::NO_GROUPS_ALLOWED) {
- return array($currentgroup, $empty, $empty, $empty);
- }
-
- $studentsjoins = get_enrolled_with_capabilities_join($this->context, '',
- array('mod/quiz:attempt', 'mod/quiz:reviewmyattempts'));
-
- if (empty($currentgroup)) {
- return array($currentgroup, $studentsjoins, $empty, $studentsjoins);
- }
-
- // We have a currently selected group.
- $groupstudentsjoins = get_enrolled_with_capabilities_join($this->context, '',
- array('mod/quiz:attempt', 'mod/quiz:reviewmyattempts'), $currentgroup);
-
- return array($currentgroup, $studentsjoins, $groupstudentsjoins, $groupstudentsjoins);
- }
-
- /**
- * Outputs the things you commonly want at the top of a quiz report.
- *
- * Calls through to {@link print_header_and_tabs()} and then
- * outputs the standard group selector, number of attempts summary,
- * and messages to cover common cases when the report can't be shown.
- *
- * @param stdClass $cm the course_module information.
- * @param stdClass $course the course settings.
- * @param stdClass $quiz the quiz settings.
- * @param mod_quiz_attempts_report_options $options the current report settings.
- * @param int $currentgroup the current group.
- * @param bool $hasquestions whether there are any questions in the quiz.
- * @param bool $hasstudents whether there are any relevant students.
- */
- protected function print_standard_header_and_messages($cm, $course, $quiz,
- $options, $currentgroup, $hasquestions, $hasstudents) {
- global $OUTPUT;
-
- $this->print_header_and_tabs($cm, $course, $quiz, $this->mode);
-
- if (groups_get_activity_groupmode($cm)) {
- // Groups are being used, so output the group selector if we are not downloading.
- groups_print_activity_menu($cm, $options->get_url());
- }
-
- // Print information on the number of existing attempts.
- if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm, true, $currentgroup)) {
- echo '' . $strattemptnum . '
';
- }
-
- if (!$hasquestions) {
- echo quiz_no_questions_message($quiz, $cm, $this->context);
- } else if ($currentgroup == self::NO_GROUPS_ALLOWED) {
- echo $OUTPUT->notification(get_string('notingroup'));
- } else if (!$hasstudents) {
- echo $OUTPUT->notification(get_string('nostudentsyet'));
- } else if ($currentgroup && !$this->hasgroupstudents) {
- echo $OUTPUT->notification(get_string('nostudentsingroup'));
- }
- }
-
- /**
- * Add all the user-related columns to the $columns and $headers arrays.
- * @param table_sql $table the table being constructed.
- * @param array $columns the list of columns. Added to.
- * @param array $headers the columns headings. Added to.
- */
- protected function add_user_columns($table, &$columns, &$headers) {
- global $CFG;
- if (!$table->is_downloading() && $CFG->grade_report_showuserimage) {
- $columns[] = 'picture';
- $headers[] = '';
- }
- if (!$table->is_downloading()) {
- $columns[] = 'fullname';
- $headers[] = get_string('name');
- } else {
- $columns[] = 'lastname';
- $headers[] = get_string('lastname');
- $columns[] = 'firstname';
- $headers[] = get_string('firstname');
- }
-
- $extrafields = \core_user\fields::get_identity_fields($this->context);
- foreach ($extrafields as $field) {
- $columns[] = $field;
- $headers[] = \core_user\fields::get_display_name($field);
- }
- }
-
- /**
- * Set the display options for the user-related columns in the table.
- * @param table_sql $table the table being constructed.
- */
- protected function configure_user_columns($table) {
- $table->column_suppress('picture');
- $table->column_suppress('fullname');
-
- $extrafields = \core_user\fields::get_identity_fields($this->context);
- foreach ($extrafields as $field) {
- $table->column_suppress($field);
- }
-
- $table->column_class('picture', 'picture');
- $table->column_class('lastname', 'bold');
- $table->column_class('firstname', 'bold');
- $table->column_class('fullname', 'bold');
- }
-
- /**
- * Add the state column to the $columns and $headers arrays.
- * @param array $columns the list of columns. Added to.
- * @param array $headers the columns headings. Added to.
- */
- protected function add_state_column(&$columns, &$headers) {
- $columns[] = 'state';
- $headers[] = get_string('attemptstate', 'quiz');
- }
-
- /**
- * Add all the time-related columns to the $columns and $headers arrays.
- * @param array $columns the list of columns. Added to.
- * @param array $headers the columns headings. Added to.
- */
- protected function add_time_columns(&$columns, &$headers) {
- $columns[] = 'timestart';
- $headers[] = get_string('startedon', 'quiz');
-
- $columns[] = 'timefinish';
- $headers[] = get_string('timecompleted', 'quiz');
-
- $columns[] = 'duration';
- $headers[] = get_string('attemptduration', 'quiz');
- }
-
- /**
- * Add all the grade and feedback columns, if applicable, to the $columns
- * and $headers arrays.
- * @param object $quiz the quiz settings.
- * @param bool $usercanseegrades whether the user is allowed to see grades for this quiz.
- * @param array $columns the list of columns. Added to.
- * @param array $headers the columns headings. Added to.
- * @param bool $includefeedback whether to include the feedbacktext columns
- */
- protected function add_grade_columns($quiz, $usercanseegrades, &$columns, &$headers, $includefeedback = true) {
- if ($usercanseegrades) {
- $columns[] = 'sumgrades';
- $headers[] = get_string('grade', 'quiz') . '/' .
- quiz_format_grade($quiz, $quiz->grade);
- }
-
- if ($includefeedback && quiz_has_feedback($quiz)) {
- $columns[] = 'feedbacktext';
- $headers[] = get_string('feedback', 'quiz');
- }
- }
-
- /**
- * Set up the table.
- * @param table_sql $table the table being constructed.
- * @param array $columns the list of columns.
- * @param array $headers the columns headings.
- * @param moodle_url $reporturl the URL of this report.
- * @param mod_quiz_attempts_report_options $options the display options.
- * @param bool $collapsible whether to allow columns in the report to be collapsed.
- */
- protected function set_up_table_columns($table, $columns, $headers, $reporturl,
- mod_quiz_attempts_report_options $options, $collapsible) {
- $table->define_columns($columns);
- $table->define_headers($headers);
- $table->sortable(true, 'uniqueid');
-
- $table->define_baseurl($options->get_url());
-
- $this->configure_user_columns($table);
-
- $table->no_sorting('feedbacktext');
- $table->column_class('sumgrades', 'bold');
-
- $table->set_attribute('id', 'attempts');
-
- $table->collapsible($collapsible);
- }
-
- /**
- * Process any submitted actions.
- * @param object $quiz the quiz settings.
- * @param object $cm the cm object for the quiz.
- * @param int $currentgroup the currently selected group.
- * @param \core\dml\sql_join $groupstudentsjoins (joins, wheres, params) the students in the current group.
- * @param \core\dml\sql_join $allowedjoins (joins, wheres, params) the users whose attempt this user is allowed to modify.
- * @param moodle_url $redirecturl where to redircet to after a successful action.
- */
- protected function process_actions($quiz, $cm, $currentgroup, \core\dml\sql_join $groupstudentsjoins,
- \core\dml\sql_join $allowedjoins, $redirecturl) {
- if (empty($currentgroup) || $this->hasgroupstudents) {
- if (optional_param('delete', 0, PARAM_BOOL) && confirm_sesskey()) {
- if ($attemptids = optional_param_array('attemptid', array(), PARAM_INT)) {
- require_capability('mod/quiz:deleteattempts', $this->context);
- $this->delete_selected_attempts($quiz, $cm, $attemptids, $allowedjoins);
- redirect($redirecturl);
- }
- }
- }
- }
-
- /**
- * Delete the quiz attempts
- * @param object $quiz the quiz settings. Attempts that don't belong to
- * this quiz are not deleted.
- * @param object $cm the course_module object.
- * @param array $attemptids the list of attempt ids to delete.
- * @param \core\dml\sql_join $allowedjoins (joins, wheres, params) This list of userids that are visible in the report.
- * Users can only delete attempts that they are allowed to see in the report.
- * Empty means all users.
- */
- protected function delete_selected_attempts($quiz, $cm, $attemptids, \core\dml\sql_join $allowedjoins) {
- global $DB;
-
- foreach ($attemptids as $attemptid) {
- if (empty($allowedjoins->joins)) {
- $sql = "SELECT quiza.*
- FROM {quiz_attempts} quiza
- JOIN {user} u ON u.id = quiza.userid
- WHERE quiza.id = :attemptid";
- } else {
- $sql = "SELECT quiza.*
- FROM {quiz_attempts} quiza
- JOIN {user} u ON u.id = quiza.userid
- {$allowedjoins->joins}
- WHERE {$allowedjoins->wheres} AND quiza.id = :attemptid";
- }
- $params = $allowedjoins->params + array('attemptid' => $attemptid);
- $attempt = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE);
- if (!$attempt || $attempt->quiz != $quiz->id || $attempt->preview != 0) {
- // Ensure the attempt exists, belongs to this quiz and belongs to
- // a student included in the report. If not skip.
- continue;
- }
-
- // Set the course module id before calling quiz_delete_attempt().
- $quiz->cmid = $cm->id;
- quiz_delete_attempt($attempt, $quiz);
- }
- }
-
- /**
- * Get information about which students to show in the report.
- * @param object $cm the coures module.
- * @param object $course the course settings.
- * @return array with four elements:
- * 0 => integer the current group id (0 for none).
- * 1 => array ids of all the students in this course.
- * 2 => array ids of all the students in the current group.
- * 3 => array ids of all the students to show in the report. Will be the
- * same as either element 1 or 2.
- * @deprecated since Moodle 3.2 Please use get_students_joins() instead.
- */
- protected function load_relevant_students($cm, $course = null) {
- $msg = 'The function load_relevant_students() is deprecated. Please use get_students_joins() instead.';
- throw new coding_exception($msg);
- }
-}
+debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);
diff --git a/mod/quiz/report/attemptsreport_form.php b/mod/quiz/report/attemptsreport_form.php
index c468f6ac20a..df1c42fe9f1 100644
--- a/mod/quiz/report/attemptsreport_form.php
+++ b/mod/quiz/report/attemptsreport_form.php
@@ -15,106 +15,11 @@
// along with Moodle. If not, see .
/**
- * Base class for the settings form for {@link quiz_attempts_report}s.
+ * File only retained to prevent fatal errors in code that tries to require/include this.
*
- * @package mod_quiz
- * @copyright 2012 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @todo MDL-76612 delete this file as part of Moodle 4.6 development.
+ * @deprecated This file is no longer required in Moodle 4.2+.
*/
-
-
defined('MOODLE_INTERNAL') || die();
-require_once($CFG->libdir . '/formslib.php');
-
-
-/**
- * Base class for the settings form for {@link quiz_attempts_report}s.
- *
- * @copyright 2012 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class mod_quiz_attempts_report_form extends moodleform {
-
- protected function definition() {
- $mform = $this->_form;
-
- $mform->addElement('header', 'preferencespage',
- get_string('reportwhattoinclude', 'quiz'));
-
- $this->standard_attempt_fields($mform);
- $this->other_attempt_fields($mform);
-
- $mform->addElement('header', 'preferencesuser',
- get_string('reportdisplayoptions', 'quiz'));
-
- $this->standard_preference_fields($mform);
- $this->other_preference_fields($mform);
-
- $mform->addElement('submit', 'submitbutton',
- get_string('showreport', 'quiz'));
- }
-
- protected function standard_attempt_fields(MoodleQuickForm $mform) {
-
- $mform->addElement('select', 'attempts', get_string('reportattemptsfrom', 'quiz'), array(
- quiz_attempts_report::ENROLLED_WITH => get_string('reportuserswith', 'quiz'),
- quiz_attempts_report::ENROLLED_WITHOUT => get_string('reportuserswithout', 'quiz'),
- quiz_attempts_report::ENROLLED_ALL => get_string('reportuserswithorwithout', 'quiz'),
- quiz_attempts_report::ALL_WITH => get_string('reportusersall', 'quiz'),
- ));
-
- $stategroup = array(
- $mform->createElement('advcheckbox', 'stateinprogress', '',
- get_string('stateinprogress', 'quiz')),
- $mform->createElement('advcheckbox', 'stateoverdue', '',
- get_string('stateoverdue', 'quiz')),
- $mform->createElement('advcheckbox', 'statefinished', '',
- get_string('statefinished', 'quiz')),
- $mform->createElement('advcheckbox', 'stateabandoned', '',
- get_string('stateabandoned', 'quiz')),
- );
- $mform->addGroup($stategroup, 'stateoptions',
- get_string('reportattemptsthatare', 'quiz'), array(' '), false);
- $mform->setDefault('stateinprogress', 1);
- $mform->setDefault('stateoverdue', 1);
- $mform->setDefault('statefinished', 1);
- $mform->setDefault('stateabandoned', 1);
- $mform->disabledIf('stateinprogress', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
- $mform->disabledIf('stateoverdue', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
- $mform->disabledIf('statefinished', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
- $mform->disabledIf('stateabandoned', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
-
- if (quiz_report_can_filter_only_graded($this->_customdata['quiz'])) {
- $gm = html_writer::tag('span',
- quiz_get_grading_option_name($this->_customdata['quiz']->grademethod),
- array('class' => 'highlight'));
- $mform->addElement('advcheckbox', 'onlygraded', '',
- get_string('reportshowonlyfinished', 'quiz', $gm));
- $mform->disabledIf('onlygraded', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
- $mform->disabledIf('onlygraded', 'statefinished', 'notchecked');
- }
- }
-
- protected function other_attempt_fields(MoodleQuickForm $mform) {
- }
-
- protected function standard_preference_fields(MoodleQuickForm $mform) {
- $mform->addElement('text', 'pagesize', get_string('pagesize', 'quiz'));
- $mform->setType('pagesize', PARAM_INT);
- }
-
- protected function other_preference_fields(MoodleQuickForm $mform) {
- }
-
- public function validation($data, $files) {
- $errors = parent::validation($data, $files);
-
- if ($data['attempts'] != quiz_attempts_report::ENROLLED_WITHOUT && !(
- $data['stateinprogress'] || $data['stateoverdue'] || $data['statefinished'] || $data['stateabandoned'])) {
- $errors['stateoptions'] = get_string('reportmustselectstate', 'quiz');
- }
-
- return $errors;
- }
-}
+debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);
diff --git a/mod/quiz/report/attemptsreport_options.php b/mod/quiz/report/attemptsreport_options.php
index 839872fa327..df1c42fe9f1 100644
--- a/mod/quiz/report/attemptsreport_options.php
+++ b/mod/quiz/report/attemptsreport_options.php
@@ -15,277 +15,11 @@
// along with Moodle. If not, see .
/**
- * Base class for the options that control what is visible in an {@link quiz_attempts_report}.
+ * File only retained to prevent fatal errors in code that tries to require/include this.
*
- * @package mod_quiz
- * @copyright 2012 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @todo MDL-76612 delete this file as part of Moodle 4.6 development.
+ * @deprecated This file is no longer required in Moodle 4.2+.
*/
-
-
defined('MOODLE_INTERNAL') || die();
-require_once($CFG->libdir . '/formslib.php');
-
-
-/**
- * Base class for the options that control what is visible in an {@link quiz_attempts_report}.
- *
- * @copyright 2012 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class mod_quiz_attempts_report_options {
-
- /** @var string the report mode. */
- public $mode;
-
- /** @var object the settings for the quiz being reported on. */
- public $quiz;
-
- /** @var object the course module objects for the quiz being reported on. */
- public $cm;
-
- /** @var object the course settings for the course the quiz is in. */
- public $course;
-
- /**
- * @var array form field name => corresponding quiz_attempt:: state constant.
- */
- protected static $statefields = array(
- 'stateinprogress' => quiz_attempt::IN_PROGRESS,
- 'stateoverdue' => quiz_attempt::OVERDUE,
- 'statefinished' => quiz_attempt::FINISHED,
- 'stateabandoned' => quiz_attempt::ABANDONED,
- );
-
- /**
- * @var string quiz_attempts_report::ALL_WITH or quiz_attempts_report::ENROLLED_WITH
- * quiz_attempts_report::ENROLLED_WITHOUT or quiz_attempts_report::ENROLLED_ALL
- */
- public $attempts = quiz_attempts_report::ENROLLED_WITH;
-
- /** @var int the currently selected group. 0 if no group is selected. */
- public $group = 0;
-
- /**
- * @var array|null of quiz_attempt::IN_PROGRESS, etc. constants. null means
- * no restriction.
- */
- public $states = array(quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE,
- quiz_attempt::FINISHED, quiz_attempt::ABANDONED);
-
- /**
- * @var bool whether to show all finished attmepts, or just the one that gave
- * the final grade for the user.
- */
- public $onlygraded = false;
-
- /** @var int Number of attempts to show per page. */
- public $pagesize = quiz_attempts_report::DEFAULT_PAGE_SIZE;
-
- /** @var string whether the data should be downloaded in some format, or '' to display it. */
- public $download = '';
-
- /** @var bool whether the current user has permission to see grades. */
- public $usercanseegrades;
-
- /** @var bool whether the report table should have a column of checkboxes. */
- public $checkboxcolumn = false;
-
- /**
- * Constructor.
- * @param string $mode which report these options are for.
- * @param object $quiz the settings for the quiz being reported on.
- * @param object $cm the course module objects for the quiz being reported on.
- * @param object $coures the course settings for the coures this quiz is in.
- */
- public function __construct($mode, $quiz, $cm, $course) {
- $this->mode = $mode;
- $this->quiz = $quiz;
- $this->cm = $cm;
- $this->course = $course;
-
- $this->usercanseegrades = quiz_report_should_show_grades($quiz, context_module::instance($cm->id));
- }
-
- /**
- * Get the URL parameters required to show the report with these options.
- * @return array URL parameter name => value.
- */
- protected function get_url_params() {
- $params = array(
- 'id' => $this->cm->id,
- 'mode' => $this->mode,
- 'attempts' => $this->attempts,
- 'onlygraded' => $this->onlygraded,
- );
-
- if ($this->states) {
- $params['states'] = implode('-', $this->states);
- }
-
- if (groups_get_activity_groupmode($this->cm, $this->course)) {
- $params['group'] = $this->group;
- }
- return $params;
- }
-
- /**
- * Get the URL to show the report with these options.
- * @return moodle_url the URL.
- */
- public function get_url() {
- return new moodle_url('/mod/quiz/report.php', $this->get_url_params());
- }
-
- /**
- * Process the data we get when the settings form is submitted. This includes
- * updating the fields of this class, and updating the user preferences
- * where appropriate.
- * @param object $fromform The data from $mform->get_data() from the settings form.
- */
- public function process_settings_from_form($fromform) {
- $this->setup_from_form_data($fromform);
- $this->resolve_dependencies();
- $this->update_user_preferences();
- }
-
- /**
- * Set up this preferences object using optional_param (using user_preferences
- * to set anything not specified by the params.
- */
- public function process_settings_from_params() {
- $this->setup_from_user_preferences();
- $this->setup_from_params();
- $this->resolve_dependencies();
- }
-
- /**
- * Get the current value of the settings to pass to the settings form.
- */
- public function get_initial_form_data() {
- $toform = new stdClass();
- $toform->attempts = $this->attempts;
- $toform->onlygraded = $this->onlygraded;
- $toform->pagesize = $this->pagesize;
-
- if ($this->states) {
- foreach (self::$statefields as $field => $state) {
- $toform->$field = in_array($state, $this->states);
- }
- }
-
- return $toform;
- }
-
- /**
- * Set the fields of this object from the form data.
- * @param object $fromform The data from $mform->get_data() from the settings form.
- */
- public function setup_from_form_data($fromform) {
- $this->attempts = $fromform->attempts;
- $this->group = groups_get_activity_group($this->cm, true);
- $this->onlygraded = !empty($fromform->onlygraded);
- $this->pagesize = $fromform->pagesize;
-
- $this->states = array();
- foreach (self::$statefields as $field => $state) {
- if (!empty($fromform->$field)) {
- $this->states[] = $state;
- }
- }
- }
-
- /**
- * Set the fields of this object from the URL parameters.
- */
- public function setup_from_params() {
- $this->attempts = optional_param('attempts', $this->attempts, PARAM_ALPHAEXT);
- $this->group = groups_get_activity_group($this->cm, true);
- $this->onlygraded = optional_param('onlygraded', $this->onlygraded, PARAM_BOOL);
- $this->pagesize = optional_param('pagesize', $this->pagesize, PARAM_INT);
-
- $states = optional_param('states', '', PARAM_ALPHAEXT);
- if (!empty($states)) {
- $this->states = explode('-', $states);
- }
-
- $this->download = optional_param('download', $this->download, PARAM_ALPHA);
- }
-
- /**
- * Set the fields of this object from the user's preferences.
- * (For those settings that are backed by user-preferences).
- */
- public function setup_from_user_preferences() {
- $this->pagesize = get_user_preferences('quiz_report_pagesize', $this->pagesize);
- }
-
- /**
- * Update the user preferences so they match the settings in this object.
- * (For those settings that are backed by user-preferences).
- */
- public function update_user_preferences() {
- set_user_preference('quiz_report_pagesize', $this->pagesize);
- }
-
- /**
- * Check the settings, and remove any 'impossible' combinations.
- */
- public function resolve_dependencies() {
- if ($this->group) {
- // Default for when a group is selected.
- if ($this->attempts === null || $this->attempts == quiz_attempts_report::ALL_WITH) {
- $this->attempts = quiz_attempts_report::ENROLLED_WITH;
- }
-
- } else if (!$this->group && $this->course->id == SITEID) {
- // Force report on front page to show all, unless a group is selected.
- $this->attempts = quiz_attempts_report::ALL_WITH;
-
- } else if (!in_array($this->attempts, array(quiz_attempts_report::ALL_WITH, quiz_attempts_report::ENROLLED_WITH,
- quiz_attempts_report::ENROLLED_WITHOUT, quiz_attempts_report::ENROLLED_ALL))) {
- $this->attempts = quiz_attempts_report::ENROLLED_WITH;
- }
-
- $cleanstates = array();
- foreach (self::$statefields as $state) {
- if (in_array($state, $this->states)) {
- $cleanstates[] = $state;
- }
- }
- $this->states = $cleanstates;
- if (count($this->states) == count(self::$statefields)) {
- // If all states have been selected, then there is no constraint
- // required in the SQL, so clear the array.
- $this->states = null;
- }
-
- if (!quiz_report_can_filter_only_graded($this->quiz)) {
- // A grading mode like 'average' has been selected, so we cannot do
- // the show the attempt that gave the final grade thing.
- $this->onlygraded = false;
- }
-
- if ($this->attempts == quiz_attempts_report::ENROLLED_WITHOUT) {
- $this->states = null;
- $this->onlygraded = false;
- }
-
- if (!$this->is_showing_finished_attempts()) {
- $this->onlygraded = false;
- }
-
- if ($this->pagesize < 1) {
- $this->pagesize = quiz_attempts_report::DEFAULT_PAGE_SIZE;
- }
- }
-
- /**
- * Whether the options are such that finished attempts are being shown.
- * @return boolean
- */
- protected function is_showing_finished_attempts() {
- return $this->states === null || in_array(quiz_attempt::FINISHED, $this->states);
- }
-}
+debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);
diff --git a/mod/quiz/report/attemptsreport_table.php b/mod/quiz/report/attemptsreport_table.php
index 1933f1259eb..df1c42fe9f1 100644
--- a/mod/quiz/report/attemptsreport_table.php
+++ b/mod/quiz/report/attemptsreport_table.php
@@ -15,687 +15,11 @@
// along with Moodle. If not, see .
/**
- * Base class for the table used by a {@link quiz_attempts_report}.
+ * File only retained to prevent fatal errors in code that tries to require/include this.
*
- * @package mod_quiz
- * @copyright 2010 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @todo MDL-76612 delete this file as part of Moodle 4.6 development.
+ * @deprecated This file is no longer required in Moodle 4.2+.
*/
-
-
defined('MOODLE_INTERNAL') || die();
-require_once($CFG->libdir.'/tablelib.php');
-
-
-/**
- * Base class for the table used by a {@link quiz_attempts_report}.
- *
- * @copyright 2010 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class quiz_attempts_report_table extends table_sql {
- public $useridfield = 'userid';
-
- /** @var moodle_url the URL of this report. */
- protected $reporturl;
-
- /** @var array the display options. */
- protected $displayoptions;
-
- /**
- * @var array information about the latest step of each question.
- * Loaded by {@link load_question_latest_steps()}, if applicable.
- */
- protected $lateststeps = null;
-
- /** @var object the quiz settings for the quiz we are reporting on. */
- protected $quiz;
-
- /** @var context the quiz context. */
- protected $context;
-
- /** @var string HTML fragment to select the first/best/last attempt, if appropriate. */
- protected $qmsubselect;
-
- /** @var object mod_quiz_attempts_report_options the options affecting this report. */
- protected $options;
-
- /** @var \core\dml\sql_join Contains joins, wheres, params to find students
- * in the currently selected group, if applicable.
- */
- protected $groupstudentsjoins;
-
- /** @var \core\dml\sql_join Contains joins, wheres, params to find the students in the course. */
- protected $studentsjoins;
-
- /** @var object the questions that comprise this quiz.. */
- protected $questions;
-
- /** @var bool whether to include the column with checkboxes to select each attempt. */
- protected $includecheckboxes;
-
- /** @var string The toggle group name for the checkboxes in the checkbox column. */
- protected $togglegroup = 'quiz-attempts';
-
- /**
- * Constructor
- * @param string $uniqueid
- * @param object $quiz
- * @param context $context
- * @param string $qmsubselect
- * @param mod_quiz_attempts_report_options $options
- * @param \core\dml\sql_join $groupstudentsjoins Contains joins, wheres, params
- * @param \core\dml\sql_join $studentsjoins Contains joins, wheres, params
- * @param array $questions
- * @param moodle_url $reporturl
- */
- public function __construct($uniqueid, $quiz, $context, $qmsubselect,
- mod_quiz_attempts_report_options $options, \core\dml\sql_join $groupstudentsjoins, \core\dml\sql_join $studentsjoins,
- $questions, $reporturl) {
- parent::__construct($uniqueid);
- $this->quiz = $quiz;
- $this->context = $context;
- $this->qmsubselect = $qmsubselect;
- $this->groupstudentsjoins = $groupstudentsjoins;
- $this->studentsjoins = $studentsjoins;
- $this->questions = $questions;
- $this->includecheckboxes = $options->checkboxcolumn;
- $this->reporturl = $reporturl;
- $this->options = $options;
- }
-
- /**
- * Generate the display of the checkbox column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_checkbox($attempt) {
- global $OUTPUT;
-
- if ($attempt->attempt) {
- $checkbox = new \core\output\checkbox_toggleall($this->togglegroup, false, [
- 'id' => "attemptid_{$attempt->attempt}",
- 'name' => 'attemptid[]',
- 'value' => $attempt->attempt,
- 'label' => get_string('selectattempt', 'quiz'),
- 'labelclasses' => 'accesshide',
- ]);
- return $OUTPUT->render($checkbox);
- } else {
- return '';
- }
- }
-
- /**
- * Generate the display of the user's picture column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_picture($attempt) {
- global $OUTPUT;
- $user = new stdClass();
- $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
- $user = username_load_fields_from_object($user, $attempt, null, $additionalfields);
- $user->id = $attempt->userid;
- return $OUTPUT->user_picture($user);
- }
-
- /**
- * Generate the display of the user's full name column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_fullname($attempt) {
- $html = parent::col_fullname($attempt);
- if ($this->is_downloading() || empty($attempt->attempt)) {
- return $html;
- }
-
- return $html . html_writer::empty_tag('br') . html_writer::link(
- new moodle_url('/mod/quiz/review.php', array('attempt' => $attempt->attempt)),
- get_string('reviewattempt', 'quiz'), array('class' => 'reviewlink'));
- }
-
- /**
- * Generate the display of the attempt state column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_state($attempt) {
- if (!is_null($attempt->attempt)) {
- return quiz_attempt::state_name($attempt->state);
- } else {
- return '-';
- }
- }
-
- /**
- * Generate the display of the start time column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_timestart($attempt) {
- if ($attempt->attempt) {
- return userdate($attempt->timestart, $this->strtimeformat);
- } else {
- return '-';
- }
- }
-
- /**
- * Generate the display of the finish time column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_timefinish($attempt) {
- if ($attempt->attempt && $attempt->timefinish) {
- return userdate($attempt->timefinish, $this->strtimeformat);
- } else {
- return '-';
- }
- }
-
- /**
- * Generate the display of the time taken column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_duration($attempt) {
- if ($attempt->timefinish) {
- return format_time($attempt->timefinish - $attempt->timestart);
- } else {
- return '-';
- }
- }
-
- /**
- * Generate the display of the feedback column.
- * @param object $attempt the table row being output.
- * @return string HTML content to go inside the td.
- */
- public function col_feedbacktext($attempt) {
- if ($attempt->state != quiz_attempt::FINISHED) {
- return '-';
- }
-
- $feedback = quiz_report_feedback_for_grade(
- quiz_rescale_grade($attempt->sumgrades, $this->quiz, false),
- $this->quiz->id, $this->context);
-
- if ($this->is_downloading()) {
- $feedback = strip_tags($feedback);
- }
-
- return $feedback;
- }
-
- public function get_row_class($attempt) {
- if ($this->qmsubselect && $attempt->gradedattempt) {
- return 'gradedattempt';
- } else {
- return '';
- }
- }
-
- /**
- * Make a link to review an individual question in a popup window.
- *
- * @param string $data HTML fragment. The text to make into the link.
- * @param object $attempt data for the row of the table being output.
- * @param int $slot the number used to identify this question within this usage.
- */
- public function make_review_link($data, $attempt, $slot) {
- global $OUTPUT, $CFG;
-
- $flag = '';
- if ($this->is_flagged($attempt->usageid, $slot)) {
- $flag = $OUTPUT->pix_icon('i/flagged', get_string('flagged', 'question'),
- 'moodle', array('class' => 'questionflag'));
- }
-
- $feedbackimg = '';
- $state = $this->slot_state($attempt, $slot);
- if ($state && $state->is_finished() && $state != question_state::$needsgrading) {
- $feedbackimg = $this->icon_for_fraction($this->slot_fraction($attempt, $slot));
- }
-
- $output = html_writer::tag('span', $feedbackimg . html_writer::tag('span',
- $data, array('class' => $state->get_state_class(true))) . $flag, array('class' => 'que'));
-
- $reviewparams = array('attempt' => $attempt->attempt, 'slot' => $slot);
- if (isset($attempt->try)) {
- $reviewparams['step'] = $this->step_no_for_try($attempt->usageid, $slot, $attempt->try);
- }
- $url = new moodle_url('/mod/quiz/reviewquestion.php', $reviewparams);
- $output = $OUTPUT->action_link($url, $output,
- new popup_action('click', $url, 'reviewquestion',
- array('height' => 450, 'width' => 650)),
- array('title' => get_string('reviewresponse', 'quiz')));
-
- if (!empty($CFG->enableplagiarism)) {
- require_once($CFG->libdir . '/plagiarismlib.php');
- $output .= plagiarism_get_links([
- 'context' => $this->context->id,
- 'component' => 'qtype_'.$this->questions[$slot]->qtype,
- 'cmid' => $this->context->instanceid,
- 'area' => $attempt->usageid,
- 'itemid' => $slot,
- 'userid' => $attempt->userid]);
- }
- return $output;
- }
-
- /**
- * @param object $attempt the row data
- * @param int $slot
- * @return question_state
- */
- protected function slot_state($attempt, $slot) {
- $stepdata = $this->lateststeps[$attempt->usageid][$slot];
- return question_state::get($stepdata->state);
- }
-
- /**
- * @param int $questionusageid
- * @param int $slot
- * @return bool
- */
- protected function is_flagged($questionusageid, $slot) {
- $stepdata = $this->lateststeps[$questionusageid][$slot];
- return $stepdata->flagged;
- }
-
-
- /**
- * @param object $attempt the row data
- * @param int $slot
- * @return float
- */
- protected function slot_fraction($attempt, $slot) {
- $stepdata = $this->lateststeps[$attempt->usageid][$slot];
- return $stepdata->fraction;
- }
-
- /**
- * Return an appropriate icon (green tick, red cross, etc.) for a grade.
- * @param float $fraction grade on a scale 0..1.
- * @return string html fragment.
- */
- protected function icon_for_fraction($fraction) {
- global $OUTPUT;
-
- $feedbackclass = question_state::graded_state_for_fraction($fraction)->get_feedback_class();
- return $OUTPUT->pix_icon('i/grade_' . $feedbackclass, get_string($feedbackclass, 'question'),
- 'moodle', array('class' => 'icon'));
- }
-
- /**
- * Load any extra data after main query. At this point you can call {@link get_qubaids_condition} to get the condition that
- * limits the query to just the question usages shown in this report page or alternatively for all attempts if downloading a
- * full report.
- */
- protected function load_extra_data() {
- $this->lateststeps = $this->load_question_latest_steps();
- }
-
- /**
- * Load information about the latest state of selected questions in selected attempts.
- *
- * The results are returned as an two dimensional array $qubaid => $slot => $dataobject
- *
- * @param qubaid_condition|null $qubaids used to restrict which usages are included
- * in the query. See {@link qubaid_condition}.
- * @return array of records. See the SQL in this function to see the fields available.
- */
- protected function load_question_latest_steps(qubaid_condition $qubaids = null) {
- if ($qubaids === null) {
- $qubaids = $this->get_qubaids_condition();
- }
- $dm = new question_engine_data_mapper();
- $latesstepdata = $dm->load_questions_usages_latest_steps(
- $qubaids, array_keys($this->questions));
-
- $lateststeps = array();
- foreach ($latesstepdata as $step) {
- $lateststeps[$step->questionusageid][$step->slot] = $step;
- }
-
- return $lateststeps;
- }
-
- /**
- * Does this report require loading any more data after the main query. After the main query then
- * you can use $this->get
- *
- * @return bool should {@link query_db()} call {@link load_extra_data}?
- */
- protected function requires_extra_data() {
- return $this->requires_latest_steps_loaded();
- }
-
- /**
- * Does this report require the detailed information for each question from the
- * question_attempts_steps table?
- * @return bool should {@link load_extra_data} call {@link load_question_latest_steps}?
- */
- protected function requires_latest_steps_loaded() {
- return false;
- }
-
- /**
- * Is this a column that depends on joining to the latest state information?
- * If so, return the corresponding slot. If not, return false.
- * @param string $column a column name
- * @return int false if no, else a slot.
- */
- protected function is_latest_step_column($column) {
- return false;
- }
-
- /**
- * Get any fields that might be needed when sorting on date for a particular slot.
- *
- * Note: these values are only used for sorting. The values displayed are taken
- * from $this->lateststeps loaded in load_extra_data().
- *
- * @param int $slot the slot for the column we want.
- * @param string $alias the table alias for latest state information relating to that slot.
- * @return string definitions of extra fields to add to the SELECT list of the query.
- */
- protected function get_required_latest_state_fields($slot, $alias) {
- return '';
- }
-
- /**
- * Contruct all the parts of the main database query.
- * @param \core\dml\sql_join $allowedstudentsjoins (joins, wheres, params) defines allowed users for the report.
- * @return array with 4 elements ($fields, $from, $where, $params) that can be used to
- * build the actual database query.
- */
- public function base_sql(\core\dml\sql_join $allowedstudentsjoins) {
- global $DB;
-
- // Please note this uniqueid column is not the same as quiza.uniqueid.
- $fields = 'DISTINCT ' . $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') . ' AS uniqueid,';
-
- if ($this->qmsubselect) {
- $fields .= "\n(CASE WHEN $this->qmsubselect THEN 1 ELSE 0 END) AS gradedattempt,";
- }
-
- $userfieldsapi = \core_user\fields::for_identity($this->context)->with_name()
- ->excluding('id', 'idnumber', 'picture', 'imagealt', 'institution', 'department', 'email');
- $userfields = $userfieldsapi->get_sql('u', true, '', '', false);
-
- $fields .= '
- quiza.uniqueid AS usageid,
- quiza.id AS attempt,
- u.id AS userid,
- u.idnumber,
- u.picture,
- u.imagealt,
- u.institution,
- u.department,
- u.email,' . $userfields->selects . ',
- quiza.state,
- quiza.sumgrades,
- quiza.timefinish,
- quiza.timestart,
- CASE WHEN quiza.timefinish = 0 THEN null
- WHEN quiza.timefinish > quiza.timestart THEN quiza.timefinish - quiza.timestart
- ELSE 0 END AS duration';
- // To explain that last bit, timefinish can be non-zero and less
- // than timestart when you have two load-balanced servers with very
- // badly synchronised clocks, and a student does a really quick attempt.
-
- // This part is the same for all cases. Join the users and quiz_attempts tables.
- $from = " {user} u";
- $from .= "\n{$userfields->joins}";
- $from .= "\nLEFT JOIN {quiz_attempts} quiza ON
- quiza.userid = u.id AND quiza.quiz = :quizid";
- $params = array_merge($userfields->params, ['quizid' => $this->quiz->id]);
-
- if ($this->qmsubselect && $this->options->onlygraded) {
- $from .= " AND (quiza.state <> :finishedstate OR $this->qmsubselect)";
- $params['finishedstate'] = quiz_attempt::FINISHED;
- }
-
- switch ($this->options->attempts) {
- case quiz_attempts_report::ALL_WITH:
- // Show all attempts, including students who are no longer in the course.
- $where = 'quiza.id IS NOT NULL AND quiza.preview = 0';
- break;
- case quiz_attempts_report::ENROLLED_WITH:
- // Show only students with attempts.
- $from .= "\n" . $allowedstudentsjoins->joins;
- $where = "quiza.preview = 0 AND quiza.id IS NOT NULL AND " . $allowedstudentsjoins->wheres;
- $params = array_merge($params, $allowedstudentsjoins->params);
- break;
- case quiz_attempts_report::ENROLLED_WITHOUT:
- // Show only students without attempts.
- $from .= "\n" . $allowedstudentsjoins->joins;
- $where = "quiza.id IS NULL AND " . $allowedstudentsjoins->wheres;
- $params = array_merge($params, $allowedstudentsjoins->params);
- break;
- case quiz_attempts_report::ENROLLED_ALL:
- // Show all students with or without attempts.
- $from .= "\n" . $allowedstudentsjoins->joins;
- $where = "(quiza.preview = 0 OR quiza.preview IS NULL) AND " . $allowedstudentsjoins->wheres;
- $params = array_merge($params, $allowedstudentsjoins->params);
- break;
- }
-
- if ($this->options->states) {
- list($statesql, $stateparams) = $DB->get_in_or_equal($this->options->states,
- SQL_PARAMS_NAMED, 'state');
- $params += $stateparams;
- $where .= " AND (quiza.state $statesql OR quiza.state IS NULL)";
- }
-
- return array($fields, $from, $where, $params);
- }
-
- /**
- * A chance for subclasses to modify the SQL after the count query has been generated,
- * and before the full query is constructed.
- * @param string $fields SELECT list.
- * @param string $from JOINs part of the SQL.
- * @param string $where WHERE clauses.
- * @param array $params Query params.
- * @return array with 4 elements ($fields, $from, $where, $params) as from base_sql.
- */
- protected function update_sql_after_count($fields, $from, $where, $params) {
- return [$fields, $from, $where, $params];
- }
-
- /**
- * Set up the SQL queries (count rows, and get data).
- *
- * @param \core\dml\sql_join $allowedjoins (joins, wheres, params) defines allowed users for the report.
- */
- public function setup_sql_queries($allowedjoins) {
- list($fields, $from, $where, $params) = $this->base_sql($allowedjoins);
-
- // The WHERE clause is vital here, because some parts of tablelib.php will expect to
- // add bits like ' AND x = 1' on the end, and that needs to leave to valid SQL.
- $this->set_count_sql("SELECT COUNT(1) FROM (SELECT $fields FROM $from WHERE $where) temp WHERE 1 = 1", $params);
-
- list($fields, $from, $where, $params) = $this->update_sql_after_count($fields, $from, $where, $params);
- $this->set_sql($fields, $from, $where, $params);
- }
-
- /**
- * Add the information about the latest state of the question with slot
- * $slot to the query.
- *
- * The extra information is added as a join to a
- * 'table' with alias qa$slot, with columns that are a union of
- * the columns of the question_attempts and question_attempts_states tables.
- *
- * @param int $slot the question to add information for.
- */
- protected function add_latest_state_join($slot) {
- $alias = 'qa' . $slot;
-
- $fields = $this->get_required_latest_state_fields($slot, $alias);
- if (!$fields) {
- return;
- }
-
- // This condition roughly filters the list of attempts to be considered.
- // It is only used in a subselect to help crappy databases (see MDL-30122)
- // therefore, it is better to use a very simple join, which may include
- // too many records, than to do a super-accurate join.
- $qubaids = new qubaid_join("{quiz_attempts} {$alias}quiza", "{$alias}quiza.uniqueid",
- "{$alias}quiza.quiz = :{$alias}quizid", array("{$alias}quizid" => $this->sql->params['quizid']));
-
- $dm = new question_engine_data_mapper();
- list($inlineview, $viewparams) = $dm->question_attempt_latest_state_view($alias, $qubaids);
-
- $this->sql->fields .= ",\n$fields";
- $this->sql->from .= "\nLEFT JOIN $inlineview ON " .
- "$alias.questionusageid = quiza.uniqueid AND $alias.slot = :{$alias}slot";
- $this->sql->params[$alias . 'slot'] = $slot;
- $this->sql->params = array_merge($this->sql->params, $viewparams);
- }
-
- /**
- * Get an appropriate qubaid_condition for loading more data about the
- * attempts we are displaying.
- * @return qubaid_condition
- */
- protected function get_qubaids_condition() {
- if (is_null($this->rawdata)) {
- throw new coding_exception(
- 'Cannot call get_qubaids_condition until the main data has been loaded.');
- }
-
- if ($this->is_downloading()) {
- // We want usages for all attempts.
- return new qubaid_join("(
- SELECT DISTINCT quiza.uniqueid
- FROM " . $this->sql->from . "
- WHERE " . $this->sql->where . "
- ) quizasubquery", 'quizasubquery.uniqueid',
- "1 = 1", $this->sql->params);
- }
-
- $qubaids = array();
- foreach ($this->rawdata as $attempt) {
- if ($attempt->usageid > 0) {
- $qubaids[] = $attempt->usageid;
- }
- }
-
- return new qubaid_list($qubaids);
- }
-
- public function query_db($pagesize, $useinitialsbar = true) {
- $doneslots = array();
- foreach ($this->get_sort_columns() as $column => $notused) {
- $slot = $this->is_latest_step_column($column);
- if ($slot && !in_array($slot, $doneslots)) {
- $this->add_latest_state_join($slot);
- $doneslots[] = $slot;
- }
- }
-
- parent::query_db($pagesize, $useinitialsbar);
-
- if ($this->requires_extra_data()) {
- $this->load_extra_data();
- }
- }
-
- public function get_sort_columns() {
- // Add attemptid as a final tie-break to the sort. This ensures that
- // Attempts by the same student appear in order when just sorting by name.
- $sortcolumns = parent::get_sort_columns();
- $sortcolumns['quiza.id'] = SORT_ASC;
- return $sortcolumns;
- }
-
- public function wrap_html_start() {
- if ($this->is_downloading() || !$this->includecheckboxes) {
- return;
- }
-
- $url = $this->options->get_url();
- $url->param('sesskey', sesskey());
-
- echo '';
- }
-
- /**
- * Output any submit buttons required by the $this->includecheckboxes form.
- */
- protected function submit_buttons() {
- global $PAGE;
- if (has_capability('mod/quiz:deleteattempts', $this->context)) {
- $deletebuttonparams = [
- 'type' => 'submit',
- 'class' => 'btn btn-secondary mr-1',
- 'id' => 'deleteattemptsbutton',
- 'name' => 'delete',
- 'value' => get_string('deleteselected', 'quiz_overview'),
- 'data-action' => 'toggle',
- 'data-togglegroup' => $this->togglegroup,
- 'data-toggle' => 'action',
- 'disabled' => true
- ];
- echo html_writer::empty_tag('input', $deletebuttonparams);
- $PAGE->requires->event_handler('#deleteattemptsbutton', 'click', 'M.util.show_confirm_dialog',
- array('message' => get_string('deleteattemptcheck', 'quiz')));
- }
- }
-
- /**
- * Generates the contents for the checkbox column header.
- *
- * It returns the HTML for a master \core\output\checkbox_toggleall component that selects/deselects all quiz attempts.
- *
- * @param string $columnname The name of the checkbox column.
- * @return string
- */
- public function checkbox_col_header(string $columnname) {
- global $OUTPUT;
-
- // Make sure to disable sorting on this column.
- $this->no_sorting($columnname);
-
- // Build the select/deselect all control.
- $selectallid = $this->uniqueid . '-selectall-attempts';
- $selectalltext = get_string('selectall', 'quiz');
- $deselectalltext = get_string('selectnone', 'quiz');
- $mastercheckbox = new \core\output\checkbox_toggleall($this->togglegroup, true, [
- 'id' => $selectallid,
- 'name' => $selectallid,
- 'value' => 1,
- 'label' => $selectalltext,
- 'labelclasses' => 'accesshide',
- 'selectall' => $selectalltext,
- 'deselectall' => $deselectalltext,
- ]);
-
- return $OUTPUT->render($mastercheckbox);
- }
-}
+debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);
diff --git a/mod/quiz/report/default.php b/mod/quiz/report/default.php
index 03725fbd31f..df1c42fe9f1 100644
--- a/mod/quiz/report/default.php
+++ b/mod/quiz/report/default.php
@@ -15,85 +15,11 @@
// along with Moodle. If not, see .
/**
- * Base class for quiz report plugins.
+ * File only retained to prevent fatal errors in code that tries to require/include this.
*
- * @package mod_quiz
- * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @todo MDL-76612 delete this file as part of Moodle 4.6 development.
+ * @deprecated This file is no longer required in Moodle 4.2+.
*/
-
-
defined('MOODLE_INTERNAL') || die();
-
-/**
- * Base class for quiz report plugins.
- *
- * Doesn't do anything on it's own -- it needs to be extended.
- * This class displays quiz reports. Because it is called from
- * within /mod/quiz/report.php you can assume that the page header
- * and footer are taken care of.
- *
- * This file can refer to itself as report.php to pass variables
- * to itself - all these will also be globally available. You must
- * pass "id=$cm->id" or q=$quiz->id", and "mode=reportname".
- *
- * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class quiz_default_report {
- const NO_GROUPS_ALLOWED = -2;
-
- /**
- * Override this function to displays the report.
- * @param $cm the course-module for this quiz.
- * @param $course the coures we are in.
- * @param $quiz this quiz.
- */
- public abstract function display($cm, $course, $quiz);
-
- /**
- * Initialise some parts of $PAGE and start output.
- *
- * @param object $cm the course_module information.
- * @param object $coures the course settings.
- * @param object $quiz the quiz settings.
- * @param string $reportmode the report name.
- */
- public function print_header_and_tabs($cm, $course, $quiz, $reportmode = 'overview') {
- global $PAGE, $OUTPUT, $CFG;
-
- // Print the page header.
- $PAGE->set_title($quiz->name);
- $PAGE->set_heading($course->fullname);
- echo $OUTPUT->header();
- $context = context_module::instance($cm->id);
- if (!$PAGE->has_secondary_navigation()) {
- echo $OUTPUT->heading(format_string($quiz->name, true, array('context' => $context)));
- }
- if (!empty($CFG->enableplagiarism)) {
- require_once($CFG->libdir . '/plagiarismlib.php');
- echo plagiarism_update_status($course, $cm);
- }
- }
-
- /**
- * Get the current group for the user user looking at the report.
- *
- * @param object $cm the course_module information.
- * @param object $coures the course settings.
- * @param context $context the quiz context.
- * @return int the current group id, if applicable. 0 for all users,
- * NO_GROUPS_ALLOWED if the user cannot see any group.
- */
- public function get_current_group($cm, $course, $context) {
- $groupmode = groups_get_activity_groupmode($cm, $course);
- $currentgroup = groups_get_activity_group($cm, true);
-
- if ($groupmode == SEPARATEGROUPS && !$currentgroup && !has_capability('moodle/site:accessallgroups', $context)) {
- $currentgroup = self::NO_GROUPS_ALLOWED;
- }
-
- return $currentgroup;
- }
-}
+debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER);
diff --git a/mod/quiz/report/grading/report.php b/mod/quiz/report/grading/report.php
index 7f90ed9f562..c43532be94d 100644
--- a/mod/quiz/report/grading/report.php
+++ b/mod/quiz/report/grading/report.php
@@ -14,20 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * This file defines the quiz manual grading report class.
- *
- * @package quiz_grading
- * @copyright 2006 Gustav Delius
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
+use mod_quiz\local\reports\report_base;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/report/grading/gradingsettings_form.php');
-
/**
* Quiz report to help teachers manually grade questions that need it.
*
@@ -38,7 +30,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/grading/gradingsettings_form.php'
* @copyright 2006 Gustav Delius
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_grading_report extends quiz_default_report {
+class quiz_grading_report extends report_base {
const DEFAULT_PAGE_SIZE = 5;
const DEFAULT_ORDER = 'random';
diff --git a/mod/quiz/report/overview/overview_form.php b/mod/quiz/report/overview/overview_form.php
index c3aed7426cb..29ad8b4d3a3 100644
--- a/mod/quiz/report/overview/overview_form.php
+++ b/mod/quiz/report/overview/overview_form.php
@@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * This file defines the setting form for the quiz overview report.
- *
- * @package quiz_overview
- * @copyright 2008 Jamie Pratt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_form.php');
-
+use mod_quiz\local\reports\attempts_report;
+use mod_quiz\local\reports\attempts_report_options_form;
/**
* Quiz overview report settings form.
@@ -34,13 +23,13 @@ require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_form.php');
* @copyright 2008 Jamie Pratt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_overview_settings_form extends mod_quiz_attempts_report_form {
+class quiz_overview_settings_form extends attempts_report_options_form {
protected function other_attempt_fields(MoodleQuickForm $mform) {
if (has_capability('mod/quiz:regrade', $this->_customdata['context'])) {
$mform->addElement('advcheckbox', 'onlyregraded', get_string('reportshowonly', 'quiz'),
get_string('optonlyregradedattempts', 'quiz_overview'));
- $mform->disabledIf('onlyregraded', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('onlyregraded', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
}
}
diff --git a/mod/quiz/report/overview/overview_options.php b/mod/quiz/report/overview/overview_options.php
index 57c995a58c0..8a3a16bd650 100644
--- a/mod/quiz/report/overview/overview_options.php
+++ b/mod/quiz/report/overview/overview_options.php
@@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * Class to store the options for a {@link quiz_overview_report}.
- *
- * @package quiz_overview
- * @copyright 2012 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_options.php');
-
+use mod_quiz\local\reports\attempts_report;
+use mod_quiz\local\reports\attempts_report_options;
/**
* Class to store the options for a {@link quiz_overview_report}.
@@ -34,7 +23,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_options.php');
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_overview_options extends mod_quiz_attempts_report_options {
+class quiz_overview_options extends attempts_report_options {
/** @var bool whether to show only attempt that need regrading. */
public $onlyregraded = false;
@@ -88,7 +77,7 @@ class quiz_overview_options extends mod_quiz_attempts_report_options {
public function resolve_dependencies() {
parent::resolve_dependencies();
- if ($this->attempts == quiz_attempts_report::ENROLLED_WITHOUT) {
+ if ($this->attempts == attempts_report::ENROLLED_WITHOUT) {
$this->onlyregraded = false;
}
@@ -100,6 +89,6 @@ class quiz_overview_options extends mod_quiz_attempts_report_options {
// if the user has permissions and if the report mode is showing attempts.
$this->checkboxcolumn = has_any_capability(
array('mod/quiz:regrade', 'mod/quiz:deleteattempts'), context_module::instance($this->cm->id))
- && ($this->attempts != quiz_attempts_report::ENROLLED_WITHOUT);
+ && ($this->attempts != attempts_report::ENROLLED_WITHOUT);
}
}
diff --git a/mod/quiz/report/overview/overview_table.php b/mod/quiz/report/overview/overview_table.php
index e8088f79514..681ee0f4ce4 100644
--- a/mod/quiz/report/overview/overview_table.php
+++ b/mod/quiz/report/overview/overview_table.php
@@ -14,19 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * This file defines the quiz grades table.
- *
- * @package quiz_overview
- * @copyright 2008 Jamie Pratt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_table.php');
-
+use mod_quiz\local\reports\attempts_report_table;
/**
* This is a table subclass for displaying the quiz grades report.
@@ -34,7 +22,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_table.php');
* @copyright 2008 Jamie Pratt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_overview_table extends quiz_attempts_report_table {
+class quiz_overview_table extends attempts_report_table {
protected $regradedqs = array();
diff --git a/mod/quiz/report/overview/report.php b/mod/quiz/report/overview/report.php
index 121c9c2e702..4469aa77c4b 100644
--- a/mod/quiz/report/overview/report.php
+++ b/mod/quiz/report/overview/report.php
@@ -22,11 +22,11 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+use mod_quiz\local\reports\attempts_report;
use mod_quiz\question\bank\qbank_helper;
defined('MOODLE_INTERNAL') || die();
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_options.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_form.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_table.php');
@@ -38,7 +38,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_table.php');
* @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_overview_report extends quiz_attempts_report {
+class quiz_overview_report extends attempts_report {
/**
* @var bool whether there are actually students to show, given the options.
diff --git a/mod/quiz/report/overview/tests/helpers.php b/mod/quiz/report/overview/tests/helpers.php
index 50a36507936..3400f44df17 100644
--- a/mod/quiz/report/overview/tests/helpers.php
+++ b/mod/quiz/report/overview/tests/helpers.php
@@ -14,26 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+use mod_quiz\local\reports\attempts_report;
+
/**
- * Makes some protected methods of quiz_attempts_report public to facilitate testing.
+ * Makes some protected methods of attempts_report public to facilitate testing.
*
* @package quiz_overview
* @copyright 2020 Huong Nguyen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport.php');
-
/**
- * Makes some protected methods of quiz_attempts_report public to facilitate testing.
+ * Makes some protected methods of attempts_report public to facilitate testing.
*
* @copyright 2020 Huong Nguyen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class testable_quiz_attempts_report extends quiz_attempts_report {
+class testable_quiz_attempts_report extends attempts_report {
/**
* Override this function to displays the report.
diff --git a/mod/quiz/report/overview/tests/report_test.php b/mod/quiz/report/overview/tests/report_test.php
index f82d457f069..309a78d430a 100644
--- a/mod/quiz/report/overview/tests/report_test.php
+++ b/mod/quiz/report/overview/tests/report_test.php
@@ -21,7 +21,7 @@ use mod_quiz\external\submit_question_version;
use question_engine;
use quiz;
use quiz_attempt;
-use quiz_attempts_report;
+use mod_quiz\local\reports\attempts_report;
use quiz_overview_options;
use quiz_overview_report;
use quiz_overview_table;
@@ -31,7 +31,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
-require_once($CFG->dirroot . '/mod/quiz/report/default.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/report.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_form.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/tests/helpers.php');
@@ -176,7 +175,7 @@ class report_test extends \advanced_testcase {
// Set the options.
$reportoptions = new quiz_overview_options('overview', $quiz, $cm, null);
- $reportoptions->attempts = quiz_attempts_report::ENROLLED_ALL;
+ $reportoptions->attempts = attempts_report::ENROLLED_ALL;
$reportoptions->onlygraded = true;
$reportoptions->states = array(quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE, quiz_attempt::FINISHED);
diff --git a/mod/quiz/report/responses/last_responses_table.php b/mod/quiz/report/responses/last_responses_table.php
index b040279bf89..92eb5ba828e 100644
--- a/mod/quiz/report/responses/last_responses_table.php
+++ b/mod/quiz/report/responses/last_responses_table.php
@@ -14,18 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * This file defines the quiz responses table for showing last try at question.
- *
- * @package quiz_responses
- * @copyright 2008 Jean-Michel Vedrine
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_table.php');
+use mod_quiz\local\reports\attempts_report_table;
/**
@@ -34,7 +23,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_table.php');
* @copyright 2008 Jean-Michel Vedrine
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_last_responses_table extends quiz_attempts_report_table {
+class quiz_last_responses_table extends attempts_report_table {
/**
* Constructor
diff --git a/mod/quiz/report/responses/report.php b/mod/quiz/report/responses/report.php
index 89f1d6e8974..6662e4d4328 100644
--- a/mod/quiz/report/responses/report.php
+++ b/mod/quiz/report/responses/report.php
@@ -22,10 +22,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+use mod_quiz\local\reports\attempts_report;
defined('MOODLE_INTERNAL') || die();
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport.php');
require_once($CFG->dirroot . '/mod/quiz/report/responses/responses_options.php');
require_once($CFG->dirroot . '/mod/quiz/report/responses/responses_form.php');
require_once($CFG->dirroot . '/mod/quiz/report/responses/last_responses_table.php');
@@ -46,7 +46,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/responses/first_or_all_responses_
* @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_responses_report extends quiz_attempts_report {
+class quiz_responses_report extends attempts_report {
public function display($quiz, $cm, $course) {
global $OUTPUT, $DB;
diff --git a/mod/quiz/report/responses/responses_form.php b/mod/quiz/report/responses/responses_form.php
index 4fdbe909a01..ca86d93b608 100644
--- a/mod/quiz/report/responses/responses_form.php
+++ b/mod/quiz/report/responses/responses_form.php
@@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * This file defines the setting form for the quiz responses report.
- *
- * @package quiz_responses
- * @copyright 2008 Jean-Michel Vedrine
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_form.php');
-
+use mod_quiz\local\reports\attempts_report;
+use mod_quiz\local\reports\attempts_report_options_form;
/**
* Quiz responses report settings form.
@@ -34,7 +23,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_form.php');
* @copyright 2008 Jean-Michel Vedrine
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_responses_settings_form extends mod_quiz_attempts_report_form {
+class quiz_responses_settings_form extends attempts_report_options_form {
protected function other_preference_fields(MoodleQuickForm $mform) {
$mform->addGroup(array(
@@ -45,15 +34,15 @@ class quiz_responses_settings_form extends mod_quiz_attempts_report_form {
$mform->createElement('advcheckbox', 'right', '',
get_string('rightanswer', 'quiz_responses')),
), 'coloptions', get_string('showthe', 'quiz_responses'), array(' '), false);
- $mform->disabledIf('qtext', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
- $mform->disabledIf('resp', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
- $mform->disabledIf('right', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('qtext', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('resp', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('right', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
}
public function validation($data, $files) {
$errors = parent::validation($data, $files);
- if ($data['attempts'] != quiz_attempts_report::ENROLLED_WITHOUT && !(
+ if ($data['attempts'] != attempts_report::ENROLLED_WITHOUT && !(
$data['qtext'] || $data['resp'] || $data['right'])) {
$errors['coloptions'] = get_string('reportmustselectstate', 'quiz');
}
@@ -70,7 +59,7 @@ class quiz_responses_settings_form extends mod_quiz_attempts_report_form {
question_attempt::ALL_TRIES => get_string('alltries', 'question'))
);
$mform->setDefault('whichtries', question_attempt::LAST_TRY);
- $mform->disabledIf('whichtries', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
+ $mform->disabledIf('whichtries', 'attempts', 'eq', attempts_report::ENROLLED_WITHOUT);
}
}
}
diff --git a/mod/quiz/report/responses/responses_options.php b/mod/quiz/report/responses/responses_options.php
index 64f1bb1a217..81d972a60a6 100644
--- a/mod/quiz/report/responses/responses_options.php
+++ b/mod/quiz/report/responses/responses_options.php
@@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-/**
- * Class to store the options for a {@link quiz_responses_report}.
- *
- * @package quiz_responses
- * @copyright 2012 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_options.php');
-
+use mod_quiz\local\reports\attempts_report;
+use mod_quiz\local\reports\attempts_report_options;
/**
* Class to store the options for a {@link quiz_responses_report}.
@@ -34,7 +23,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_options.php');
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_responses_options extends mod_quiz_attempts_report_options {
+class quiz_responses_options extends attempts_report_options {
/** @var bool whether to show the question text columns. */
public $showqtext = false;
@@ -126,6 +115,6 @@ class quiz_responses_options extends mod_quiz_attempts_report_options {
// We only want to show the checkbox to delete attempts
// if the user has permissions and if the report mode is showing attempts.
$this->checkboxcolumn = has_capability('mod/quiz:deleteattempts', context_module::instance($this->cm->id))
- && ($this->attempts != quiz_attempts_report::ENROLLED_WITHOUT);
+ && ($this->attempts != attempts_report::ENROLLED_WITHOUT);
}
}
diff --git a/mod/quiz/report/responses/tests/responses_from_steps_walkthrough_test.php b/mod/quiz/report/responses/tests/responses_from_steps_walkthrough_test.php
index 13fa708867d..cb16d9ad759 100644
--- a/mod/quiz/report/responses/tests/responses_from_steps_walkthrough_test.php
+++ b/mod/quiz/report/responses/tests/responses_from_steps_walkthrough_test.php
@@ -23,7 +23,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/tests/attempt_walkthrough_from_csv_test.php');
-require_once($CFG->dirroot . '/mod/quiz/report/default.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
diff --git a/mod/quiz/report/statistics/report.php b/mod/quiz/report/statistics/report.php
index de46c441d5b..96162615bf8 100644
--- a/mod/quiz/report/statistics/report.php
+++ b/mod/quiz/report/statistics/report.php
@@ -25,9 +25,9 @@
defined('MOODLE_INTERNAL') || die();
+use mod_quiz\local\reports\report_base;
use core_question\statistics\questions\all_calculated_for_qubaid_condition;
-require_once($CFG->dirroot . '/mod/quiz/report/default.php');
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_form.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_table.php');
@@ -42,7 +42,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
* @copyright 2008 Jamie Pratt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class quiz_statistics_report extends quiz_default_report {
+class quiz_statistics_report extends report_base {
/** @var context_module context of this quiz.*/
protected $context;
diff --git a/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php b/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
index d3d7c748403..b467bbfe00e 100644
--- a/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
+++ b/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
@@ -25,7 +25,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/tests/attempt_walkthrough_from_csv_test.php');
-require_once($CFG->dirroot . '/mod/quiz/report/default.php');
require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
diff --git a/mod/quiz/report/upgrade.txt b/mod/quiz/report/upgrade.txt
index 951b689781a..eb0017520e5 100644
--- a/mod/quiz/report/upgrade.txt
+++ b/mod/quiz/report/upgrade.txt
@@ -4,11 +4,31 @@ Overview of this plugin type at http://docs.moodle.org/dev/Quiz_reports
=== 4.2 ===
-There was an ill-advised, never documented, API change in quiz_report_get_significant_questions
-in Moodle 4.0. The API has now been reverted to how it was before 4.0. Hopefully this
-will not cause anyone a problem. (The API revert did not require any changes in any automated tests
-or standard quiz reports.)
+* There was an ill-advised, never documented, API change in quiz_report_get_significant_questions
+ in Moodle 4.0. The API has now been reverted to how it was before 4.0. Hopefully this
+ will not cause anyone a problem. (The API revert did not require any changes in any automated tests
+ or standard quiz reports.)
+* The quiz has a lot of old classes in lib.php files. These have now been moved into the classes folder,
+ and so are now in namespaces. Because of Moodle's class renaming support, your code should continue
+ working, but output deprecated warnings, so you probably want to update. This should mostly be
+ doable by adding use statements, and a search/replace.
+ - quiz_default_report => mod_quiz\local\reports\report_base
+ - quiz_attempts_report => mod_quiz\local\reports\attempts_report
+ - mod_quiz_attempts_report_form => mod_quiz\local\reports\attempts_report_options_form
+ - mod_quiz_attempts_report_options => mod_quiz\local\reports\attempts_report_options
+ - quiz_attempts_report_table => mod_quiz\local\reports\attempts_report_table
+
+ As part of the clean-up, the following files are no longer required, and if you try to
+ include them, you will get a debugging notices telling you not to:
+ - mod/quiz/report/attemptsreport.php
+ - mod/quiz/report/attemptsreport_form.php
+ - mod/quiz/report/attemptsreport_options.php
+ - mod/quiz/report/attemptsreport_table.php
+ - mod/quiz/report/default.php
+
+
+>>>>>>> 40c14e4712e... MDL-74923 quiz: move report base classes to local folder
=== 3.9 ===
* Quiz report plugins defining capabilities used to require an extra string like
diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt
index 2bb7be9f5bd..9c27c08735b 100644
--- a/mod/quiz/upgrade.txt
+++ b/mod/quiz/upgrade.txt
@@ -30,6 +30,19 @@ This files describes API changes in the quiz code.
mod_quiz\adminpresets\adminpresets_review_setting
- mod_quiz\adminpresets\adminpresets_mod_quiz_admin_setting_user_image =>
mod_quiz\adminpresets\adminpresets_user_image_setting
+ - quiz_default_report => mod_quiz\local\reports\report_base
+ - quiz_attempts_report => mod_quiz\local\reports\attempts_report
+ - mod_quiz_attempts_report_form => mod_quiz\local\reports\attempts_report_options_form
+ - mod_quiz_attempts_report_options => mod_quiz\local\reports\attempts_report_options
+ - quiz_attempts_report_table => mod_quiz\local\reports\attempts_report_table
+
+ As part of the clean-up, the following files are no longer required, and if you try to
+ include them, you will get a debugging notices telling you not to:
+ - mod/quiz/report/attemptsreport.php
+ - mod/quiz/report/attemptsreport_form.php
+ - mod/quiz/report/attemptsreport_options.php
+ - mod/quiz/report/attemptsreport_table.php
+ - mod/quiz/report/default.php
=== 4.1 ===
diff --git a/question/engine/datalib.php b/question/engine/datalib.php
index 0c32fe44f62..86af4021489 100644
--- a/question/engine/datalib.php
+++ b/question/engine/datalib.php
@@ -1210,7 +1210,7 @@ ORDER BY
/**
* Get a subquery that returns the latest step of every qa in some qubas.
* Currently, this is only used by the quiz reports. See
- * {@link quiz_attempts_report_table::add_latest_state_join()}.
+ * {@see \mod_quiz\local\reports\attempts_report_table::add_latest_state_join()}.
*
* This method may be called publicly.
*