From 8cbc6acb17a67a205e4a64bee7a6776d246ba14f Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Fri, 9 Dec 2022 12:42:57 +0000 Subject: [PATCH] MDL-74923 quiz: move report base classes to local folder --- .../classes/local/reports/attempts_report.php | 383 ++++++++++ .../local/reports/attempts_report_options.php | 285 +++++++ .../reports/attempts_report_options_form.php | 115 +++ .../local/reports/attempts_report_table.php | 707 ++++++++++++++++++ .../classes/local/reports/report_base.php | 94 +++ mod/quiz/db/renamedclasses.php | 5 + mod/quiz/report/attemptsreport.php | 368 +-------- mod/quiz/report/attemptsreport_form.php | 103 +-- mod/quiz/report/attemptsreport_options.php | 274 +------ mod/quiz/report/attemptsreport_table.php | 684 +---------------- mod/quiz/report/default.php | 82 +- mod/quiz/report/grading/report.php | 12 +- mod/quiz/report/overview/overview_form.php | 19 +- mod/quiz/report/overview/overview_options.php | 21 +- mod/quiz/report/overview/overview_table.php | 16 +- mod/quiz/report/overview/report.php | 4 +- mod/quiz/report/overview/tests/helpers.php | 13 +- .../report/overview/tests/report_test.php | 5 +- .../report/responses/last_responses_table.php | 15 +- mod/quiz/report/responses/report.php | 4 +- mod/quiz/report/responses/responses_form.php | 27 +- .../report/responses/responses_options.php | 19 +- .../responses_from_steps_walkthrough_test.php | 1 - mod/quiz/report/statistics/report.php | 4 +- .../stats_from_steps_walkthrough_test.php | 1 - mod/quiz/report/upgrade.txt | 28 +- mod/quiz/upgrade.txt | 13 + question/engine/datalib.php | 2 +- 28 files changed, 1687 insertions(+), 1617 deletions(-) create mode 100644 mod/quiz/classes/local/reports/attempts_report.php create mode 100644 mod/quiz/classes/local/reports/attempts_report_options.php create mode 100644 mod/quiz/classes/local/reports/attempts_report_options_form.php create mode 100644 mod/quiz/classes/local/reports/attempts_report_table.php create mode 100644 mod/quiz/classes/local/reports/report_base.php 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 '
'; + echo '
'; + + echo html_writer::input_hidden_params($url); + echo '
'; + } + + public function wrap_html_finish() { + global $PAGE; + if ($this->is_downloading() || !$this->includecheckboxes) { + return; + } + + echo '
'; + $this->submit_buttons(); + echo '
'; + + // Close the form. + echo '
'; + 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 '
'; - echo '
'; - - echo html_writer::input_hidden_params($url); - echo '
'; - } - - public function wrap_html_finish() { - global $PAGE; - if ($this->is_downloading() || !$this->includecheckboxes) { - return; - } - - echo '
'; - $this->submit_buttons(); - echo '
'; - - // Close the form. - echo '
'; - 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. *