This commit is contained in:
Andrew Nicols 2022-12-15 07:35:46 +08:00
commit ee48f4ab99
63 changed files with 2267 additions and 2049 deletions

View File

@ -309,11 +309,17 @@ class manager {
$classname = null;
// Getting the appropiate class to get the correct setting value.
// Getting the appropriate class to get the correct setting value.
$settingtype = get_class($settingdata);
// Check if it is a setting from a plugin.
$plugindata = explode('_', $settingtype);
$namespacedata = explode('\\', $settingtype);
if (count($namespacedata) > 1) {
$plugindata = explode('_', $namespacedata[0]);
$settingtype = end($namespacedata);
} else {
$plugindata = explode('_', $settingtype, 2);
}
$types = \core_component::get_plugin_types();
if (array_key_exists($plugindata[0], $types)) {
$plugins = \core_component::get_plugin_list($plugindata[0]);

View File

@ -133,8 +133,8 @@ class manager_test extends \advanced_testcase {
$settingpage = $adminroot->locate('modsettingquiz');
$settingdata = $settingpage->settings->quizbrowsersecurity;;
$result = $manager->get_setting($settingdata, '');
$this->assertInstanceOf('\mod_quiz\adminpresets\adminpresets_mod_quiz_admin_setting_browsersecurity', $result);
$this->assertNotEquals('core_adminpresets\local\setting\adminpresets_setting', get_class($result));
$this->assertInstanceOf(\mod_quiz\adminpresets\adminpresets_browser_security_setting::class, $result);
$this->assertNotEquals(\core_adminpresets\local\setting\adminpresets_setting::class, get_class($result));
// Check the adminpresets_setting class is returned when no specific class exists.
$settingpage = $adminroot->locate('managecustomfields');

View File

@ -25,6 +25,7 @@
defined('MOODLE_INTERNAL') || die();
use mod_quiz\question\display_options;
/**
* This class keeps track of the various access rules that apply to a particular
@ -528,7 +529,7 @@ class quiz_access_manager {
}
$when = quiz_attempt_state($this->quizobj->get_quiz(), $attempt);
$reviewoptions = mod_quiz_display_options::make_from_quiz(
$reviewoptions = display_options::make_from_quiz(
$this->quizobj->get_quiz(), $when);
if (!$reviewoptions->attempt) {

View File

@ -28,6 +28,7 @@
defined('MOODLE_INTERNAL') || die();
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\question\display_options;
/**
@ -476,7 +477,7 @@ class quiz {
* If $reviewoptions->attempt is false, meaning that students can't review this
* attempt at the moment, return an appropriate string explaining why.
*
* @param int $when One of the mod_quiz_display_options::DURING,
* @param int $when One of the display_options::DURING,
* IMMEDIATELY_AFTER, LATER_WHILE_OPEN or AFTER_CLOSE constants.
* @param bool $short if true, return a shorter string.
* @return string an appropraite message.
@ -491,11 +492,11 @@ class quiz {
$dateformat = '';
}
if ($when == mod_quiz_display_options::DURING ||
$when == mod_quiz_display_options::IMMEDIATELY_AFTER) {
if ($when == display_options::DURING ||
$when == display_options::IMMEDIATELY_AFTER) {
return '';
} else if ($when == mod_quiz_display_options::LATER_WHILE_OPEN && $this->quiz->timeclose &&
$this->quiz->reviewattempt & mod_quiz_display_options::AFTER_CLOSE) {
} else if ($when == display_options::LATER_WHILE_OPEN && $this->quiz->timeclose &&
$this->quiz->reviewattempt & display_options::AFTER_CLOSE) {
return get_string('noreviewuntil' . $langstrsuffix, 'quiz',
userdate($this->quiz->timeclose, $dateformat));
} else {
@ -631,7 +632,7 @@ class quiz_attempt {
/** @var array slot => page number for this slot. */
protected $questionpages;
/** @var mod_quiz_display_options cache for the appropriate review options. */
/** @var display_options cache for the appropriate review options. */
protected $reviewoptions = null;
// Constructor =============================================================
@ -1080,7 +1081,7 @@ class quiz_attempt {
* If not, prints an error.
*/
public function check_review_capability() {
if ($this->get_attempt_state() == mod_quiz_display_options::IMMEDIATELY_AFTER) {
if ($this->get_attempt_state() == display_options::IMMEDIATELY_AFTER) {
$capability = 'mod/quiz:attempt';
} else {
$capability = 'mod/quiz:reviewmyattempts';
@ -1132,7 +1133,7 @@ class quiz_attempt {
}
/**
* @return int one of the mod_quiz_display_options::DURING,
* @return int one of the display_options::DURING,
* IMMEDIATELY_AFTER, LATER_WHILE_OPEN or AFTER_CLOSE constants.
*/
public function get_attempt_state() {
@ -1140,7 +1141,7 @@ class quiz_attempt {
}
/**
* Wrapper that the correct mod_quiz_display_options for this quiz at the
* Wrapper that the correct display_options for this quiz at the
* moment.
*
* @param bool $reviewing true for options when reviewing, false for when attempting.
@ -1160,15 +1161,15 @@ class quiz_attempt {
return $this->reviewoptions;
} else {
$options = mod_quiz_display_options::make_from_quiz($this->get_quiz(),
mod_quiz_display_options::DURING);
$options = display_options::make_from_quiz($this->get_quiz(),
display_options::DURING);
$options->flags = quiz_get_flag_option($this->attempt, $this->quizobj->get_context());
return $options;
}
}
/**
* Wrapper that the correct mod_quiz_display_options for this quiz at the
* Wrapper that the correct display_options for this quiz at the
* moment.
*
* @param bool $reviewing true for review page, else attempt page.

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use mod_quiz\question\display_options;
/**
* Structure step to restore one quiz activity
*
@ -162,70 +164,70 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
$oldreview = $data->review;
$data->reviewattempt =
mod_quiz_display_options::DURING |
display_options::DURING |
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_RESPONSES ?
mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
display_options::IMMEDIATELY_AFTER : 0) |
($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_RESPONSES ?
mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
display_options::LATER_WHILE_OPEN : 0) |
($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_RESPONSES ?
mod_quiz_display_options::AFTER_CLOSE : 0);
display_options::AFTER_CLOSE : 0);
$data->reviewcorrectness =
mod_quiz_display_options::DURING |
display_options::DURING |
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ?
mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
display_options::IMMEDIATELY_AFTER : 0) |
($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ?
mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
display_options::LATER_WHILE_OPEN : 0) |
($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ?
mod_quiz_display_options::AFTER_CLOSE : 0);
display_options::AFTER_CLOSE : 0);
$data->reviewmarks =
mod_quiz_display_options::DURING |
display_options::DURING |
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ?
mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
display_options::IMMEDIATELY_AFTER : 0) |
($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ?
mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
display_options::LATER_WHILE_OPEN : 0) |
($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ?
mod_quiz_display_options::AFTER_CLOSE : 0);
display_options::AFTER_CLOSE : 0);
$data->reviewspecificfeedback =
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ?
mod_quiz_display_options::DURING : 0) |
display_options::DURING : 0) |
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ?
mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
display_options::IMMEDIATELY_AFTER : 0) |
($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_FEEDBACK ?
mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
display_options::LATER_WHILE_OPEN : 0) |
($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_FEEDBACK ?
mod_quiz_display_options::AFTER_CLOSE : 0);
display_options::AFTER_CLOSE : 0);
$data->reviewgeneralfeedback =
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ?
mod_quiz_display_options::DURING : 0) |
display_options::DURING : 0) |
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ?
mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
display_options::IMMEDIATELY_AFTER : 0) |
($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_GENERALFEEDBACK ?
mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
display_options::LATER_WHILE_OPEN : 0) |
($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_GENERALFEEDBACK ?
mod_quiz_display_options::AFTER_CLOSE : 0);
display_options::AFTER_CLOSE : 0);
$data->reviewrightanswer =
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ?
mod_quiz_display_options::DURING : 0) |
display_options::DURING : 0) |
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ?
mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
display_options::IMMEDIATELY_AFTER : 0) |
($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_ANSWERS ?
mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
display_options::LATER_WHILE_OPEN : 0) |
($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_ANSWERS ?
mod_quiz_display_options::AFTER_CLOSE : 0);
display_options::AFTER_CLOSE : 0);
$data->reviewoverallfeedback =
0 |
($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_OVERALLFEEDBACK ?
mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
display_options::IMMEDIATELY_AFTER : 0) |
($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_OVERALLFEEDBACK ?
mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
display_options::LATER_WHILE_OPEN : 0) |
($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_OVERALLFEEDBACK ?
mod_quiz_display_options::AFTER_CLOSE : 0);
display_options::AFTER_CLOSE : 0);
}
// The old popup column from from <= 2.1 need to be mapped to

View File

@ -14,27 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings class for the quiz browser security option.
*
* @package mod_quiz
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
namespace mod_quiz\admin;
/**
* Admin settings class for the quiz browser security option.
*
* Just so we can lazy-load the choices.
*
* @package mod_quiz
* @category admin
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_admin_setting_browsersecurity extends admin_setting_configselect_with_advanced {
class browser_security_setting extends \admin_setting_configselect_with_advanced {
public function load_choices() {
global $CFG;
@ -43,7 +35,7 @@ class mod_quiz_admin_setting_browsersecurity extends admin_setting_configselect_
}
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$this->choices = quiz_access_manager::get_browser_security_choices();
$this->choices = \quiz_access_manager::get_browser_security_choices();
return true;
}

View File

@ -14,27 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings class for the quiz grading method.
*
* @package mod_quiz
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
namespace mod_quiz\admin;
/**
* Admin settings class for the quiz grading method.
*
* Just so we can lazy-load the choices.
*
* @package mod_quiz
* @category admin
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_admin_setting_grademethod extends admin_setting_configselect_with_advanced {
class grade_method_setting extends \admin_setting_configselect_with_advanced {
public function load_choices() {
global $CFG;

View File

@ -14,27 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings class for the quiz overdue attempt handling method.
*
* @package mod_quiz
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
namespace mod_quiz\admin;
/**
* Admin settings class for the quiz overdue attempt handling method.
*
* Just so we can lazy-load the choices.
*
* @package mod_quiz
* @category admin
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_admin_setting_overduehandling extends admin_setting_configselect_with_advanced {
class overdue_handling_setting extends \admin_setting_configselect_with_advanced {
public function load_choices() {
global $CFG;

View File

@ -14,46 +14,38 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\admin;
/**
* Admin settings class for the quiz review options.
*
* @package mod_quiz
* @category admin
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Admin settings class for the quiz review options.
*
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_admin_review_setting extends admin_setting {
class review_setting extends \admin_setting {
/**
* @var integer should match the constants defined in
* {@link mod_quiz_display_options}. Copied for performance reasons.
* {@see display_options}. Copied for performance reasons.
*/
const DURING = 0x10000;
/**
* @var integer should match the constants defined in
* {@link mod_quiz_display_options}. Copied for performance reasons.
* {@see display_options}. Copied for performance reasons.
*/
const IMMEDIATELY_AFTER = 0x01000;
/**
* @var integer should match the constants defined in
* {@link mod_quiz_display_options}. Copied for performance reasons.
* {@see display_options}. Copied for performance reasons.
*/
const LATER_WHILE_OPEN = 0x00100;
/**
* @var integer should match the constants defined in
* {@link mod_quiz_display_options}. Copied for performance reasons.
* {@see display_options}. Copied for performance reasons.
*/
const AFTER_CLOSE = 0x00010;

View File

@ -14,27 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings class for the choices for how to display the user's image
*
* @package mod_quiz
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
namespace mod_quiz\admin;
/**
* Admin settings class for the choices for how to display the user's image.
*
* Just so we can lazy-load the choices.
*
* @package mod_quiz
* @category admin
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_admin_setting_user_image extends admin_setting_configselect_with_advanced {
class user_image_setting extends \admin_setting_configselect_with_advanced {
public function load_choices() {
global $CFG;

View File

@ -26,7 +26,7 @@ use core_adminpresets\local\setting\adminpresets_admin_setting_configselect_with
* @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David Monllaó <david.monllao@urv.cat> code
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class adminpresets_mod_quiz_admin_setting_browsersecurity extends adminpresets_admin_setting_configselect_with_advanced {
class adminpresets_browser_security_setting extends adminpresets_admin_setting_configselect_with_advanced {
public function set_behaviors() {
$this->behaviors['loadchoices'] = &$this->settingdata;

View File

@ -26,7 +26,7 @@ use core_adminpresets\local\setting\adminpresets_admin_setting_configselect_with
* @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David Monllaó <david.monllao@urv.cat> code
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class adminpresets_mod_quiz_admin_setting_grademethod extends adminpresets_admin_setting_configselect_with_advanced {
class adminpresets_grade_method_setting extends adminpresets_admin_setting_configselect_with_advanced {
public function set_behaviors() {
$this->behaviors['loadchoices'] = &$this->settingdata;

View File

@ -26,7 +26,7 @@ use core_adminpresets\local\setting\adminpresets_admin_setting_configselect_with
* @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David Monllaó <david.monllao@urv.cat> code
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class adminpresets_mod_quiz_admin_setting_overduehandling extends adminpresets_admin_setting_configselect_with_advanced {
class adminpresets_overdue_handling_setting extends adminpresets_admin_setting_configselect_with_advanced {
public function set_behaviors() {
$this->behaviors['loadchoices'] = &$this->settingdata;

View File

@ -27,15 +27,15 @@ use core_adminpresets\local\setting\adminpresets_setting;
* @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David Monllaó <david.monllao@urv.cat> code
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class adminpresets_mod_quiz_admin_review_setting extends adminpresets_setting {
class adminpresets_review_setting extends adminpresets_setting {
/**
* The setting value is a sum of 'mod_quiz_admin_review_setting::times'
* The setting value is a sum of 'review_setting::times'
*/
protected function set_visiblevalue() {
// Getting the masks descriptions (mod_quiz_admin_review_setting protected method).
$reflectiontimes = new ReflectionMethod('mod_quiz_admin_review_setting', 'times');
// Getting the masks descriptions (review_setting protected method).
$reflectiontimes = new ReflectionMethod('mod_quiz\admin\review_setting', 'times');
$reflectiontimes->setAccessible(true);
$times = $reflectiontimes->invoke(null);

View File

@ -26,7 +26,7 @@ use core_adminpresets\local\setting\adminpresets_admin_setting_configselect_with
* @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David Monllaó <david.monllao@urv.cat> code
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class adminpresets_mod_quiz_admin_setting_user_image extends adminpresets_admin_setting_configselect_with_advanced {
class adminpresets_user_image_setting extends adminpresets_admin_setting_configselect_with_advanced {
public function set_behaviors() {
$this->behaviors['loadchoices'] = &$this->settingdata;

View File

@ -194,8 +194,8 @@ class mod_quiz_external extends external_api {
(-1 means use decimalpoints.)', VALUE_OPTIONAL),
'reviewattempt' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz
attempts at various times. This is a bit field, decoded by the
mod_quiz_display_options class. It is formed by ORing together
the constants defined there.', VALUE_OPTIONAL),
\mod_quiz\question\display_options class. It is formed by ORing
together the constants defined there.', VALUE_OPTIONAL),
'reviewcorrectness' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz
attempts at various times.
A bit field, like reviewattempt.', VALUE_OPTIONAL),

View File

@ -0,0 +1,384 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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.
*
* @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 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 {@see 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): array {
$this->mode = $mode;
$this->context = context_module::instance($cm->id);
[$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(),
['quiz' => $quiz, 'currentgroup' => $currentgroup, 'context' => $this->context]);
return [$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',
['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 [$currentgroup, $empty, $empty, $empty];
}
$studentsjoins = get_enrolled_with_capabilities_join($this->context, '',
['mod/quiz:attempt', 'mod/quiz:reviewmyattempts']);
if (empty($currentgroup)) {
return [$currentgroup, $studentsjoins, $empty, $studentsjoins];
}
// We have a currently selected group.
$groupstudentsjoins = get_enrolled_with_capabilities_join($this->context, '',
['mod/quiz:attempt', 'mod/quiz:reviewmyattempts'], $currentgroup);
return [$currentgroup, $studentsjoins, $groupstudentsjoins, $groupstudentsjoins];
}
/**
* Outputs the things you commonly want at the top of a quiz report.
*
* Calls through to {@see 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 '<div class="quizattemptcounts">' . $strattemptnum . '</div>';
}
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', [], 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 + ['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);
}
}

View File

@ -0,0 +1,286 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\local\reports;
use context_module;
use moodle_url;
use quiz_attempt;
use stdClass;
/**
* Base class for the options that control what is visible in an {@see attempts_report}.
*
* @package mod_quiz
* @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 stdClass the settings for the quiz being reported on. */
public $quiz;
/** @var stdClass the course module objects for the quiz being reported on. */
public $cm;
/** @var stdClass 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 = [
'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 = [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 $course 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 = [
'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 = [];
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, [attempts_report::ALL_WITH, attempts_report::ENROLLED_WITH,
attempts_report::ENROLLED_WITHOUT, attempts_report::ENROLLED_ALL])) {
$this->attempts = attempts_report::ENROLLED_WITH;
}
$cleanstates = [];
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);
}
}

View File

@ -0,0 +1,136 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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.
*
* @package mod_quiz
* @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'));
}
/**
* Add the standard form fields for selecting which attempts to include in the report.
*
* @param MoodleQuickForm $mform the form we are building.
*/
protected function standard_attempt_fields(MoodleQuickForm $mform) {
$mform->addElement('select', 'attempts', get_string('reportattemptsfrom', 'quiz'), [
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 = [
$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'), [' '], 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),
['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');
}
}
/**
* Extension point to allow subclasses to add their own fields in the attempts section.
*
* @param MoodleQuickForm $mform the form we are building.
*/
protected function other_attempt_fields(MoodleQuickForm $mform) {
}
/**
* Add the standard options fields to the form.
*
* @param MoodleQuickForm $mform the form we are building.
*/
protected function standard_preference_fields(MoodleQuickForm $mform) {
$mform->addElement('text', 'pagesize', get_string('pagesize', 'quiz'));
$mform->setType('pagesize', PARAM_INT);
}
/**
* Extension point to allow subclasses to add their own fields in the options section.
*
* @param MoodleQuickForm $mform the form we are building.
*/
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;
}
}

View File

@ -0,0 +1,726 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\local\reports;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/tablelib.php');
use coding_exception;
use context_module;
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 {@see load_question_latest_steps()}, if applicable.
*/
protected $lateststeps = null;
/** @var stdClass the quiz settings for the quiz we are reporting on. */
protected $quiz;
/** @var context_module the quiz context. */
protected $context;
/** @var string HTML fragment to select the first/best/last attempt, if appropriate. */
protected $qmsubselect;
/** @var stdClass 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 array 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 stdClass $quiz
* @param context_module $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 stdClass $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 stdClass $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 stdClass $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', ['attempt' => $attempt->attempt]),
get_string('reviewattempt', 'quiz'), ['class' => 'reviewlink']);
}
/**
* Generate the display of the attempt state column.
*
* @param stdClass $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 stdClass $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 stdClass $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 stdClass $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 stdClass $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 stdClass $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', ['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, ['class' => $state->get_state_class(true)]) . $flag, ['class' => 'que']);
$reviewparams = ['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',
['height' => 450, 'width' => 650]),
['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;
}
/**
* Get the question attempt state for a particular question in a particular quiz attempt.
*
* @param stdClass $attempt the row data.
* @param int $slot indicates which question.
* @return question_state the state of that question.
*/
protected function slot_state($attempt, $slot) {
$stepdata = $this->lateststeps[$attempt->usageid][$slot];
return question_state::get($stepdata->state);
}
/**
* Work out if a particular question in a particular attempt has been flagged.
*
* @param int $questionusageid used to identify the attempt of interest.
* @param int $slot identifies which question in the attempt to check.
* @return bool true if the question is flagged in the attempt.
*/
protected function is_flagged($questionusageid, $slot) {
$stepdata = $this->lateststeps[$questionusageid][$slot];
return $stepdata->flagged;
}
/**
* Get the mark (out of 1) for the question in a particular slot.
*
* @param stdClass $attempt the row data
* @param int $slot which slot to check.
* @return float the score for this question on a scale of 0 - 1.
*/
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', ['class' => 'icon']);
}
/**
* Load any extra data after main query.
*
* At this point you can call {@see 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 a two-dimensional array $qubaid => $slot => $dataobject.
*
* @param qubaid_condition|null $qubaids used to restrict which usages are included
* in the query. See {@see 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 = [];
foreach ($latesstepdata as $step) {
$lateststeps[$step->questionusageid][$step->slot] = $step;
}
return $lateststeps;
}
/**
* Does this report require loading any more data after the main query.
*
* @return bool should {@see query_db()} call {@see 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 {@see load_extra_data} call {@see 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 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) {
[$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 [$fields, $from, $where, $params];
}
/**
* Lets subclasses modify the SQL after the count query has been created and before the full query is.
*
* @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) {
[$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);
[$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 sub-select 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", ["{$alias}quizid" => $this->sql->params['quizid']]);
$dm = new question_engine_data_mapper();
[$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 = [];
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 = [];
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 '<div id="tablecontainer">';
echo '<form id="attemptsform" method="post" action="' . $url->out_omit_querystring() . '">';
echo html_writer::input_hidden_params($url);
echo '<div>';
}
public function wrap_html_finish() {
global $PAGE;
if ($this->is_downloading() || !$this->includecheckboxes) {
return;
}
echo '<div id="commands">';
$this->submit_buttons();
echo '</div>';
// Close the form.
echo '</div>';
echo '</form></div>';
}
/**
* 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',
['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);
}
}

View File

@ -0,0 +1,96 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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 {
/** @var int special value used in place of groupid, to mean the use cannot access any groups. */
const NO_GROUPS_ALLOWED = -2;
/**
* Override this function to display the report.
*
* @param stdClass $quiz this quiz.
* @param stdClass $cm the course-module for this quiz.
* @param stdClass $course the coures we are in.
*/
abstract public function display($quiz, $cm, $course);
/**
* 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, ['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;
}
}

View File

@ -0,0 +1,112 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\question;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/lib.php');
/**
* An extension of question_display_options that includes the extra options used by the quiz.
*
* @package mod_quiz
* @category question
* @copyright 2022 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class display_options extends \question_display_options {
/**
* The bitmask patterns use in the review option settings.
*
* In the quiz settings, the review... (e.g. reviewmarks) values are
* bit patterns that allow what is visible to be change at different times.
* These constants define which bit is for which time.
*
* @var int bit used to indicate 'during the attempt'.
*/
const DURING = 0x10000;
/** @var int as above, bit used to indicate 'immediately after the attempt'. */
const IMMEDIATELY_AFTER = 0x01000;
/** @var int as above, bit used to indicate 'later while the quiz is still open'. */
const LATER_WHILE_OPEN = 0x00100;
/** @var int as above, bit used to indicate 'after the quiz is closed'. */
const AFTER_CLOSE = 0x00010;
/**
* @var bool if this is false, then the student is not allowed to review
* anything about the attempt.
*/
public $attempt = true;
/**
* @var int whether the attempt overall feedback is visible.
*/
public $overallfeedback = self::VISIBLE;
/**
* Set up the various options from the quiz settings, and a time constant.
*
* @param \stdClass $quiz the quiz settings from the database.
* @param int $when of the constants {@see DURING}, {@see IMMEDIATELY_AFTER},
* {@see LATER_WHILE_OPEN} or {@see AFTER_CLOSE}.
* @return display_options instance of this class set up appropriately.
*/
public static function make_from_quiz(\stdClass $quiz, int $when): self {
$options = new self();
$options->attempt = self::extract($quiz->reviewattempt, $when, true, false);
$options->correctness = self::extract($quiz->reviewcorrectness, $when);
$options->marks = self::extract($quiz->reviewmarks, $when,
self::MARK_AND_MAX, self::MAX_ONLY);
$options->feedback = self::extract($quiz->reviewspecificfeedback, $when);
$options->generalfeedback = self::extract($quiz->reviewgeneralfeedback, $when);
$options->rightanswer = self::extract($quiz->reviewrightanswer, $when);
$options->overallfeedback = self::extract($quiz->reviewoverallfeedback, $when);
$options->numpartscorrect = $options->feedback;
$options->manualcomment = $options->feedback;
if ($quiz->questiondecimalpoints != -1) {
$options->markdp = $quiz->questiondecimalpoints;
} else {
$options->markdp = $quiz->decimalpoints;
}
return $options;
}
/**
* Helper function to return one value or another depending on whether one bit is set.
*
* @param int $setting the setting to unpack (e.g. $quiz->reviewmarks).
* @param int $when of the constants {@see DURING}, {@see IMMEDIATELY_AFTER},
* {@see LATER_WHILE_OPEN} or {@see AFTER_CLOSE}.
* @param bool|int $whenset value to return when the bit is set.
* @param bool|int $whennotset value to return when the bit is set.
* @return bool|int $whenset or $whennotset, depending.
*/
protected static function extract(int $setting, int $when,
$whenset = self::VISIBLE, $whennotset = self::HIDDEN) {
if ($setting & $when) {
return $whenset;
} else {
return $whennotset;
}
}
}

View File

@ -0,0 +1,55 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\question;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/datalib.php');
/**
* A {@see qubaid_condition} for finding all the question usages belonging to a particular quiz.
*
* @package mod_quiz
* @category question
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qubaids_for_quiz extends \qubaid_join {
/**
* Constructor.
*
* @param int $quizid The quiz to search.
* @param bool $includepreviews Whether to include preview attempts
* @param bool $onlyfinished Whether to only include finished attempts or not
*/
public function __construct(int $quizid, bool $includepreviews = true, bool $onlyfinished = false) {
$where = 'quiza.quiz = :quizaquiz';
$params = array('quizaquiz' => $quizid);
if (!$includepreviews) {
$where .= ' AND preview = 0';
}
if ($onlyfinished) {
$where .= ' AND state = :statefinished';
$params['statefinished'] = \quiz_attempt::FINISHED;
}
parent::__construct('{quiz_attempts} quiza', 'quiza.uniqueid', $where, $params);
}
}

View File

@ -0,0 +1,43 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\question;
/**
* A {@see qubaid_condition} for finding all the question usages belonging to a particular user and quiz combination.
*
* @package mod_quiz
* @category question
* @copyright 2018 Andrew Nicols <andrwe@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated This class was never needed because qubaids_for_users_attempts already existed and is more flexible.
*/
class qubaids_for_quiz_user extends qubaids_for_users_attempts {
/**
* Constructor.
*
* @param int $quizid The quiz to search.
* @param int $userid The user to filter on
* @param bool $includepreviews Whether to include preview attempts
* @param bool $onlyfinished Whether to only include finished attempts or not
*/
public function __construct(int $quizid, int $userid,
bool $includepreviews = true, bool $onlyfinished = false) {
debugging('qubaids_for_quiz_user is deprecated. Please use qubaids_for_users_attempts instead.');
parent::__construct($quizid, $userid,
$onlyfinished ? 'finished' : 'all', $includepreviews);
}
}

View File

@ -14,24 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A {@link qubaid_condition} representing all the attempts by one user at a given quiz.
*
* @package mod_quiz
* @category question
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\question;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/datalib.php');
require_once($CFG->dirroot.'/mod/quiz/attemptlib.php');
/**
* A {@link qubaid_condition} representing all the attempts by one user at a given quiz.
* A {@see qubaid_condition} representing all the attempts by one user at a given quiz.
*
* @package mod_quiz
* @category question
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@ -48,7 +42,7 @@ class qubaids_for_users_attempts extends \qubaid_join {
*/
public function __construct($quizid, $userid, $status = 'finished', $includepreviews = false) {
$where = 'quiza.quiz = :quizaquiz AND quiza.userid = :userid';
$params = array('quizaquiz' => $quizid, 'userid' => $userid);
$params = ['quizaquiz' => $quizid, 'userid' => $userid];
if (!$includepreviews) {
$where .= ' AND preview = 0';

View File

@ -24,6 +24,7 @@
namespace mod_quiz;
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\question\qubaids_for_quiz;
defined('MOODLE_INTERNAL') || die();
@ -1043,7 +1044,7 @@ class structure {
$previousmaxmark = $slot->maxmark;
$slot->maxmark = $maxmark;
$DB->update_record('quiz_slots', $slot);
\question_engine::set_max_mark_in_attempts(new \qubaids_for_quiz($slot->quizid),
\question_engine::set_max_mark_in_attempts(new qubaids_for_quiz($slot->quizid),
$slot->slot, $maxmark);
$trans->allow_commit();

View File

@ -22,7 +22,7 @@ use context_course;
use core_user;
use moodle_recordset;
use question_display_options;
use mod_quiz_display_options;
use mod_quiz\question\display_options;
use quiz_attempt;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
@ -103,7 +103,7 @@ class quiz_notify_attempt_manual_grading_completed extends \core\task\scheduled_
$quiz = quiz_update_effective_access($quiz, $attempt->userid);
$attemptobj = new quiz_attempt($attempt, $quiz, $cm, $course, false);
$options = mod_quiz_display_options::make_from_quiz($quiz, quiz_attempt_state($quiz, $attempt));
$options = display_options::make_from_quiz($quiz, quiz_attempt_state($quiz, $attempt));
if ($options->manualcomment == question_display_options::HIDDEN) {
// User cannot currently see the feedback, so don't message them.

View File

@ -23,7 +23,7 @@
<FIELD NAME="grademethod" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="One of the values QUIZ_GRADEHIGHEST, QUIZ_GRADEAVERAGE, QUIZ_ATTEMPTFIRST or QUIZ_ATTEMPTLAST."/>
<FIELD NAME="decimalpoints" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="2" SEQUENCE="false" COMMENT="Number of decimal points to use when displaying grades."/>
<FIELD NAME="questiondecimalpoints" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="-1" SEQUENCE="false" COMMENT="Number of decimal points to use when displaying question grades. (-1 means use decimalpoints.)"/>
<FIELD NAME="reviewattempt" TYPE="int" LENGTH="6" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether users are allowed to review their quiz attempts at various times. This is a bit field, decoded by the mod_quiz_display_options class. It is formed by ORing together the constants defined there."/>
<FIELD NAME="reviewattempt" TYPE="int" LENGTH="6" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether users are allowed to review their quiz attempts at various times. This is a bit field, decoded by the \mod_quiz\question\display_options class. It is formed by ORing together the constants defined there."/>
<FIELD NAME="reviewcorrectness" TYPE="int" LENGTH="6" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether users are allowed to review their quiz attempts at various times. A bit field, like reviewattempt."/>
<FIELD NAME="reviewmarks" TYPE="int" LENGTH="6" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether users are allowed to review their quiz attempts at various times. A bit field, like reviewattempt."/>
<FIELD NAME="reviewspecificfeedback" TYPE="int" LENGTH="6" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether users are allowed to review their quiz attempts at various times. A bit field, like reviewattempt."/>

View File

@ -27,4 +27,28 @@ defined('MOODLE_INTERNAL') || die();
$renamedclasses = [
// Since Moodle 4.1.
'mod_quiz\local\views\secondary' => 'mod_quiz\navigation\views\secondary',
// Since Moodle 4.2.
'mod_quiz_display_options' => 'mod_quiz\question\display_options',
'qubaids_for_quiz' => 'mod_quiz\question\qubaids_for_quiz',
'qubaids_for_quiz_user' => 'mod_quiz\question\qubaids_for_quiz_user',
'mod_quiz_admin_setting_browsersecurity' => 'mod_quiz\admin\browser_security_setting',
'mod_quiz_admin_setting_grademethod' => 'mod_quiz\admin\grade_method_setting',
'mod_quiz_admin_setting_overduehandling' => 'mod_quiz\admin\overdue_handling_setting',
'mod_quiz_admin_review_setting' => 'mod_quiz\admin\review_setting',
'mod_quiz_admin_setting_user_image' => 'mod_quiz\admin\user_image_setting',
'mod_quiz\adminpresets\adminpresets_mod_quiz_admin_setting_browsersecurity' =>
'mod_quiz\adminpresets\adminpresets_browser_security_setting',
'mod_quiz\adminpresets/adminpresets_mod_quiz_admin_setting_grademethod' =>
'mod_quiz\adminpresets\adminpresets_grade_method_setting',
'mod_quiz\adminpresets\adminpresets_mod_quiz_admin_setting_overduehandling' =>
'mod_quiz\adminpresets\adminpresets_overdue_handling_setting',
'mod_quiz\adminpresets\adminpresets_mod_quiz_admin_review_setting' =>
'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',
];

View File

@ -29,6 +29,9 @@
defined('MOODLE_INTERNAL') || die();
use mod_quiz\question\bank\custom_view;
use mod_quiz\question\display_options;
use mod_quiz\question\qubaids_for_quiz;
use mod_quiz\question\qubaids_for_users_attempts;
use core_question\statistics\questions\all_calculated_for_qubaid_condition;
require_once($CFG->dirroot . '/calendar/lib.php');
@ -414,7 +417,8 @@ function quiz_delete_all_attempts($quiz) {
function quiz_delete_user_attempts($quiz, $user) {
global $CFG, $DB;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
question_engine::delete_questions_usage_by_activities(new qubaids_for_quiz_user($quiz->get_quizid(), $user->id));
question_engine::delete_questions_usage_by_activities(new qubaids_for_users_attempts(
$quiz->get_quizid(), $user->id, 'all'));
$params = [
'quiz' => $quiz->get_quizid(),
'userid' => $user->id,
@ -759,10 +763,10 @@ function quiz_grade_item_update($quiz, $grades = null) {
// 2. If the quiz is set to not show grades at either of those times,
// create the grade_item as hidden.
// 3. If the quiz is set to show grades, create the grade_item visible.
$openreviewoptions = mod_quiz_display_options::make_from_quiz($quiz,
mod_quiz_display_options::LATER_WHILE_OPEN);
$closedreviewoptions = mod_quiz_display_options::make_from_quiz($quiz,
mod_quiz_display_options::AFTER_CLOSE);
$openreviewoptions = display_options::make_from_quiz($quiz,
display_options::LATER_WHILE_OPEN);
$closedreviewoptions = display_options::make_from_quiz($quiz,
display_options::AFTER_CLOSE);
if ($openreviewoptions->marks < question_display_options::MARK_AND_MAX &&
$closedreviewoptions->marks < question_display_options::MARK_AND_MAX) {
$params['hidden'] = 1;
@ -1115,8 +1119,8 @@ function quiz_process_options($quiz) {
$quiz->reviewgeneralfeedback = quiz_review_option_form_to_db($quiz, 'generalfeedback');
$quiz->reviewrightanswer = quiz_review_option_form_to_db($quiz, 'rightanswer');
$quiz->reviewoverallfeedback = quiz_review_option_form_to_db($quiz, 'overallfeedback');
$quiz->reviewattempt |= mod_quiz_display_options::DURING;
$quiz->reviewoverallfeedback &= ~mod_quiz_display_options::DURING;
$quiz->reviewattempt |= display_options::DURING;
$quiz->reviewoverallfeedback &= ~display_options::DURING;
// Ensure that disabled checkboxes in completion settings are set to 0.
// But only if the completion settinsg are unlocked.
@ -1140,10 +1144,10 @@ function quiz_process_options($quiz) {
*/
function quiz_review_option_form_to_db($fromform, $field) {
static $times = array(
'during' => mod_quiz_display_options::DURING,
'immediately' => mod_quiz_display_options::IMMEDIATELY_AFTER,
'open' => mod_quiz_display_options::LATER_WHILE_OPEN,
'closed' => mod_quiz_display_options::AFTER_CLOSE,
'during' => display_options::DURING,
'immediately' => display_options::IMMEDIATELY_AFTER,
'open' => display_options::LATER_WHILE_OPEN,
'closed' => display_options::AFTER_CLOSE,
);
$review = 0;

View File

@ -40,6 +40,7 @@ require_once($CFG->libdir . '/filelib.php');
require_once($CFG->libdir . '/questionlib.php');
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\question\display_options;
/**
* @var int We show the countdown timer if there is less than this amount of time left before the
@ -1450,8 +1451,8 @@ function quiz_question_edit_button($cmid, $question, $returnurl, $contentafteric
*/
function quiz_question_preview_url($quiz, $question, $variant = null) {
// Get the appropriate display options.
$displayoptions = mod_quiz_display_options::make_from_quiz($quiz,
mod_quiz_display_options::DURING);
$displayoptions = display_options::make_from_quiz($quiz,
display_options::DURING);
$maxmark = null;
if (isset($question->maxmark)) {
@ -1500,33 +1501,32 @@ function quiz_get_flag_option($attempt, $context) {
* quiz_get_review_options, not in the sense of $attempt->state.
* @param object $quiz the quiz settings
* @param object $attempt the quiz_attempt database row.
* @return int one of the mod_quiz_display_options::DURING,
* @return int one of the display_options::DURING,
* IMMEDIATELY_AFTER, LATER_WHILE_OPEN or AFTER_CLOSE constants.
*/
function quiz_attempt_state($quiz, $attempt) {
if ($attempt->state == quiz_attempt::IN_PROGRESS) {
return mod_quiz_display_options::DURING;
return display_options::DURING;
} else if ($quiz->timeclose && time() >= $quiz->timeclose) {
return mod_quiz_display_options::AFTER_CLOSE;
return display_options::AFTER_CLOSE;
} else if (time() < $attempt->timefinish + 120) {
return mod_quiz_display_options::IMMEDIATELY_AFTER;
return display_options::IMMEDIATELY_AFTER;
} else {
return mod_quiz_display_options::LATER_WHILE_OPEN;
return display_options::LATER_WHILE_OPEN;
}
}
/**
* The the appropraite mod_quiz_display_options object for this attempt at this
* quiz right now.
* The appropriate display_options object for this attempt at this quiz right now.
*
* @param stdClass $quiz the quiz instance.
* @param stdClass $attempt the attempt in question.
* @param context $context the quiz context.
*
* @return mod_quiz_display_options
* @return display_options
*/
function quiz_get_review_options($quiz, $attempt, $context) {
$options = mod_quiz_display_options::make_from_quiz($quiz, quiz_attempt_state($quiz, $attempt));
$options = display_options::make_from_quiz($quiz, quiz_attempt_state($quiz, $attempt));
$options->readonly = true;
$options->flags = quiz_get_flag_option($attempt, $context);
@ -1595,7 +1595,7 @@ function quiz_get_combined_reviewoptions($quiz, $attempts) {
}
foreach ($attempts as $attempt) {
$attemptoptions = mod_quiz_display_options::make_from_quiz($quiz,
$attemptoptions = display_options::make_from_quiz($quiz,
quiz_attempt_state($quiz, $attempt));
foreach ($fields as $field) {
$someoptions->$field = $someoptions->$field || $attemptoptions->$field;
@ -2066,137 +2066,6 @@ function quiz_get_js_module() {
}
/**
* An extension of question_display_options that includes the extra options used
* by the quiz.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_display_options extends question_display_options {
/**#@+
* @var integer bits used to indicate various times in relation to a
* quiz attempt.
*/
const DURING = 0x10000;
const IMMEDIATELY_AFTER = 0x01000;
const LATER_WHILE_OPEN = 0x00100;
const AFTER_CLOSE = 0x00010;
/**#@-*/
/**
* @var boolean if this is false, then the student is not allowed to review
* anything about the attempt.
*/
public $attempt = true;
/**
* @var boolean if this is false, then the student is not allowed to review
* anything about the attempt.
*/
public $overallfeedback = self::VISIBLE;
/**
* Set up the various options from the quiz settings, and a time constant.
* @param object $quiz the quiz settings.
* @param int $one of the {@link DURING}, {@link IMMEDIATELY_AFTER},
* {@link LATER_WHILE_OPEN} or {@link AFTER_CLOSE} constants.
* @return mod_quiz_display_options set up appropriately.
*/
public static function make_from_quiz($quiz, $when) {
$options = new self();
$options->attempt = self::extract($quiz->reviewattempt, $when, true, false);
$options->correctness = self::extract($quiz->reviewcorrectness, $when);
$options->marks = self::extract($quiz->reviewmarks, $when,
self::MARK_AND_MAX, self::MAX_ONLY);
$options->feedback = self::extract($quiz->reviewspecificfeedback, $when);
$options->generalfeedback = self::extract($quiz->reviewgeneralfeedback, $when);
$options->rightanswer = self::extract($quiz->reviewrightanswer, $when);
$options->overallfeedback = self::extract($quiz->reviewoverallfeedback, $when);
$options->numpartscorrect = $options->feedback;
$options->manualcomment = $options->feedback;
if ($quiz->questiondecimalpoints != -1) {
$options->markdp = $quiz->questiondecimalpoints;
} else {
$options->markdp = $quiz->decimalpoints;
}
return $options;
}
protected static function extract($bitmask, $bit,
$whenset = self::VISIBLE, $whennotset = self::HIDDEN) {
if ($bitmask & $bit) {
return $whenset;
} else {
return $whennotset;
}
}
}
/**
* A {@link qubaid_condition} for finding all the question usages belonging to
* a particular quiz.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qubaids_for_quiz extends qubaid_join {
public function __construct($quizid, $includepreviews = true, $onlyfinished = false) {
$where = 'quiza.quiz = :quizaquiz';
$params = array('quizaquiz' => $quizid);
if (!$includepreviews) {
$where .= ' AND preview = 0';
}
if ($onlyfinished) {
$where .= ' AND state = :statefinished';
$params['statefinished'] = quiz_attempt::FINISHED;
}
parent::__construct('{quiz_attempts} quiza', 'quiza.uniqueid', $where, $params);
}
}
/**
* A {@link qubaid_condition} for finding all the question usages belonging to a particular user and quiz combination.
*
* @copyright 2018 Andrew Nicols <andrwe@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qubaids_for_quiz_user extends qubaid_join {
/**
* Constructor for this qubaid.
*
* @param int $quizid The quiz to search.
* @param int $userid The user to filter on
* @param bool $includepreviews Whether to include preview attempts
* @param bool $onlyfinished Whether to only include finished attempts or not
*/
public function __construct($quizid, $userid, $includepreviews = true, $onlyfinished = false) {
$where = 'quiza.quiz = :quizaquiz AND quiza.userid = :quizauserid';
$params = [
'quizaquiz' => $quizid,
'quizauserid' => $userid,
];
if (!$includepreviews) {
$where .= ' AND preview = 0';
}
if ($onlyfinished) {
$where .= ' AND state = :statefinished';
$params['statefinished'] = quiz_attempt::FINISHED;
}
parent::__construct('{quiz_attempts} quiza', 'quiza.uniqueid', $where, $params);
}
}
/**
* Creates a textual representation of a question for display.
*

View File

@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/course/moodleform_mod.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
use mod_quiz\question\display_options;
/**
* Settings form for the quiz module.
@ -204,13 +205,13 @@ class mod_quiz_mod_form extends moodleform_mod {
// Review options.
$this->add_review_options_group($mform, $quizconfig, 'during',
mod_quiz_display_options::DURING, true);
display_options::DURING, true);
$this->add_review_options_group($mform, $quizconfig, 'immediately',
mod_quiz_display_options::IMMEDIATELY_AFTER);
display_options::IMMEDIATELY_AFTER);
$this->add_review_options_group($mform, $quizconfig, 'open',
mod_quiz_display_options::LATER_WHILE_OPEN);
display_options::LATER_WHILE_OPEN);
$this->add_review_options_group($mform, $quizconfig, 'closed',
mod_quiz_display_options::AFTER_CLOSE);
display_options::AFTER_CLOSE);
foreach ($behaviours as $behaviour => $notused) {
$unusedoptions = question_engine::get_behaviour_unused_display_options($behaviour);
@ -459,13 +460,13 @@ class mod_quiz_mod_form extends moodleform_mod {
}
$this->preprocessing_review_settings($toform, 'during',
mod_quiz_display_options::DURING);
display_options::DURING);
$this->preprocessing_review_settings($toform, 'immediately',
mod_quiz_display_options::IMMEDIATELY_AFTER);
display_options::IMMEDIATELY_AFTER);
$this->preprocessing_review_settings($toform, 'open',
mod_quiz_display_options::LATER_WHILE_OPEN);
display_options::LATER_WHILE_OPEN);
$this->preprocessing_review_settings($toform, 'closed',
mod_quiz_display_options::AFTER_CLOSE);
display_options::AFTER_CLOSE);
$toform['attemptduring'] = true;
$toform['overallfeedbackduring'] = false;

View File

@ -25,6 +25,8 @@
defined('MOODLE_INTERNAL') || die();
use mod_quiz\question\display_options;
/**
* The renderer for the quiz module.
@ -41,12 +43,12 @@ class mod_quiz_renderer extends plugin_renderer_base {
* @param int $page the current page number
* @param bool $showall whether to show entire attempt on one page.
* @param bool $lastpage if true the current page is the last page.
* @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options.
* @param display_options $displayoptions instance of display_options.
* @param array $summarydata contains all table data
* @return $output containing html data.
*/
public function review_page(quiz_attempt $attemptobj, $slots, $page, $showall,
$lastpage, mod_quiz_display_options $displayoptions,
$lastpage, display_options $displayoptions,
$summarydata) {
$output = '';
@ -67,12 +69,12 @@ class mod_quiz_renderer extends plugin_renderer_base {
* @param quiz_attempt $attemptobj an instance of quiz_attempt.
* @param int $slot which question to display.
* @param int $seq which step of the question attempt to show. null = latest.
* @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options.
* @param display_options $displayoptions instance of display_options.
* @param array $summarydata contains all table data
* @return $output containing html data.
*/
public function review_question_page(quiz_attempt $attemptobj, $slot, $seq,
mod_quiz_display_options $displayoptions, $summarydata) {
display_options $displayoptions, $summarydata) {
$output = '';
$output .= $this->header();
@ -177,10 +179,10 @@ class mod_quiz_renderer extends plugin_renderer_base {
* @param array $slots array of intgers relating to questions
* @param int $page current page number
* @param bool $showall if true shows attempt on single page
* @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options
* @param display_options $displayoptions instance of display_options
*/
public function questions(quiz_attempt $attemptobj, $reviewing, $slots, $page, $showall,
mod_quiz_display_options $displayoptions) {
display_options $displayoptions) {
$output = '';
foreach ($slots as $slot) {
$output .= $attemptobj->render_question($slot, $reviewing, $this,
@ -194,7 +196,7 @@ class mod_quiz_renderer extends plugin_renderer_base {
*
* @param array $summarydata contain row data for table
* @param int $page current page number
* @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options
* @param display_options $displayoptions instance of display_options
* @param $content contains each question
* @param quiz_attempt $attemptobj instance of quiz_attempt
* @param bool $showall if true display attempt on one page
@ -662,7 +664,7 @@ class mod_quiz_renderer extends plugin_renderer_base {
* Create the summary page
*
* @param quiz_attempt $attemptobj
* @param mod_quiz_display_options $displayoptions
* @param display_options $displayoptions
*/
public function summary_page($attemptobj, $displayoptions) {
$output = '';
@ -680,7 +682,7 @@ class mod_quiz_renderer extends plugin_renderer_base {
* Generates the table of summarydata
*
* @param quiz_attempt $attemptobj
* @param mod_quiz_display_options $displayoptions
* @param display_options $displayoptions
*/
public function summary_table($attemptobj, $displayoptions) {
// Prepare the summary table header.

View File

@ -27,7 +27,6 @@ define('NO_OUTPUT_BUFFERING', true);
require_once(__DIR__ . '/../../config.php');
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');
$id = optional_param('id', 0, PARAM_INT);
$q = optional_param('q', 0, PARAM_INT);

View File

@ -15,371 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 '<div class="quizattemptcounts">' . $strattemptnum . '</div>';
}
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);

View File

@ -15,106 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);

View File

@ -15,277 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);

View File

@ -15,687 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 '<div id="tablecontainer">';
echo '<form id="attemptsform" method="post" action="' . $url->out_omit_querystring() . '">';
echo html_writer::input_hidden_params($url);
echo '<div>';
}
public function wrap_html_finish() {
global $PAGE;
if ($this->is_downloading() || !$this->includecheckboxes) {
return;
}
echo '<div id="commands">';
$this->submit_buttons();
echo '</div>';
// Close the form.
echo '</div>';
echo '</form></div>';
}
/**
* 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);

View File

@ -15,85 +15,11 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);

View File

@ -14,20 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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';

View File

@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}

View File

@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}

View File

@ -14,19 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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();

View File

@ -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.

View File

@ -14,26 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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 <huongnv13@gmail.com>
* @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 <huongnv13@gmail.com>
* @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.

View File

@ -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);

View File

@ -30,6 +30,8 @@ require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
require_once($CFG->libdir . '/filelib.php');
require_once($CFG->dirroot . '/mod/quiz/accessmanager.php');
use mod_quiz\question\display_options;
/**
* Takes an array of objects and constructs a multidimensional array keyed by
* the keys it finds on the object.
@ -427,11 +429,11 @@ function quiz_no_questions_message($quiz, $cm, $context) {
*/
function quiz_report_should_show_grades($quiz, context $context) {
if ($quiz->timeclose && time() > $quiz->timeclose) {
$when = mod_quiz_display_options::AFTER_CLOSE;
$when = display_options::AFTER_CLOSE;
} else {
$when = mod_quiz_display_options::LATER_WHILE_OPEN;
$when = display_options::LATER_WHILE_OPEN;
}
$reviewoptions = mod_quiz_display_options::make_from_quiz($quiz, $when);
$reviewoptions = display_options::make_from_quiz($quiz, $when);
return quiz_has_grades($quiz) &&
($reviewoptions->marks >= question_display_options::MARK_AND_MAX ||

View File

@ -14,18 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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

View File

@ -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;

View File

@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}
}

View File

@ -14,19 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}

View File

@ -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');

View File

@ -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;

View File

@ -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');

View File

@ -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
* 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

View File

@ -22,6 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\admin\review_setting;
defined('MOODLE_INTERNAL') || die();
@ -72,7 +73,7 @@ if ($ADMIN->fulltree) {
get_string('attemptgradeddelay', 'quiz'), get_string('attemptgradeddelay_desc', 'quiz'), 5 * HOURSECS, HOURSECS));
// What to do with overdue attempts.
$setting = new mod_quiz_admin_setting_overduehandling('quiz/overduehandling',
$setting = new \mod_quiz\admin\overdue_handling_setting('quiz/overduehandling',
get_string('overduehandling', 'quiz'), get_string('overduehandling_desc', 'quiz'),
array('value' => 'autosubmit', 'adv' => false), null);
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
@ -104,7 +105,7 @@ if ($ADMIN->fulltree) {
$quizsettings->add($setting);
// Grading method.
$setting = new mod_quiz_admin_setting_grademethod('quiz/grademethod',
$setting = new \mod_quiz\admin\grade_method_setting('quiz/grademethod',
get_string('grademethod', 'quiz'), get_string('configgrademethod', 'quiz'),
array('value' => QUIZ_GRADEHIGHEST, 'adv' => false), null);
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
@ -174,21 +175,21 @@ if ($ADMIN->fulltree) {
// Review options.
$quizsettings->add(new admin_setting_heading('reviewheading',
get_string('reviewoptionsheading', 'quiz'), ''));
foreach (mod_quiz_admin_review_setting::fields() as $field => $name) {
$default = mod_quiz_admin_review_setting::all_on();
foreach (review_setting::fields() as $field => $name) {
$default = review_setting::all_on();
$forceduring = null;
if ($field == 'attempt') {
$forceduring = true;
} else if ($field == 'overallfeedback') {
$default = $default ^ mod_quiz_admin_review_setting::DURING;
$default = $default ^ review_setting::DURING;
$forceduring = false;
}
$quizsettings->add(new mod_quiz_admin_review_setting('quiz/review' . $field,
$quizsettings->add(new review_setting('quiz/review' . $field,
$name, '', $default, $forceduring));
}
// Show the user's picture.
$setting = new mod_quiz_admin_setting_user_image('quiz/showuserpicture',
$setting = new \mod_quiz\admin\user_image_setting('quiz/showuserpicture',
get_string('showuserpicture', 'quiz'), get_string('configshowuserpicture', 'quiz'),
array('value' => 0, 'adv' => false), null);
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);
@ -259,7 +260,7 @@ if ($ADMIN->fulltree) {
$quizsettings->add($setting);
// Browser security.
$setting = new mod_quiz_admin_setting_browsersecurity('quiz/browsersecurity',
$setting = new \mod_quiz\admin\browser_security_setting('quiz/browsersecurity',
get_string('showinsecurepopup', 'quiz'), get_string('configpopup', 'quiz'),
array('value' => '-', 'adv' => true), null);
$setting->set_locked_flag_options(admin_setting_flag::ENABLED, false);

View File

@ -28,7 +28,7 @@ namespace mod_quiz\external;
use externallib_advanced_testcase;
use mod_quiz_external;
use mod_quiz_display_options;
use mod_quiz\question\display_options;
use quiz;
use quiz_attempt;
@ -681,7 +681,7 @@ class external_test extends externallib_advanced_testcase {
}
/**
* Test get_combined_review_options.
* This is a basic test, this is already tested in mod_quiz_display_options_testcase.
* This is a basic test, this is already tested in display_options_testcase.
*/
public function test_get_combined_review_options() {
global $DB;
@ -1027,7 +1027,7 @@ class external_test extends externallib_advanced_testcase {
list($quiz, $context, $quizobj, $attempt, $attemptobj) = $this->create_quiz_with_questions(true);
// Set correctness mask so questions state can be fetched only after finishing the attempt.
$DB->set_field('quiz', 'reviewcorrectness', mod_quiz_display_options::IMMEDIATELY_AFTER, array('id' => $quiz->id));
$DB->set_field('quiz', 'reviewcorrectness', display_options::IMMEDIATELY_AFTER, array('id' => $quiz->id));
$quizobj = $attemptobj->get_quizobj();
$quizobj->preload_questions();

View File

@ -25,7 +25,7 @@
namespace mod_quiz;
use quiz_attempt;
use mod_quiz_display_options;
use mod_quiz\question\display_options;
defined('MOODLE_INTERNAL') || die();
@ -60,30 +60,31 @@ class locallib_test extends \advanced_testcase {
public function quiz_attempt_state_data_provider() {
return [
[quiz_attempt::IN_PROGRESS, null, null, mod_quiz_display_options::DURING],
[quiz_attempt::FINISHED, -90, null, mod_quiz_display_options::IMMEDIATELY_AFTER],
[quiz_attempt::FINISHED, -7200, null, mod_quiz_display_options::LATER_WHILE_OPEN],
[quiz_attempt::FINISHED, -7200, 3600, mod_quiz_display_options::LATER_WHILE_OPEN],
[quiz_attempt::FINISHED, -30, 30, mod_quiz_display_options::IMMEDIATELY_AFTER],
[quiz_attempt::FINISHED, -90, -30, mod_quiz_display_options::AFTER_CLOSE],
[quiz_attempt::FINISHED, -7200, -3600, mod_quiz_display_options::AFTER_CLOSE],
[quiz_attempt::FINISHED, -90, -3600, mod_quiz_display_options::AFTER_CLOSE],
[quiz_attempt::ABANDONED, -10000000, null, mod_quiz_display_options::LATER_WHILE_OPEN],
[quiz_attempt::ABANDONED, -7200, 3600, mod_quiz_display_options::LATER_WHILE_OPEN],
[quiz_attempt::ABANDONED, -7200, -3600, mod_quiz_display_options::AFTER_CLOSE],
[quiz_attempt::IN_PROGRESS, null, null, display_options::DURING],
[quiz_attempt::FINISHED, -90, null, display_options::IMMEDIATELY_AFTER],
[quiz_attempt::FINISHED, -7200, null, display_options::LATER_WHILE_OPEN],
[quiz_attempt::FINISHED, -7200, 3600, display_options::LATER_WHILE_OPEN],
[quiz_attempt::FINISHED, -30, 30, display_options::IMMEDIATELY_AFTER],
[quiz_attempt::FINISHED, -90, -30, display_options::AFTER_CLOSE],
[quiz_attempt::FINISHED, -7200, -3600, display_options::AFTER_CLOSE],
[quiz_attempt::FINISHED, -90, -3600, display_options::AFTER_CLOSE],
[quiz_attempt::ABANDONED, -10000000, null, display_options::LATER_WHILE_OPEN],
[quiz_attempt::ABANDONED, -7200, 3600, display_options::LATER_WHILE_OPEN],
[quiz_attempt::ABANDONED, -7200, -3600, display_options::AFTER_CLOSE],
];
}
/**
* @dataProvider quiz_attempt_state_data_provider
*
* @param unknown $attemptstate as in the quiz_attempts.state DB column.
* @param unknown $relativetimefinish time relative to now when the attempt finished, or null for 0.
* @param unknown $relativetimeclose time relative to now when the quiz closes, or null for 0.
* @param unknown $expectedstate expected result. One of the mod_quiz_display_options constants/
* @param string $attemptstate as in the quiz_attempts.state DB column.
* @param int|null $relativetimefinish time relative to now when the attempt finished, or null for 0.
* @param int|null $relativetimeclose time relative to now when the quiz closes, or null for 0.
* @param int $expectedstate expected result. One of the display_options constants.
* @covers ::quiz_attempt_state
*/
public function test_quiz_attempt_state($attemptstate,
$relativetimefinish, $relativetimeclose, $expectedstate) {
public function test_quiz_attempt_state(string $attemptstate,
?int $relativetimefinish, ?int $relativetimeclose, int $expectedstate) {
$attempt = new \stdClass();
$attempt->state = $attemptstate;

View File

@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\question;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Unit tests for {@see display_options}.
*
* @package mod_quiz
* @category test
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \mod_quiz\question\display_options
*/
class display_options_test extends \basic_testcase {
public function test_num_attempts_access_rule() {
$quiz = new \stdClass();
$quiz->decimalpoints = 2;
$quiz->questiondecimalpoints = -1;
$quiz->reviewattempt = 0x11110;
$quiz->reviewcorrectness = 0x10000;
$quiz->reviewmarks = 0x01110;
$quiz->reviewspecificfeedback = 0x10000;
$quiz->reviewgeneralfeedback = 0x01000;
$quiz->reviewrightanswer = 0x00100;
$quiz->reviewoverallfeedback = 0x00010;
$options = display_options::make_from_quiz($quiz,
display_options::DURING);
$this->assertEquals(true, $options->attempt);
$this->assertEquals(display_options::VISIBLE, $options->correctness);
$this->assertEquals(display_options::MAX_ONLY, $options->marks);
$this->assertEquals(display_options::VISIBLE, $options->feedback);
// The next two should be controlled by the same settings as ->feedback.
$this->assertEquals(display_options::VISIBLE, $options->numpartscorrect);
$this->assertEquals(display_options::VISIBLE, $options->manualcomment);
$this->assertEquals(2, $options->markdp);
$quiz->questiondecimalpoints = 5;
$options = display_options::make_from_quiz($quiz,
display_options::IMMEDIATELY_AFTER);
$this->assertEquals(display_options::MARK_AND_MAX, $options->marks);
$this->assertEquals(display_options::VISIBLE, $options->generalfeedback);
$this->assertEquals(display_options::HIDDEN, $options->feedback);
// The next two should be controlled by the same settings as ->feedback.
$this->assertEquals(display_options::HIDDEN, $options->numpartscorrect);
$this->assertEquals(display_options::HIDDEN, $options->manualcomment);
$this->assertEquals(5, $options->markdp);
$options = display_options::make_from_quiz($quiz,
display_options::LATER_WHILE_OPEN);
$this->assertEquals(display_options::VISIBLE, $options->rightanswer);
$this->assertEquals(display_options::HIDDEN, $options->generalfeedback);
$options = display_options::make_from_quiz($quiz,
display_options::AFTER_CLOSE);
$this->assertEquals(display_options::VISIBLE, $options->overallfeedback);
$this->assertEquals(display_options::HIDDEN, $options->rightanswer);
}
}

View File

@ -1,83 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz;
use mod_quiz_display_options;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Unit tests for {@link mod_quiz_display_options}.
*
* @package mod_quiz
* @category test
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizdisplayoptions_test extends \basic_testcase {
public function test_num_attempts_access_rule() {
$quiz = new \stdClass();
$quiz->decimalpoints = 2;
$quiz->questiondecimalpoints = -1;
$quiz->reviewattempt = 0x11110;
$quiz->reviewcorrectness = 0x10000;
$quiz->reviewmarks = 0x01110;
$quiz->reviewspecificfeedback = 0x10000;
$quiz->reviewgeneralfeedback = 0x01000;
$quiz->reviewrightanswer = 0x00100;
$quiz->reviewoverallfeedback = 0x00010;
$options = mod_quiz_display_options::make_from_quiz($quiz,
mod_quiz_display_options::DURING);
$this->assertEquals(true, $options->attempt);
$this->assertEquals(mod_quiz_display_options::VISIBLE, $options->correctness);
$this->assertEquals(mod_quiz_display_options::MAX_ONLY, $options->marks);
$this->assertEquals(mod_quiz_display_options::VISIBLE, $options->feedback);
// The next two should be controlled by the same settings as ->feedback.
$this->assertEquals(mod_quiz_display_options::VISIBLE, $options->numpartscorrect);
$this->assertEquals(mod_quiz_display_options::VISIBLE, $options->manualcomment);
$this->assertEquals(2, $options->markdp);
$quiz->questiondecimalpoints = 5;
$options = mod_quiz_display_options::make_from_quiz($quiz,
mod_quiz_display_options::IMMEDIATELY_AFTER);
$this->assertEquals(mod_quiz_display_options::MARK_AND_MAX, $options->marks);
$this->assertEquals(mod_quiz_display_options::VISIBLE, $options->generalfeedback);
$this->assertEquals(mod_quiz_display_options::HIDDEN, $options->feedback);
// The next two should be controlled by the same settings as ->feedback.
$this->assertEquals(mod_quiz_display_options::HIDDEN, $options->numpartscorrect);
$this->assertEquals(mod_quiz_display_options::HIDDEN, $options->manualcomment);
$this->assertEquals(5, $options->markdp);
$options = mod_quiz_display_options::make_from_quiz($quiz,
mod_quiz_display_options::LATER_WHILE_OPEN);
$this->assertEquals(mod_quiz_display_options::VISIBLE, $options->rightanswer);
$this->assertEquals(mod_quiz_display_options::HIDDEN, $options->generalfeedback);
$options = mod_quiz_display_options::make_from_quiz($quiz,
mod_quiz_display_options::AFTER_CLOSE);
$this->assertEquals(mod_quiz_display_options::VISIBLE, $options->overallfeedback);
$this->assertEquals(mod_quiz_display_options::HIDDEN, $options->rightanswer);
}
}

View File

@ -16,7 +16,7 @@
namespace mod_quiz;
use mod_quiz_display_options;
use mod_quiz\question\display_options;
use quiz;
defined('MOODLE_INTERNAL') || die();
@ -44,19 +44,19 @@ class quizobj_test extends \basic_testcase {
$quizobj = new quiz($quiz, $cm, new \stdClass(), false);
$this->assertEquals('',
$quizobj->cannot_review_message(mod_quiz_display_options::DURING));
$quizobj->cannot_review_message(display_options::DURING));
$this->assertEquals('',
$quizobj->cannot_review_message(mod_quiz_display_options::IMMEDIATELY_AFTER));
$quizobj->cannot_review_message(display_options::IMMEDIATELY_AFTER));
$this->assertEquals(get_string('noreview', 'quiz'),
$quizobj->cannot_review_message(mod_quiz_display_options::LATER_WHILE_OPEN));
$quizobj->cannot_review_message(display_options::LATER_WHILE_OPEN));
$this->assertEquals(get_string('noreview', 'quiz'),
$quizobj->cannot_review_message(mod_quiz_display_options::AFTER_CLOSE));
$quizobj->cannot_review_message(display_options::AFTER_CLOSE));
$closetime = time() + 10000;
$quiz->timeclose = $closetime;
$quizobj = new quiz($quiz, $cm, new \stdClass(), false);
$this->assertEquals(get_string('noreviewuntil', 'quiz', userdate($closetime)),
$quizobj->cannot_review_message(mod_quiz_display_options::LATER_WHILE_OPEN));
$quizobj->cannot_review_message(display_options::LATER_WHILE_OPEN));
}
}

View File

@ -1,5 +1,50 @@
This files describes API changes in the quiz code.
=== 4.2 ===
* 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.
- mod_quiz_display_options => mod_quiz\question\display_options
- qubaids_for_quiz => mod_quiz\question\qubaids_for_quiz
- qubaids_for_quiz_user => mod_quiz\question\qubaids_for_quiz_user - which is deprecated, because
it is almost the same as mod_quiz\question\qubaids_for_users_attempts.
- mod_quiz_admin_setting_browsersecurity => mod_quiz\admin\browser_security_setting
- mod_quiz_admin_setting_grademethod => mod_quiz\admin\grade_method_setting
- mod_quiz_admin_setting_overduehandling => mod_quiz\admin\overdue_handling_setting
- mod_quiz_admin_review_setting => mod_quiz\admin\review_setting
- mod_quiz_admin_setting_user_image => mod_quiz\admin\user_image_setting
- mod_quiz_admin_setting_browsersecurity => mod_quiz\admin\browser_security_setting
- mod_quiz_admin_setting_grademethod => mod_quiz\admin\grade_method_setting
- mod_quiz_admin_setting_overduehandling => mod_quiz\admin\overdue_handling_setting
- mod_quiz_admin_review_setting => mod_quiz\admin\review_setting
- mod_quiz_admin_setting_user_image => mod_quiz\admin\user_image_setting
- mod_quiz\adminpresets\adminpresets_mod_quiz_admin_setting_browsersecurity =>
mod_quiz\adminpresets\adminpresets_browser_security_setting
- mod_quiz\adminpresets/adminpresets_mod_quiz_admin_setting_grademethod =>
mod_quiz\adminpresets\adminpresets_grade_method_setting
- mod_quiz\adminpresets\adminpresets_mod_quiz_admin_setting_overduehandling =>
mod_quiz\adminpresets\adminpresets_overdue_handling_setting
- mod_quiz\adminpresets\adminpresets_mod_quiz_admin_review_setting =>
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 ===
* quiz_has_question_use is now deprecated. Use mod_quiz\structure::has_use_capability istead.

View File

@ -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.
*