Commit 25215446 authored by 0815-xyz's avatar 0815-xyz
Browse files

Initial commit

parents
<?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/>.
/**
* Activity custom completion subclass for the adaptive quiz activity.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\completion;
use core_completion\activity_custom_completion;
use mod_adaptivequiz\local\attempt;
class custom_completion extends activity_custom_completion {
/**
* @inheritDoc
*/
public function get_state(string $rule): int {
$this->validate_rule($rule);
return attempt::user_has_completed_on_quiz($this->cm->instance, $this->userid)
? COMPLETION_COMPLETE
: COMPLETION_INCOMPLETE;
}
/**
* @inheritDoc
*/
public static function get_defined_custom_rules(): array {
return ['completionattemptcompleted'];
}
/**
* @inheritDoc
*/
public function get_custom_rule_descriptions(): array {
return ['completionattemptcompleted' => get_string('completionattemptcompletedcminfo', 'adaptivequiz')];
}
/**
* @inheritDoc
*/
public function get_sort_order(): array {
return ['completionview', 'completionusegrade', 'completionattemptcompleted'];
}
}
<?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/>.
/**
* Event which is triggered when a user completes an attempt on adaptive quiz.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\event;
use core\event\base;
use moodle_exception;
use moodle_url;
class attempt_completed extends base {
/**
* @inheritDoc
*/
public static function get_name() {
return get_string('eventattemptcompleted', 'adaptivequiz');
}
/**
* @inheritDoc
*/
public function get_description() {
return "The user with id '$this->userid' has completed the attempt with id '$this->objectid' for the " .
"adaptive quiz with course module id '$this->contextinstanceid'.";
}
/**
* Returns related URL where result of the event can be observed.
*
* @throws moodle_exception
* @return moodle_url
*/
public function get_url() {
return new moodle_url('/mod/adaptivequiz/reviewattempt.php', ['attempt' => $this->objectid]);
}
/**
* @inheritDoc
*/
public static function get_objectid_mapping() {
return ['db' => 'adaptivequiz_attempt', 'restore' => 'adaptiveattempts'];
}
/**
* @inheritDoc
*/
protected function init() {
$this->data['objecttable'] = 'adaptivequiz_attempt';
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
}
<?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/>.
/**
* The mod_peerassess instance list viewed event.
*
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\event;
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
/**
* Create the event from course record.
*
* @param \stdClass $course
* @return course_module_instance_list_viewed
*/
public static function create_from_course(\stdClass $course) {
$params = array(
'context' => \context_course::instance($course->id)
);
$event = self::create($params);
$event->add_record_snapshot('course', $course);
return $event;
}
}
<?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/>.
/**
* Defines the course module viewed event.
*
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\event;
class course_module_viewed extends \core\event\course_module_viewed {
protected function init() {
$this->data['objecttable'] = 'adaptivequiz';
parent::init();
}
}
<?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/>.
/**
* Adaptivequiz required password form
*
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
use moodleform;
use html_writer;
class requiredpassword extends moodleform {
/** @var string $passwordmessage a string containing text for a failed password attempt */
public $passwordmessage = '';
/**
* This method is refactored code from the quiz's password rule add_preflight_check_form_fields() method.
* It prints a form for the user to enter a password
*/
protected function definition() {
$mform = $this->_form;
foreach ($this->_customdata['hidden'] as $name => $value) {
if ($name === 'sesskey') {
continue;
}
if ($name === 'cmid' || $name === 'uniqueid') {
$mform->setType($name, PARAM_INT);
}
$mform->addElement('hidden', $name, $value);
}
$mform->addElement('header', 'passwordheader', get_string('password'));
$mform->addElement('static', 'passwordmessage', '', get_string('requirepasswordmessage', 'adaptivequiz'));
$attr = array('style' => 'color:red;', 'class' => 'wrongpassword');
$html = html_writer::start_tag('div', $attr);
$mform->addElement('html', $html);
$mform->addElement('static', 'message');
$html = html_writer::end_tag('div');
$mform->addElement('html', $html);
// Don't use the 'proper' field name of 'password' since that get's
// Firefox's password auto-complete over-excited.
$mform->addElement('password', 'quizpassword', get_string('enterrequiredpassword', 'adaptivequiz'));
$this->add_action_buttons(true, get_string('continue'));
}
}
This diff is collapsed.
<?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/>.
/**
* A class to emulate enum type for attempt state.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace mod_adaptivequiz\local\attempt;
final class attempt_state {
public const IN_PROGRESS = 'inprogress';
public const COMPLETED = 'complete';
/**
* @var string $stateasstring
*/
private $stateasstring;
private function __construct(string $state) {
$this->stateasstring = $state;
}
public function is_in_progress(): bool {
return self::IN_PROGRESS === $this->stateasstring;
}
public function is_completed(): bool {
return self::COMPLETED === $this->stateasstring;
}
public static function in_progress(): self {
return new self(self::IN_PROGRESS);
}
public static function completed(): self {
return new self(self::COMPLETED);
}
}
This diff is collapsed.
<?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/>.
/**
* This class does the work of fetching a questions associated with a level of difficulty and within
* a question category.
*
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local;
use coding_exception;
use dml_exception;
use dml_read_exception;
use invalid_parameter_exception;
use mod_adaptivequiz\local\repository\questions_number_per_difficulty;
use mod_adaptivequiz\local\repository\questions_repository;
use mod_adaptivequiz\local\repository\tags_repository;
use moodle_exception;
use stdClass;
class fetchquestion {
/**
* The maximum number of attempts at finding a tag containing questions
*/
const MAXTAGRETRY = 5;
/**
* The maximum number of tries at finding avaiable questions
*/
const MAXNUMTRY = 100000;
/** @var stdClass $adaptivequiz object, properties come from the adaptivequiz table */
protected $adaptivequiz;
/**
* @var bool $debugenabled flag to denote developer debugging is enabled and this class should write message to the debug array
*/
protected $debugenabled = false;
/** @var array $debug array containing debugging information */
protected $debug = array();
/** @var array $tags an array of tags that used to identify eligible questions for the attempt */
protected $tags = array();
/** @var int $level the level of difficutly that will be used to fetch questions */
protected $level = 1;
/** @var string $questcatids a string of comma separated question category ids */
protected $questcatids = '';
/** @var int $minimumlevel the minimum level achievable in the attempt */
protected $minimumlevel;
/** @var int $maximumlevel the maximum level achievable in the attempt */
protected $maximumlevel;
/**
* @var array $tagquestsum an array whose keys are difficulty numbers and values are the sum of questions associated with the
* difficulty level
*/
protected $tagquestsum = array();
/** @var bool $rebuild a flag used to force the rebuilding of the $tagquestsum property */
public $rebuild = false;
/**
* The constructor.
*
* @param stdClass $adaptivequiz A record object from {adaptivequiz}.
* @param int $level Level of difficulty to look for when fetching a question.
* @param int $minimumlevel The minimum level the student can achieve.
* @param int $maximumlevel The maximum level the student can achieve.
* @param array $tags An array of accepted tags.
* @throws coding_exception
*/
public function __construct($adaptivequiz, $level, $minimumlevel, $maximumlevel, $tags = []) {
global $SESSION;
$this->adaptivequiz = $adaptivequiz;
$this->tags = $tags;
$this->tags[] = ADAPTIVEQUIZ_QUESTION_TAG;
$this->minimumlevel = $minimumlevel;
$this->maximumlevel = $maximumlevel;
if (!is_int($level) || 0 >= $level) {
throw new coding_exception('Argument 2 is not an positive integer', 'Second parameter must be a positive integer');
}
if ($minimumlevel >= $maximumlevel) {
throw new coding_exception('Minimum level is greater than maximum level',
'Invalid minimum and maximum parameters passed');
}
$this->level = $level;
// Initialize $tagquestsum property.
if (!isset($SESSION->adpqtagquestsum)) {
$SESSION->adpqtagquestsum = array();
$this->tagquestsum = $SESSION->adpqtagquestsum;
} else {
$this->tagquestsum = $SESSION->adpqtagquestsum;
}
if (debugging('', DEBUG_DEVELOPER)) {
$this->debugenabled = true;
}
}
/**
* This function sets the level of difficulty property
* @param int $level level of difficulty
* @return void
*/
public function set_level($level = 1) {
if (!is_int($level) || 0 >= $level) {
throw new coding_exception('Argument 1 is not an positive integer', 'First parameter must be a positive integer');
}
$this->level = $level;
}
/**
* This function returns the level of difficulty property
* @return int - level of difficulty
*/
public function get_level() {
return $this->level;
}
/**
* Reset the maximum question level to search for to a new value
*
* @param int $maximumlevel
* @return void
* @throws coding_exception if the maximum level is less than minimum level
*/
public function set_maximum_level($maximumlevel) {
if ($maximumlevel < $this->minimumlevel) {
throw new coding_exception('Maximum level is less than minimum level', 'Invalid maximum level set.');
}
$this->maximumlevel = $maximumlevel;
}
/**
* Reset the maximum question level to search for to a new value
*
* @param int $maximumlevel
* @return void
* @throws coding_exception if the minimum level is less than maximum level
*/
public function set_minimum_level($minimumlevel) {
if ($minimumlevel > $this->maximumlevel) {
throw new coding_exception('Minimum level is less than maximum level', 'Invalid minimum level set.');
}
$this->minimumlevel = $minimumlevel;
}
/**
* This functions adds a message to the debugging array
* @param string $message: details of the debugging message
* @return void
*/
protected function print_debug($message = '') {
if ($this->debugenabled) {
$this->debug[] = $message;
}
}
/**
* Answer a string view of a variable for debugging purposes
* @param mixed $variable
*/
protected function vardump($variable) {
ob_start();
var_dump($variable);
return ob_get_clean();
}
/**
* This function returns the debug array
* @return array - array of debugging messages
*/
public function get_debug() {
return $this->debug;
}
/**
* This functions returns the $tagquestsum class property
* @return array an array whose keys are difficulty levels and values are the sum of questions associated with the difficulty
*/
public function get_tagquestsum() {
return $this->tagquestsum;
}
/**
* This functions sets the $tagquestsum class property
* @param array an array whose keys are difficulty levels and values are the sum of questions associated with the difficulty
*/
public function set_tagquestsum($tagquestsum) {
$this->tagquestsum = $tagquestsum;
}
/**
* This function decrements 1 from the sum of questions in a difficulty level
* @param array $tagquestsum an array equal to the $tagquestsum property, where the key is the difficulty level and the value
* is the total number of
* questions associated with it. This parameter will be modified.
* @param int $level the difficulty level
* @return array an array whose keys are difficulty levels and values are the sum of questions associated with the difficulty
*/
public function decrement_question_sum_from_difficulty($tagquestsum, $level) {
if (array_key_exists($level, $tagquestsum)) {
$tagquestsum[$level] -= 1;
}
return $tagquestsum;
}
/**
* This function first checks if the session variable already contains a mapping of difficulty levels and the number of
* questions associated with each level. Otherwise it constructos a mapping of difficulty levels and the number of questions
* in each difficulty level.
* @param array $tagquestsum an array equal to the $tagquestsum property, where the key is the difficulty level and the value
* is the total number of
* questions associated with it. This parameter will be modified.
* @param array $tags an array of tags used by the activity
* @param int $min the minimum difficulty allowed for the attempt
* @param int $max the maximum difficulty allowed for the attempt
* @param bool $rebuild true to force the rebuilding the difficulty question count array, otherwise false. Set to "true" only
* for brand new attempts
* @return array an array whose keys are difficulty levels and values are the sum of questions associated with the difficulty
*/
public function initalize_tags_with_quest_count($tagquestsum, $tags, $min, $max, $rebuild = false) {
global $SESSION;
// Check to see if the tagquestsum argument is initialized.
$count = count($tagquestsum);
if (empty($count) || !empty($rebuild)) {
$tagquestsum = array();
// Retrieve the question categories set for this activity.
$questcat = $this->retrieve_question_categories();
// Traverse through the array of configured tags used by the activity.
foreach ($tags as $tag) {
// Retrieve all of id for the configured tag.
$tagids = $this->retrieve_all_tag_ids($min, $max, $tag);
// Retrieve a count of all of the questions associated with each tag.
$difficultiesquestionsnumber = $this->retrieve_tags_with_question_count($tagids, $questcat);
// Traverse the $difficultiesquestionsnumber array and add the values with the values current in the
// $tagquestsum argument.
foreach ($difficultiesquestionsnumber as $questionsnumberperdifficulty) {
$difflevel = $questionsnumberperdifficulty->difficulty();
$totalquestindiff = $questionsnumberperdifficulty->questions_number();
// If the array key exists, then add the sum to what is already in the array.
if (array_key_exists($difflevel, $tagquestsum)) {
$tagquestsum[$difflevel] += $totalquestindiff;
} else {
$tagquestsum[$difflevel] = $totalquestindiff;
}
}
}
} else {
$tagquestsum = $SESSION->adpqtagquestsum;
}
return $tagquestsum;
}
/**
* This function retrieves a question associated with a Moodle tag level of difficulty. If the search for the tag turns up
* empty the function tries to find another tag whose difficulty level is either higher or lower
* @param array $excquestids an array of question ids to exclude from the search
* @return array an array of question ids
*/
public function fetch_questions($excquestids = array()) {
$questids = array();
// Initialize the difficulty tag question sum property for searching.
$this->tagquestsum = $this->initalize_tags_with_quest_count($this->tagquestsum, $this->tags, $this->minimumlevel,
$this->maximumlevel, $this->rebuild);
// If tagquestsum property ie empty then return with nothing.
if (empty($this->tagquestsum)) {
$this->print_debug('fetch_questions() - tagquestsum is empty');
return array();
}
// Check if the requested level has available questions.
if (array_key_exists($this->level, $this->tagquestsum) && 0 < $this->tagquestsum[$this->level]) {
$tagids = $this->retrieve_tag($this->level);
$questids = $this->find_questions_with_tags($tagids, $excquestids);
$this->print_debug('fetch_questions() - Requested level '.$this->level.' has available questions. '.
$this->tagquestsum[$this->level].' question remaining.');
return $questids;
}
// Look for a level that has avaialbe qustions.
$level = $this->level;
for ($i = 1; $i <= self::MAXNUMTRY; $i++) {
// Check if the offset level is now out of bounds and stop the loop.
if ($this->minimumlevel > $level - $i && $this->maximumlevel < $level + $i) {
$i += self::MAXNUMTRY + 1;
$this->print_debug('fetch_questions() - searching levels has gone out of bounds of the min and max levels. '.
'No questions returned');
continue;
}
// First check a level higher than the originally requested level.
$newlevel = $level + $i;
/*
* If the level is within the boundries set for the attempt and the level exists and the count of question is greater
* than zero, retrieve the tag id and the questions available
*/
$condition = $newlevel <= $this->maximumlevel && array_key_exists($newlevel, $this->tagquestsum)
&& 0 < $this->tagquestsum[$newlevel];
if ($condition) {
$tagids = $this->retrieve_tag($newlevel);
$questids = $this->find_questions_with_tags($tagids, $excquestids);
$this->level = $newlevel;
$i += self::MAXNUMTRY + 1;
$this->print_debug('fetch_questions() - original level could not be found. Returned a question from level '.
$newlevel.' instead');
continue;
}
// Check a level lower than the originally requested level.
$newlevel = $level - $i;
/*
* If the level is within the boundries set for the attempt and the level exists and the count of question is greater
* than zero, retrieve the tag id and thequestions available
*/
$condition = $newlevel >= $this->minimumlevel && array_key_exists($newlevel, $this->tagquestsum)
&& 0 < $this->tagquestsum[$newlevel];
if ($condition) {
$tagids = $this->retrieve_tag($newlevel);
$questids = $this->find_questions_with_tags($tagids, $excquestids);
$this->level = $newlevel;
$i += self::MAXNUMTRY + 1;
$this->print_debug('fetch_questions() - original level could not be found. Returned a question from level '
.$newlevel.' instead');
continue;
}
}
return $questids;
}
/**
* This function retrieves all the tag ids that can be used in this attempt.
*
* @param int $minimumlevel The minimum level the student can achieve.
* @param int $maximumlevel The maximum level the student can achieve.
* @param string $tagprefix The tag prefix used.
* @return array An array whose keys represent the difficulty level and values are tag ids.
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
public function retrieve_all_tag_ids(int $minimumlevel, int $maximumlevel, string $tagprefix): array {
if (empty(trim($tagprefix))) {
throw new invalid_parameter_exception('Tag prefix cannot be empty.');
}
$tags = array_map(function(int $level): string {
return ADAPTIVEQUIZ_QUESTION_TAG . $level;
}, range($minimumlevel, $maximumlevel));
if (!$leveltagidmap = tags_repository::get_question_level_to_tag_id_mapping_by_tag_names($tags)) {
return [];
}
return $leveltagidmap;
}
/**
* This function determines how many questions are associated with a tag, for questions contained in the category
* used by the activity.
*
* @param array $tagids an array whose key is the difficulty level and value is the tag id representing the difficulty level
* @param array $categories an array whose key and value is the question category id
* @return questions_number_per_difficulty[]
* @throws coding_exception
* @throws dml_read_exception
* @throws dml_exception
*/
public function retrieve_tags_with_question_count($tagids, $categories): array {
return questions_repository::count_questions_number_per_difficulty($tagids, $categories);
}
/**
* This function retrieves all tag ids, used by this activity and associated with a particular level of difficulty.
*
* @param int $level The level of difficulty (optional). If 0 is passed then the function will use the level class
* property, otherwise the argument value will be used.
* @return array An array whose keys represent the difficulty level and values are tag ids.
* @throws dml_exception
* @throws coding_exception
*/
public function retrieve_tag(int $level = 0): array {
$tags = array_map(function(string $tag) use($level): string {
return $tag . $level;
}, $this->tags);
if (!$tagidlist = tags_repository::get_tag_id_list_by_tag_names($tags)) {
return [];
}
return $tagidlist;
}
/**
* This function retrieves questions within the assigned question categories and
* questions associated with tagids
* @param array $tagids an array of tag is
* @param array $exclude an array of question ids to exclude from the search
* @return array an array whose keys are qustion ids and values are the question names
*/
public function find_questions_with_tags($tagids = [], $exclude = []) {
$questcat = $this->retrieve_question_categories();
return questions_repository::find_questions_with_tags($tagids, $questcat, $exclude);
}
/**
* This function retrieves all of the question categories used the activity.
* @return array an array of quesiton category ids
*/
protected function retrieve_question_categories() {
global $DB;
// Check cached result.
if (!empty($this->questcatids)) {
$this->print_debug('retrieve_question_categories() - question category ids (from cache): '.
$this->vardump($this->questcatids));
return $this->questcatids;
}
$param = array('instance' => $this->adaptivequiz->id);
$records = $DB->get_records_menu('adaptivequiz_question', $param, 'questioncategory ASC', 'id,questioncategory');
// Cache the results.
$this->questcatids = $records;
$this->print_debug('retrieve_question_categories() - question category ids: '.$this->vardump($records));
return $records;
}
/**
* The destruct method saves the difficult level and qustion number mapping to the session variable
*/
public function __destruct() {
global $SESSION;
$SESSION->adpqtagquestsum = $this->tagquestsum;
}
}
<?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/>.
/**
* This class provides access to various numeric representations of a score.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis;
use mod_adaptivequiz\local\catalgo;
class attempt_score {
/** @var float $measuredabilitylogits The measured ability of the attempt in logits. */
protected $measuredabilitylogits = null;
/** @var float $standarderrorlogits The standard error in the score in logits. */
protected $standarderrorlogits = null;
/** @var float $lowestlevel The lowest level of question in the adaptive quiz. */
protected $lowestlevel = null;
/** @var float $highestlevel The highest level of question in the adaptive quiz. */
protected $highestlevel = null;
/**
* Constructor
*
* @return void
*/
public function __construct ($measuredabilitylogits, $standarderrorlogits, $lowestlevel, $highestlevel) {
$this->measuredabilitylogits = $measuredabilitylogits;
$this->standarderrorlogits = $standarderrorlogits;
$this->lowestlevel = $lowestlevel;
$this->highestlevel = $highestlevel;
}
/**
* Answer the measured ability in logits.
*
* @return float
*/
public function measured_ability_in_logits () {
return $this->measuredabilitylogits;
}
/**
* Answer the standard error in logits.
*
* @return float
*/
public function standard_error_in_logits () {
return $this->standarderrorlogits;
}
/**
* Answer the measured ability as a fraction 0-1.
*
* @return float
*/
public function measured_ability_in_fraction () {
return catalgo::convert_logit_to_fraction($this->measuredabilitylogits);
}
/**
* Answer the standard error a fraction 0-0.5.
*
* @return float
*/
public function standard_error_in_fraction () {
return catalgo::convert_logit_to_percent($this->standarderrorlogits);
}
/**
* Answer the measured ability on the adaptive quiz's scale
*
* @return float
*/
public function measured_ability_in_scale () {
return catalgo::map_logit_to_scale($this->measuredabilitylogits, $this->highestlevel, $this->lowestlevel);
}
/**
* Answer the standard error on the adaptive quiz's scale
*
* @return float
*/
public function standard_error_in_scale () {
return catalgo::convert_logit_to_percent($this->standarderrorlogits) * ($this->highestlevel - $this->lowestlevel);
}
}
<?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/>.
/**
* This class provides a mechanism for analysing the usage, performance, and efficacy
* of a single question in an adaptive quiz.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis;
use context;
use InvalidArgumentException;
use mod_adaptivequiz\local\catalgo;
use mod_adaptivequiz\local\questionanalysis\statistics\question_statistic;
use mod_adaptivequiz\local\questionanalysis\statistics\question_statistic_result;
use question_definition;
use stdClass;
class question_analyser {
/** @var context the context this usage belongs to. */
protected $context;
/** @var question_definition $definition The question */
protected $definition = null;
/** @var float $level The question level */
protected $level = null;
/** @var float $lowestlevel The lowest question-level in the adaptive quiz */
protected $lowestlevel = null;
/** @var float $highestlevel The highest question-level in the adaptive quiz */
protected $highestlevel = null;
/** @var array $results An array of the re objects */
protected $results = array();
/** @var array $statistics An array of the adaptivequiz_question_statistic added to this report */
protected $statistics = array();
/** @var array $statisticresults An array of the adaptivequiz_question_statistic_result added to this report */
protected $statisticresults = array();
/**
* Constructor - Create a new analyser.
*
* @param object $context
* @param question_definition $definition
* @param float $level The level (0-1) of the question.
* @return void
*/
public function __construct ($context, question_definition $definition, $level, $lowestlevel, $highestlevel) {
$this->context = $context;
$this->definition = $definition;
$this->level = $level;
$this->lowestlevel = $lowestlevel;
$this->highestlevel = $highestlevel;
}
/**
* Add an usage result for this question.
*
* @param attempt_score $score The user's score on this attempt.
* @param boolean $correct True if the user answered correctly.
* @param string $answer
* @return void
*/
public function add_result ($attemptid, $user, attempt_score $score, $correct, $answer) {
$result = new stdClass();
$result->attemptid = $attemptid;
$result->user = $user;
$result->score = $score;
$result->correct = $correct;
$result->answer = $answer;
$this->results[] = $result;
}
/**
* @return context the context this usage belongs to.
*/
public function get_owning_context() {
return $this->context;
}
/**
* Answer the question definition for this question.
*
* @return question_definition
*/
public function get_question_definition () {
return $this->definition;
}
/**
* Answer the question level for this question.
*
* @return int
*/
public function get_question_level () {
return $this->level;
}
/**
* Answer the question level for this question in logits.
*
* @return int
*/
public function get_question_level_in_logits () {
return catalgo::convert_linear_to_logit($this->level, $this->lowestlevel, $this->highestlevel);
}
/**
* Answer the results for this question
*
* @return array An array of stdClass objects.
*/
public function get_results () {
return $this->results;
}
/**
* Add and calculate a statistic.
*
* @param string $key A key to identify this statistic for sorting and printing.
* @param question_statistic $statistic
* @return void
*/
public function add_statistic ($key, question_statistic $statistic) {
if (!empty($this->statistics[$key])) {
throw new InvalidArgumentException("Statistic key '$key' is already in use.");
}
$this->statistics[$key] = $statistic;
$this->statisticresults[$key] = $statistic->calculate($this);
}
/**
* Answer a statistic result.
*
* @param string $key A key to identify this statistic.
* @return question_statistic_result
*/
public function get_statistic_result ($key) {
if (empty($this->statisticresults[$key])) {
throw new InvalidArgumentException("Unknown statistic key '$key'.");
}
return $this->statisticresults[$key];
}
/**
* Utility function to map a logit value to this question's scale
*
* @param $logit
* @return float Scaled value
*/
public function map_logit_to_scale ($logit) {
return catalgo::map_logit_to_scale($logit, $this->highestlevel, $this->lowestlevel);
}
}
<?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/>.
/**
* This class stores information about a particular attempt's result on a question
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis;
use Exception;
use InvalidArgumentException;
class question_result {
/** @var float $_measuredability The measured ability of the user who attempted this question */
protected $_measuredability = null;
/** @var boolean $_correct True if the user was correct in their answer */
protected $_correct = null;
/**
* Constructor - Create a new result.
*
* @param float $measuredability The measured ability (0-1) of the user in this attempt.
* @param boolean $correct
* @return void
*/
public function __construct ($measuredability, $correct) {
if (!is_numeric($measuredability) || $measuredability < 0 || $measuredability > 1) {
throw new InvalidArgumentException('$measuredability must be a float between 0 and 1.');
}
$this->_measuredability = $measuredability;
$this->_correct = (bool)$correct;
}
/**
* Magic method to provide read-only access to our parameters
*
* @param $key
* @return mixed
*/
public function __get ($key) {
$param = '$_'.$key;
if (isset($this->$param)) {
return $this->$param;
} else {
throw new Exception('Unknown property, '.get_class($this).'->'.$key.'.');
}
}
}
<?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_adaptivequiz\local\questionanalysis;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/tag/lib.php');
use core_tag_tag;
use Exception;
use InvalidArgumentException;
use mod_adaptivequiz\local\attempt\attempt_state;
use mod_adaptivequiz\local\questionanalysis\statistics\question_statistic;
use question_engine;
use stdClass;
/**
* Questions-analyser class.
*
* The class provides a mechanism for loading and analysing question usage, performance, and efficacy.
*
* @package mod_adaptivequiz
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_analyser {
/** @var array $questions An array of all questions loaded and their stats */
protected $questions = array();
/** @var array $statistics An array of the statistics added to this report */
protected $statistics = array();
/**
* Constructor - Create a new analyser.
*
* @return void
*/
public function __construct() {
}
/**
* Load attempts from an adaptive quiz instance.
*
* @param int $instance
*/
public function load_attempts(int $instance): void {
global $DB;
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $instance], '*');
// Get all of the completed attempts for this adaptive quiz instance.
$attempts = $DB->get_records('adaptivequiz_attempt',
['instance' => $instance, 'attemptstate' => attempt_state::COMPLETED]);
foreach ($attempts as $attempt) {
if ($attempt->uniqueid == 0) {
continue;
}
$user = $DB->get_record('user', ['id' => $attempt->userid]);
if (!$user) {
$user = new stdClass();
$user->firstname = get_string('unknownuser', 'adaptivequiz');
$user->lastname = '#' . $attempt->userid;
}
// For each attempt, get the attempt's final score.
$score = new attempt_score($attempt->measure, $attempt->standarderror, $adaptivequiz->lowestlevel,
$adaptivequiz->highestlevel);
// For each attempt, loop through all questions asked and add that usage
// to the question.
$quba = question_engine::load_questions_usage_by_activity($attempt->uniqueid);
foreach ($quba->get_slots() as $i => $slot) {
$question = $quba->get_question($slot);
// Create a question-analyser for the question.
if (empty($this->questions[$question->id])) {
$tags = core_tag_tag::get_item_tags_array('core_question', 'question', $question->id);
$difficulty = adaptivequiz_get_difficulty_from_tags($tags);
$this->questions[$question->id] = new question_analyser($quba->get_owning_context(), $question,
$difficulty, $adaptivequiz->lowestlevel, $adaptivequiz->highestlevel);
}
// Record the attempt score and the individual question result.
// KNIGHT: Questions are considered correct if the marked fraction is at least at the acceptance threshold determined by the plugin settings
$correct = ($quba->get_question_fraction($slot) >= $adaptivequiz->acceptancethreshold);
$answer = $quba->get_response_summary($slot);
$this->questions[$question->id]->add_result($attempt->id, $user, $score, $correct, $answer);
}
}
}
/**
* Add a statistic to calculate.
*
* @param string $key A key to identify this statistic for sorting and printing.
* @param question_statistic $statistic
* @return void
*/
public function add_statistic($key, question_statistic $statistic) {
if (!empty($this->statistics[$key])) {
throw new InvalidArgumentException("Statistic key '$key' is already in use.");
}
$this->statistics[$key] = $statistic;
foreach ($this->questions as $question) {
$question->add_statistic($key, $statistic);
}
}
/**
* Answer a header row.
*
* @return array
*/
public function get_header() {
$header = array();
$header['id'] = get_string('id', 'adaptivequiz');
$header['name'] = get_string('adaptivequizname', 'adaptivequiz');
$header['level'] = get_string('attemptquestion_level', 'adaptivequiz');
foreach ($this->statistics as $key => $statistic) {
$header[$key] = $statistic->get_display_name();
}
return $header;
}
/**
* Return an array of table records, sorted by the statistics given.
*
* @param string $sort Which statistic to sort on.
* @param string $direction ASC or DESC.
* @return array
*/
public function get_records($sort = null, $direction = 'ASC') {
if (empty($this->questions)) {
return [];
}
$records = [];
foreach ($this->questions as $question) {
$record = [];
$record[] = $question->get_question_definition()->id;
$record[] = $question->get_question_definition()->name;
$record[] = $question->get_question_level();
foreach ($this->statistics as $key => $statistic) {
$record[] = $question->get_statistic_result($key)->printable();
}
$records[] = $record;
}
if ($direction != 'ASC' && $direction != 'DESC') {
throw new InvalidArgumentException('Invalid sort direction. Must be SORT_ASC or SORT_DESC, \''.$direction.'\' given.');
}
if ($direction == 'DESC') {
$direction = SORT_DESC;
} else {
$direction = SORT_ASC;
}
if (!is_null($sort)) {
$sortkeys = [];
foreach ($this->questions as $question) {
if ($sort == 'name') {
$sortkeys[] = $question->get_question_definition()->name;
$sorttype = SORT_REGULAR;
} else if ($sort == 'level') {
$sortkeys[] = $question->get_question_level();
$sorttype = SORT_NUMERIC;
} else {
$sortkeys[] = $question->get_statistic_result($sort)->sortable();
$sorttype = SORT_NUMERIC;
}
}
array_multisort($sortkeys, $direction, $sorttype, $records);
}
return $records;
}
/**
* Answer a question-analyzer for a particular question id analyze
*
* @param int $qid The question id
* @return question_analyser
* @throws Exception
*/
public function get_question_analyzer($qid) {
if (!isset($this->questions[$qid])) {
throw new Exception('Question-id not found.');
}
return $this->questions[$qid];
}
/**
* Answer the record for a single question
*
* @param int $qid The question id
* @return array
* @throws Exception
*/
public function get_record($qid) {
$question = $this->get_question_analyzer($qid);
$record = [];
$record[] = $question->get_question_definition()->id;
$record[] = $question->get_question_definition()->name;
$record[] = $question->get_question_level();
foreach ($this->statistics as $key => $statistic) {
$record[] = $question->get_statistic_result($key)->printable();
}
return $record;
}
}
<?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_adaptivequiz\local\questionanalysis\statistics;
use html_writer;
use mod_adaptivequiz\local\questionanalysis\question_analyser;
use moodle_url;
use stdClass;
/**
* This interface defines the methods required for pluggable statistics that may be added to the question analysis.
*
* @package mod_adaptivequiz
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class answers_statistic implements question_statistic {
/**
* Answer a display-name for this statistic.
*
* @return string
*/
public function get_display_name () {
return get_string('answers_display_name', 'adaptivequiz');
}
/**
* Calculate this statistic for a question's results.
*
* @param question_analyser $analyser
* @return question_statistic_result
*/
public function calculate(question_analyser $analyser): question_statistic_result {
// Sort the results.
$results = $analyser->get_results();
foreach ($results as $result) {
$sortkeys[] = $result->score->measured_ability_in_logits();
}
array_multisort($sortkeys, SORT_NUMERIC, SORT_DESC, $results);
// Sort the results into three arrays based on how far above or below the question-level the users are.
$high = array();
$mid = array();
$low = array();
foreach ($results as $result) {
$ceiling = $result->score->measured_ability_in_logits() + $result->score->standard_error_in_logits();
$floor = $result->score->measured_ability_in_logits() - $result->score->standard_error_in_logits();
if ($analyser->get_question_level_in_logits() < $floor) {
// User is significantly above the question-level.
$high[] = $result;
} else if ($analyser->get_question_level_in_logits() > $ceiling) {
// User is significantly below the question-level.
$low[] = $result;
} else {
// User's ability overlaps the question level.
$mid[] = $result;
}
}
ob_start();
print html_writer::end_tag('tr');
print html_writer::start_tag('tr');
print html_writer::tag('th', get_string('attemptquestion_ability', 'adaptivequiz'));
print html_writer::tag('th', get_string('user', 'adaptivequiz'));
print html_writer::tag('th', get_string('result', 'adaptivequiz'));
print html_writer::tag('th', get_string('answer', 'adaptivequiz'));
print html_writer::tag('th', '');
print html_writer::end_tag('tr');
$headings = ob_get_clean();
ob_start();
print html_writer::start_tag('table', array('class' => 'adpq_answers_table'));
print html_writer::start_tag('thead');
print html_writer::start_tag('tr');
print html_writer::tag('th', get_string('highlevelusers', 'adaptivequiz').':',
array('colspan' => '5', 'class' => 'section'));
print $headings;
print html_writer::end_tag('thead');
print html_writer::start_tag('tbody', array('class' => 'adpq_highlevel'));
if (count($high)) {
foreach ($high as $result) {
$this->print_user_result($result);
}
} else {
$this->print_empty_user_result();
}
print html_writer::end_tag('tbody');
print html_writer::start_tag('thead');
print html_writer::start_tag('tr');
print html_writer::tag('th', get_string('midlevelusers', 'adaptivequiz').':',
array('colspan' => '5', 'class' => 'section'));
print $headings;
print html_writer::end_tag('thead');
print html_writer::start_tag('tbody', array('class' => 'adpq_midlevel'));
if (count($mid)) {
foreach ($mid as $result) {
$this->print_user_result($result);
}
} else {
$this->print_empty_user_result();
}
print html_writer::end_tag('tbody');
print html_writer::start_tag('thead');
print html_writer::start_tag('tr');
print html_writer::tag('th', get_string('lowlevelusers', 'adaptivequiz').':',
array('colspan' => '5', 'class' => 'section'));
print $headings;
print html_writer::end_tag('thead');
print html_writer::start_tag('tbody', array('class' => 'adpq_lowlevel'));
if (count($low)) {
foreach ($low as $result) {
$this->print_user_result($result);
}
} else {
$this->print_empty_user_result();
}
print html_writer::end_tag('tbody');
print html_writer::end_tag('table');
return new answers_statistic_result(count($results), ob_get_clean());
}
/**
* Print out a user result.
*
* @param stdClass $result
*/
public function print_user_result(stdClass $result): void {
if ($result->correct) {
$class = 'adpq_correct';
} else {
$class = 'adpq_incorrect';
}
$url = new moodle_url('/mod/adaptivequiz/reviewattempt.php', ['attempt' => $result->attemptid]);
print html_writer::start_tag('tr', ['class' => $class]);
print html_writer::tag('td', round($result->score->measured_ability_in_scale(), 2));
print html_writer::tag('td', $result->user->firstname." ".$result->user->lastname);
print html_writer::tag('td', (($result->correct) ? "correct" : "incorrect"));
print html_writer::tag('td', $result->answer);
print html_writer::tag('td', html_writer::link($url, get_string('reviewattempt', 'adaptivequiz')));
print html_writer::end_tag('tr');
}
/**
* Print out an empty user-result row.
*
* @param question_analyser $analyser
* @param stdClass $result
* @return void
*/
public function print_empty_user_result () {
print html_writer::start_tag('tr');
print html_writer::tag('td', '');
print html_writer::tag('td', '');
print html_writer::tag('td', '');
print html_writer::tag('td', '');
print html_writer::tag('td', '');
print html_writer::end_tag('tr');
}
}
<?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/>.
/**
* This interface defines the methods required for pluggable statistic-results that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis\statistics;
class answers_statistic_result implements question_statistic_result {
/** @var int $count */
protected $count = null;
/** @var string $printable */
protected $printable = null;
/**
* Constructor
*
* @param int $count
* @return void
*/
public function __construct ($count, $printable) {
$this->count = $count;
$this->printable = $printable;
}
/**
* A sortable version of the result.
*
* @return mixed string or numeric
*/
public function sortable () {
return $this->count;
}
/**
* A printable version of the result.
*
* @param numeric $result
* @return mixed string or numeric
*/
public function printable () {
return $this->printable;
}
}
<?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/>.
/**
* This interface defines the methods required for pluggable statistics that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis\statistics;
use mod_adaptivequiz\local\questionanalysis\question_analyser;
class discrimination_statistic implements question_statistic {
/**
* Answer a display-name for this statistic.
*
* @return string
*/
public function get_display_name () {
return get_string('discrimination_display_name', 'adaptivequiz');
}
/**
* Calculate this statistic for a question's results
*
* @param question_analyser $analyser
* @return question_statistic_result
*/
public function calculate (question_analyser $analyser) {
// Discrimination is generally defined as comparing the results of two sub-groups,
// the top 27% of test-takers (the upper group) and the bottom 27% of test-takers (the lower group),
// assuming a normal distribution of scores).
//
// Given that likely have a very sparse data-set we will instead categorize our
// responses into the upper group if the respondent's overall ability measure minus the measure's standard error
// is greater than the question's level. Likewise, responses will be categorized into the lower group if the respondent's
// ability measure plus the measure's standard error is less than the question's level.
// Responses where the user's ability measure and error-range include the question level will be ignored.
$level = $analyser->get_question_level_in_logits();
$uppergroupsize = 0;
$uppergroupcorrect = 0;
$lowergroupsize = 0;
$lowergroupcorrect = 0;
foreach ($analyser->get_results() as $result) {
if ($result->score->measured_ability_in_logits() - $result->score->standard_error_in_logits() > $level) {
// Upper group.
$uppergroupsize++;
if ($result->correct) {
$uppergroupcorrect++;
}
} else if ($result->score->measured_ability_in_logits() + $result->score->standard_error_in_logits() < $level) {
// Lower Group.
$lowergroupsize++;
if ($result->correct) {
$lowergroupcorrect++;
}
}
}
if ($uppergroupsize > 0 && $lowergroupsize > 0) {
// We need at least one result in the upper and lower groups.
$upperproportion = $uppergroupcorrect / $uppergroupsize;
$lowerproportion = $lowergroupcorrect / $lowergroupsize;
$discrimination = $upperproportion - $lowerproportion;
return new discrimination_statistic_result($discrimination);
} else {
// If we don't have any responses in the upper or lower group, then we don't have a meaningful result.
return new discrimination_statistic_result(null);
}
}
}
<?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/>.
/**
* This interface defines the methods required for pluggable statistic-results that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis\statistics;
class discrimination_statistic_result implements question_statistic_result {
/** @var float $discrimination */
protected $discrimination = null;
/**
* Constructor
*
* @param float $discrimination
* @return void
*/
public function __construct ($discrimination) {
$this->discrimination = $discrimination;
}
/**
* A sortable version of the result.
*
* @return mixed string or numeric
*/
public function sortable () {
if (is_null($this->discrimination)) {
return -2;
} else {
return $this->discrimination;
}
}
/**
* A printable version of the result.
*
* @param numeric $result
* @return mixed string or numeric
*/
public function printable () {
if (is_null($this->discrimination)) {
return 'n/a';
} else {
return round($this->discrimination, 3);
}
}
}
<?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/>.
/**
* This interface defines the methods required for pluggable statistics that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis\statistics;
use mod_adaptivequiz\local\questionanalysis\question_analyser;
class percent_correct_statistic implements question_statistic {
/**
* Answer a display-name for this statistic.
*
* @return string
*/
public function get_display_name () {
return get_string('percent_correct_display_name', 'adaptivequiz');
}
/**
* Calculate this statistic for a question's results
*
* @param question_analyser $analyser
* @return question_statistic_result
*/
public function calculate (question_analyser $analyser) {
$correct = 0;
$total = 0;
foreach ($analyser->get_results() as $result) {
$total++;
if ($result->correct) {
$correct++;
}
}
if ($total) {
return new percent_correct_statistic_result ($correct / $total);
} else {
return new percent_correct_statistic_result (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/>.
/**
* This interface defines the methods required for pluggable statistic-results that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis\statistics;
class percent_correct_statistic_result implements question_statistic_result {
/** @var float $fraction */
protected $fraction = null;
/**
* Constructor
*
* @param float $fraction
* @return void
*/
public function __construct ($fraction) {
$this->fraction = $fraction;
}
/**
* A sortable version of the result.
*
* @return mixed string or numeric
*/
public function sortable () {
return $this->fraction;
}
/**
* A printable version of the result.
*
* @param numeric $result
* @return mixed string or numeric
*/
public function printable () {
return round($this->fraction * 100).'%';
}
}
<?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/>.
/**
* This interface defines the methods required for pluggable statistics that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\questionanalysis\statistics;
use mod_adaptivequiz\local\questionanalysis\question_analyser;
interface question_statistic {
/**
* Answer a display-name for this statistic.
*
* @return string
*/
public function get_display_name ();
/**
* Calculate this statistic for a question's results
*
* @param question_analyser $analyser
* @return question_statistic_result
*/
public function calculate (question_analyser $analyser);
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment