Commit 7f723723 authored by 0815-xyz's avatar 0815-xyz
Browse files

Initial commit: Transfer from MOODLE_404_KNIGHT

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/>.
/**
* A class to wrap all database queries which are specific to tags and their related data. Normally should contain
* only static methods to call.
*
* @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\repository;
use coding_exception;
use dml_exception;
use stdClass;
final class tags_repository {
/**
* @param string[] $tagnames
* @return array Map of question difficulty level and tag id, same as what
* {@link moodle_database::get_records_menu()} would return.
* @throws dml_exception
* @throws coding_exception
*/
public static function get_question_level_to_tag_id_mapping_by_tag_names(array $tagnames): array {
global $DB;
list($tagnameselect, $tagnameparams) = $DB->get_in_or_equal($tagnames);
$sql = 'SELECT t.id, ' . $DB->sql_substr('t.name', strlen(ADAPTIVEQUIZ_QUESTION_TAG) + 1) . ' AS level
FROM {tag} t
JOIN {tag_instance} ti ON t.id = ti.tagid AND ti.itemtype = ?
WHERE t.name ' . $tagnameselect . '
GROUP BY t.id';
$params = array_merge(['question'], $tagnameparams);
if (!$records = $DB->get_records_sql($sql, $params)) {
return [];
}
return array_flip(
array_map(function(stdClass $record): int {
return $record->level;
}, $records)
);
}
/**
* @param string[] Array of tag names.
* @return int[] Tag id list.
* @throws dml_exception
* @throws coding_exception
*/
public static function get_tag_id_list_by_tag_names(array $tagnames): array {
global $DB;
list($tagnameselect, $tagnameparams) = $DB->get_in_or_equal($tagnames);
$sql = 'SELECT t.id
FROM {tag} t
JOIN {tag_instance} ti ON t.id = ti.tagid AND ti.itemtype = ?
WHERE t.name ' . $tagnameselect . '
GROUP BY t.id
ORDER BY t.id';
$params = array_merge(['question'], $tagnameparams);
return ($fieldset = $DB->get_fieldset_sql($sql, $params)) ? $fieldset : [];
}
}
<?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 display a table with user's own attempts on the activity's view page.
*
* @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 help_icon;
use html_writer;
use mod_adaptivequiz\local\attempt\attempt_state;
use mod_adaptivequiz_renderer;
use moodle_url;
use stdClass;
use table_sql;
final class user_attempts_table extends table_sql {
/**
* @var mod_adaptivequiz_renderer $renderer
*/
private $renderer;
private int $adaptivequizid;
public function __construct(mod_adaptivequiz_renderer $renderer) {
parent::__construct('userattemptstable');
$this->renderer = $renderer;
}
/**
* A convenience function to call a bunch of init methods.
*
* @param moodle_url $baseurl
* @param stdClass $adaptivequiz A record form {adaptivequiz}. id, lowestlevel, highestlevel, showabilitymeasure are
* the expected fields.
* @param int $userid
* @throws coding_exception
*/
public function init(moodle_url $baseurl, stdClass $adaptivequiz, int $userid): void {
$this->adaptivequizid = $adaptivequiz->id;
$columns = ['state', 'timefinished'];
if ($adaptivequiz->showabilitymeasure) {
$columns[] = 'measure';
}
// KNIGHT: Adds and integrate the review attempt icon in the user attempts table if "show resutls to student" option is activated from quiz settings.
if($adaptivequiz->showownattemptresult) {
$columns[] = 'review';
}
$this->define_columns($columns);
$headers = [
get_string('attempt_state', 'adaptivequiz'),
get_string('attemptfinishedtimestamp', 'adaptivequiz'),
];
if ($adaptivequiz->showabilitymeasure) {
$headers[] = get_string('abilityestimated', 'adaptivequiz') . ' / ' .
$adaptivequiz->lowestlevel . ' - ' . $adaptivequiz->highestlevel;
}
// KNIGHT: Adds the review attempt column header in the user attempts table if "show resutls to student" option is activated from quiz settings.
if($adaptivequiz->showownattemptresult) {
$headers[] = get_string('reviewattempt', 'adaptivequiz');
}
$this->define_headers($headers);
$this->set_attribute('class', 'generaltable userattemptstable');
$this->is_downloadable(false);
$this->collapsible(false);
$this->sortable(false, 'timefinished', SORT_DESC);
$this->define_help_for_headers(
[2 => new help_icon('abilityestimated', 'adaptivequiz')]
);
$this->set_column_css_classes();
$this->set_content_alignment_in_columns();
$this->define_baseurl($baseurl);
$this->set_sql(
'a.id, a.instance, a.attemptstate AS state, a.timemodified AS timefinished, a.measure, q.highestlevel, q.lowestlevel',
'{adaptivequiz_attempt} a, {adaptivequiz} q',
'a.instance = q.id AND q.id = ? AND userid = ?',
[$adaptivequiz->id, $userid]
);
}
/**
* @throws coding_exception
*/
protected function col_state(stdClass $row): string {
return get_string('recent' . $row->state, 'adaptivequiz');
}
protected function col_timefinished(stdClass $row): string {
if ($row->state != attempt_state::COMPLETED) {
return '';
}
return userdate($row->timefinished);
}
//KNGIHT: Adds the ability measure column in the user attempts table.
protected function col_measure(stdClass $row): string {
$measure = $this->renderer->format_measure($row);
if ($this->is_downloading()) {
return $measure;
}
// Safe check: does the attempt belong to this quiz?
if (!empty($row->state) && strtolower($row->state) === strtolower(attempt_state::COMPLETED)) {
if (!empty($row->instance) && $row->instance == $this->adaptivequizid) { // Compare with correct quiz id
$url = new \moodle_url('/mod/adaptivequiz/feedback.php', [
'id' => $this->baseurl->param('id'), // course module id
'attempt' => $row->id // attempt id
]);
return \html_writer::link($url, $measure);
} else {
return \html_writer::tag('span', get_string('invalidattempt', 'adaptivequiz'), ['class' => 'badge badge-warning']);
}
} else {
return \html_writer::tag('span', get_string('incomplete', 'adaptivequiz'), ['class' => 'badge badge-secondary']);
}
}
//KNIGHT: Adds and integrate the review attempt icon in the user attempts table.
protected function col_review(stdClass $row): string {
$reviewurl = new moodle_url('/mod/adaptivequiz/reviewattempt.php', ['attempt' => $row->id]);
$icon = $this->renderer->pix_icon('i/search', get_string('reviewattempt', 'adaptivequiz'), 'moodle', ['class' => 'icon']);
return html_writer::link($reviewurl, $icon);
}
private function set_content_alignment_in_columns(): void {
foreach (array_keys($this->columns) as $columnname) {
$this->column_class[$columnname] .= ' text-center';
}
}
private function set_column_css_classes(): void {
$this->column_class['state'] .= ' statecol';
if (array_key_exists('measure', $this->columns)) {
$this->column_class['measure'] .= ' abilitymeasurecol';
}
}
}
<?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 display a table with user's own attempts on the activity's view page.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\output;
use renderable;
use stdClass;
final class ability_measure implements renderable {
/**
* @var float $abilitymeasure
*/
public $measurevalue;
/**
* @var int $lowestquestiondifficulty
*/
public $lowestquestiondifficulty;
/**
* @var int $highestquestiondifficulty
*/
public $highestquestiondifficulty;
/**
* A convenience method to convert the object to what {@link mod_adaptivequiz_renderer::format_measure()} expects
* to produce a formatted ability measure.
*/
public function as_object_to_format(): stdClass {
$return = new stdClass();
$return->measure = $this->measurevalue;
$return->lowestlevel = $this->lowestquestiondifficulty;
$return->highestlevel = $this->highestquestiondifficulty;
return $return;
}
/**
* A named constructor to set up the object and increase code readability.
*
* @param stdClass $adaptivequiz A record from {adaptivequiz}. lowestlevel and highestlevel are the expected fields.
* @param float $measurevalue
* @return self
*/
public static function of_attempt_on_adaptive_quiz(stdClass $adaptivequiz, float $measurevalue): self {
$return = new self();
$return->lowestquestiondifficulty = !empty($adaptivequiz->lowestlevel) ? $adaptivequiz->lowestlevel : 0;
$return->highestquestiondifficulty = !empty($adaptivequiz->highestlevel) ? $adaptivequiz->highestlevel : 0;
$return->measurevalue = $measurevalue;
return $return;
}
}
<?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\output;
use renderable;
use renderer_base;
use templatable;
use stdClass;
class attempt_feedback implements renderable, templatable {
/** @var object The adaptive quiz activity settings */
protected $adaptivequiz;
/** @var object The attempt record */
protected $attempt;
public function __construct($adaptivequiz, $attempt) {
$this->adaptivequiz = $adaptivequiz;
$this->attempt = $attempt;
}
public function export_for_template(renderer_base $output): stdClass {
global $DB,$CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
$data = new stdClass();
//KNIGHT Getting the scores
$score = adaptivequiz_calculate_score($this->attempt, $this->adaptivequiz);
// Extract the numeric value from the raw score.
if (preg_match('/^[0-9]*\.?[0-9]+/', $score, $matches)) {
$ability = (float)$matches[0]; // Extract numeric part.
} else {
throw new coding_exception('Invalid score format returned by adaptivequiz_calculate_score');
}
//KNIGHT Assigning Ability Score, Lowest and Highest Level.
$data->score = $ability;
$data->lowestlevel = $this->adaptivequiz->lowestlevel;
$data->highestlevel = $this->adaptivequiz->highestlevel;
// Fetch competency descriptions.
$descriptions = $DB->get_records_menu('adaptivequiz_competencydesc',
['adaptivequizid' => $this->adaptivequiz->id], '', 'level, description');
$floor = floor($ability);
$round = round($ability);
$ceil = ceil($ability);
$margin = 0.2;
// Initialize default values
$data->feedback = '';
$data->feedforward = '';
$data->feedup = '';
$data->feedback_level = null;
$data->feedforward_level = null;
$data->feedup_level = null;
if (abs($ability - $round) <= $margin) {
// Scenario A: close to a whole number
$data->feedback = $descriptions[$round - 1] ?? '';
$data->feedforward = $descriptions[$round] ?? '';
$data->feedup = $descriptions[$round + 1] ?? '';
$data->feedback_level = $round - 1;
$data->feedforward_level = $round;
$data->feedup_level = $round + 1;
} else {
// Scenario B: between two levels
$data->feedback = $descriptions[$floor] ?? '';
$data->feedup = $descriptions[$ceil] ?? '';
$data->feedback_level = $floor;
$data->feedup_level = $ceil;
// No feedforward in this case
}
$data->narrative_summary = get_string('feedback_summary', 'adaptivequiz', [
'ability' => $ability,
'lowest' => $this->adaptivequiz->lowestlevel,
'highest' => $this->adaptivequiz->highestlevel,
]);
$data->algorithm_explanation = get_string('feedback_algorithm_explanation', 'adaptivequiz', [
'ability' => $ability,
'floor' => $floor,
'ceil' => $ceil,
]);
//KNIGHT Assigning Data with all the feedback for the Mustache file.
// $data->feedback_block = get_string('feedback_feedback', 'adaptivequiz', $data->feedback);
// $data->feedforward_block = get_string('feedback_feedforward', 'adaptivequiz', $data->feedforward);
// $data->feedup_block = get_string('feedback_feedup', 'adaptivequiz', $data->feedup);
$data->feedback_intro = get_string('feedback_feedback', 'adaptivequiz');
$data->feedforward_intro = get_string('feedback_feedforward', 'adaptivequiz');
$data->feedup_intro = get_string('feedback_feedup', 'adaptivequiz');
$data->feedback_block = $data->feedback;
$data->feedforward_block = $data->feedforward;
$data->feedup_block = $data->feedup;
$data->show_feedback = !empty($data->feedback);
$data->show_feedforward = !empty($data->feedforward);
$data->show_feedup = !empty($data->feedup);
$data->encouragement = get_string('feedback_encouragement', 'adaptivequiz');
//KNIGHT Fetch gradepass from grade_items table
$gradeitem = $DB->get_record('grade_items', [
'itemtype' => 'mod',
'itemmodule' => 'adaptivequiz',
'iteminstance' => $this->adaptivequiz->id
]);
//KNIGHT Grade Pass Logic
if ($gradeitem && $gradeitem->gradepass > 0) {
$passinggrade = (float)$gradeitem->gradepass;
$data->passing_requirement = get_string('feedback_passingrequirement', 'adaptivequiz', $passinggrade);
if ($ability >= $passinggrade) {
$data->passing_result = get_string('feedback_passing_success', 'adaptivequiz', $ability);
} else {
$data->passing_result = get_string('feedback_passing_fail', 'adaptivequiz', $ability);
}
}
return $data;
}
}
<?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/>.
declare(strict_types=1);
namespace mod_adaptivequiz\output;
use renderable;
use renderer_base;
use templatable;
/**
* Output object to display the number of questions answered out of total question number through an attempt.
*
* @package mod_adaptivequiz
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class attempt_progress implements renderable, templatable {
/**
* @var int $questionsanswered
*/
private $questionsanswered;
/**
* @var int $maximumquestions
*/
private $maximumquestions;
/**
* @var bool $showprogressbar Whether a progress should be depicted as a filling bar. True by default.
*/
private $showprogressbar;
/**
* @var string|null $helpicon Already rendered markup for the help icon if needed.
*/
private $helpiconcontent;
/**
* The constructor. See the related class properties.
*
* @param int $questionsanswered
* @param int $maximumquestions
* @param bool $showprogressbar
* @param string|null $helpiconcontent
*/
public function __construct(int $questionsanswered, int $maximumquestions, bool $showprogressbar, ?string $helpiconcontent) {
$this->questionsanswered = $questionsanswered;
$this->maximumquestions = $maximumquestions;
$this->showprogressbar = $showprogressbar;
$this->helpiconcontent = $helpiconcontent;
}
/**
* Returns an object of the same class with modified property.
*/
public function without_progress_bar(): self {
return new self($this->questionsanswered, $this->maximumquestions, false, $this->helpiconcontent);
}
/**
* Returns an object of the same class with modified property.
*
* @param string $helpiconcontent See the related class property.
*/
public function with_help_icon_content(string $helpiconcontent): self {
return new self($this->questionsanswered, $this->maximumquestions, $this->showprogressbar, $helpiconcontent);
}
/**
* Exports the renderer data in a format that is suitable for a Mustache template.
*
* @param renderer_base $output
*/
public function export_for_template(renderer_base $output): array {
$fortemplate = [
'questionsanswerednumber' => $this->questionsanswered,
'maximumquestionsnumber' => $this->maximumquestions,
];
if ($this->showprogressbar) {
$fortemplate['showprogressbar'] = true;
$fortemplate['percentprogressbarfilled'] = floor($this->questionsanswered / $this->maximumquestions * 100);
}
$fortemplate['helpiconcontent'] = $this->helpiconcontent;
return $fortemplate;
}
/**
* A named constructor to instantiate an object from minimal data.
*
* @param int $questionsanswered
* @param int $maximumquestions
*/
public static function with_defaults(int $questionsanswered, int $maximumquestions): self {
return new self($questionsanswered, $maximumquestions, true, 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/>.
/**
* @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\output\questionanalysis;
use html_table;
use html_writer;
use mod_adaptivequiz\local\questionanalysis\question_analyser;
use moodle_url;
use plugin_renderer_base;
use question_display_options;
use question_engine;
use stdClass;
class renderer extends plugin_renderer_base {
/** @var string $sortdir the sorting direction being used */
protected $sortdir = '';
/** @var moodle_url $sorturl the current base url used for keeping the table sorted */
protected $sorturl = '';
/** @var int $groupid variable used to reference the groupid that is currently being used to filter by */
public $groupid = 0;
/** @var array options that should be used for opening the secure popup. */
protected static $popupoptions = array(
'left' => 0,
'top' => 0,
'fullscreen' => true,
'scrollbars' => false,
'resizeable' => false,
'directories' => false,
'toolbar' => false,
'titlebar' => false,
'location' => false,
'status' => false,
'menubar' => false
);
/**
* This function returns page header information to be printed to the page
* @return string HTML markup for header inforation
*/
public function print_header() {
return $this->header();
}
/**
* This function returns page footer information to be printed to the page
* @return string HTML markup for footer inforation
*/
public function print_footer() {
return $this->footer();
}
/**
* This function generates the HTML required to display the initial reports table
* @param array $records attempt records from adaptivequiz_attempt table
* @param stdClass $cm course module object set to the instance of the activity
* @param string $sort the column the the table is to be sorted by
* @param string $sortdir the direction of the sort
* @return string HTML markup
*/
public function get_report_table($headers, $records, $cm, $baseurl, $sort, $sortdir) {
$table = new html_table();
$table->attributes['class'] = 'generaltable quizsummaryofattempt boxaligncenter';
$table->head = $this->format_report_table_headers($headers, $cm, $baseurl, $sort, $sortdir);
$table->align = array('center', 'center', 'center');
$table->size = array('', '', '');
$table->data = $records;
return html_writer::table($table);
}
/**
* This function creates the table header links that will be used to allow instructor to sort the data
* @param stdClass $cm a course module object set to the instance of the activity
* @param string $sort the column the the table is to be sorted by
* @param string $sortdir the direction of the sort
* @return array an array of column headers (firstname / lastname, number of attempts, standard error)
*/
public function format_report_table_headers($headers, $cm, $baseurl, $sort, $sortdir) {
/* Create header links */
$contents = array();
foreach ($headers as $key => $name) {
if ($sort == $key) {
$seperator = ' ';
if ($sortdir == 'DESC') {
$sortdir = 'ASC';
$imageparam = array('src' => $this->image_url('t/up'), 'alt' => '');
$icon = html_writer::empty_tag('img', $imageparam);
} else {
$sortdir = 'DESC';
$imageparam = array('src' => $this->image_url('t/down'), 'alt' => '');
$icon = html_writer::empty_tag('img', $imageparam);
}
} else {
$sortdir = 'ASC';
$seperator = '';
$icon = '';
}
$url = new moodle_url($baseurl, array('cmid' => $cm->id, 'sort' => $key, 'sortdir' => $sortdir));
$contents[] = html_writer::link($url, $name.$seperator.$icon);
}
return $contents;
}
/**
* This function prints paging information
* @param int $totalrecords the total number of records returned
* @param int $page the current page the user is on
* @param int $perpage the number of records displayed on one page
* @return string HTML markup
*/
public function print_paging_bar($totalrecords, $page, $perpage, $cm, $baseurl, $sort, $sortdir) {
$url = new moodle_url($baseurl, array('cmid' => $cm->id, 'sort' => $sort, 'sortdir' => $sortdir));
$output = '';
$output .= $this->paging_bar($totalrecords, $page, $perpage, $url);
return $output;
}
/**
* This function generates the HTML required to display the single-question report
* @param array $headers The labels for the report
* @param array $record An attempt record
* @return string HTML markup
*/
public function get_single_question_report($headers, $record) {
$table = new html_table();
$table->attributes['class'] = 'generaltable quizsummaryofattempt boxaligncenter';
$table->head = array(get_string('statistic', 'adaptivequiz'), get_string('value', 'adaptivequiz'));
$table->align = array('left', 'left');
$table->size = array('200px', '');
$table->width = '100%';
while ($name = array_shift($headers)) {
$value = array_shift($record);
$table->data[] = array($name, $value);
}
return html_writer::table($table);
}
/**
* Generate an HTML view of a single question.
*
* @param $analyzer
* @return string HTML markup
*/
public function get_question_details(question_analyser $analyzer, $context) {
// Setup display options.
$options = new question_display_options();
$options->readonly = true;
$options->flags = question_display_options::HIDDEN;
$options->marks = question_display_options::MAX_ONLY;
$options->rightanswer = question_display_options::VISIBLE;
$options->correctness = question_display_options::VISIBLE;
$options->numpartscorrect = question_display_options::VISIBLE;
// Init question usage and set default behaviour of usage.
$quba = question_engine::make_questions_usage_by_activity('mod_adaptivequiz', $context);
$quba->set_preferred_behaviour('deferredfeedback');
$quba->add_question($analyzer->get_question_definition());
$quba->start_question(1);
$quba->process_action(1, $quba->get_correct_response(1));
return $quba->render_question(1, $options);
}
}
<?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\output\report;
use core\chart_line;
use core\chart_series;
use mod_adaptivequiz\local\report\attempt_report_helper;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* An object to render as a report on question administration for the given attempt.
*
* @package mod_adaptivequiz
* @copyright 2024 Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class attempt_administration_report implements renderable, templatable {
/**
* @var int $attemptid
*/
private int $attemptid;
/**
* The constructor.
*
* @param int $attemptid
*/
public function __construct(int $attemptid) {
$this->attemptid = $attemptid;
}
/**
* Implementation of the interface.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output) {
$data = attempt_report_helper::prepare_administration_data($this->attemptid);
$chart = new chart_line();
$chart->set_labels(array_keys($data));
$xaxis = $chart->get_xaxis(0, true);
$xaxis->set_label(get_string('questionnumber', 'adaptivequiz'));
$yaxis = $chart->get_yaxis(0, true);
$yaxis->set_label(get_string('attemptquestion_ability', 'adaptivequiz'));
$yaxis->set_stepsize(1);
$targetdiffseries = new chart_series(get_string('reportattemptadmcharttargetdifflabel', 'adaptivequiz'),
array_values(array_map(fn (stdClass $dataitem): int => $dataitem->targetdifficulty, $data)));
$targetdiffseries->set_color('#a1caf1');
$chart->add_series($targetdiffseries);
$admdiffseries = new chart_series(get_string('reportattemptadmchartadmdifflabel', 'adaptivequiz'),
array_values(array_map(fn (stdClass $dataitem): int => $dataitem->administereddifficulty, $data)));
$admdiffseries->set_color('#875692');
$chart->add_series($admdiffseries);
$chart->add_series(new chart_series(get_string('attemptquestion_rightwrong', 'adaptivequiz'),
array_values(array_map(
fn (stdClass $dataitem): string => $dataitem->answeredcorrectly
? get_string('reportattemptadmanswerright', 'adaptivequiz')
: get_string('reportattemptadmanswerwrong', 'adaptivequiz'),
$data
))
));
$abilityseries = new chart_series(get_string('attemptquestion_ability', 'adaptivequiz'),
array_values(array_map(fn (stdClass $dataitem): float => round($dataitem->abilitymeasure, 2), $data)));
$abilityseries->set_color('#7f180d');
$chart->add_series($abilityseries);
// We don't care about the label here, as it's supposed to be not displayed.
$standarderrormaxseries = new chart_series('standarderrormax', array_values(array_map(
fn (stdClass $dataitem): float => round($dataitem->standarderrormax, 2), $data
)));
$standarderrormaxseries->set_fill('-1');
$standarderrormaxseries->set_color('rgba(255, 26, 104, 0.2)');
$chart->add_series($standarderrormaxseries);
// Same for the label as above.
$standarderrorminseries = new chart_series('standarderrormin', array_values(array_map(
fn (stdClass $dataitem): float => round($dataitem->standarderrormin, 2), $data
)));
$standarderrorminseries->set_fill('-2');
$standarderrorminseries->set_color('rgba(255, 26, 104, 0.2)');
$chart->add_series($standarderrorminseries);
$chart->add_series(new chart_series(get_string('graphlegend_error', 'adaptivequiz'),
array_values(array_map(
fn (stdClass $dataitem): string => '+/- ' . format_float($dataitem->standarderror * 100, 2) . '%', $data
))
));
return [
'chartdata' => json_encode($chart),
'withtable' => true,
];
}
}
<?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\output\report;
use core\chart_bar;
use core\chart_series;
use mod_adaptivequiz\local\report\attempt_report_helper;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* An object to render as a report on answers distribution for the given attempt.
*
* @package mod_adaptivequiz
* @copyright 2024 Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class attempt_answers_distribution_report implements renderable, templatable {
/**
* @var int $attemptid
*/
private int $attemptid;
/**
* The constructor.
*
* @param int $attemptid
*/
public function __construct(int $attemptid) {
$this->attemptid = $attemptid;
}
/**
* Implementation of the interface.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output) {
global $USER, $DB;
$attemptrecord = $DB->get_record('adaptivequiz_attempt', ['id' => $this->attemptid], '*', MUST_EXIST);
$adaptivequizid = $attemptrecord->instance;
$showchartstacked = true;
if ($chartsettingsjson = get_user_preferences("mod_adaptivequiz_answers_distribution_chart_settings_$adaptivequizid")) {
$chartsettings = json_decode($chartsettingsjson);
$showchartstacked = $chartsettings->showstacked;
}
$data = attempt_report_helper::prepare_answers_distribution_data($this->attemptid);
// Display only those levels where question were actually administered.
$data = array_filter($data, fn (stdClass $dataitem): bool => $dataitem->numcorrect > 0 || $dataitem->numwrong > 0);
$chart = new chart_bar();
$chart->set_stacked($showchartstacked);
$chart->set_labels(array_keys($data));
$xaxis = $chart->get_xaxis(0, true);
$xaxis->set_label(get_string('reportanswersdistributionchartxaxislabel', 'adaptivequiz'));
$yaxis = $chart->get_yaxis(0, true);
$yaxis->set_label(get_string('reportanswersdistributionchartyaxislabel', 'adaptivequiz'));
$yaxis->set_stepsize(1);
$chart->add_series(new chart_series(get_string('reportanswersdistributionchartnumrightlabel', 'adaptivequiz'),
array_values(array_map(fn (stdClass $dataitem): int => $dataitem->numcorrect, $data))));
$chart->add_series(new chart_series(get_string('reportanswersdistributionchartnumwronglabel', 'adaptivequiz'),
array_values(array_map(fn (stdClass $dataitem): int => $dataitem->numwrong, $data))));
return [
'showchartstacked' => $showchartstacked,
'userid' => $USER->id,
'adaptivequizid' => $adaptivequizid,
'chartdata' => json_encode($chart),
'withtable' => true,
];
}
}
<?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/>.
/**
* Contains definition of a renderable for an action available for an individual user attempt in the report.
*
* @package mod_adaptivequiz
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\output\report\individual_user_attempts;
use moodle_url;
use pix_icon;
use renderable;
use renderer_base;
use templatable;
/**
* Definition of a renderable for an action available for an individual user attempt in the report.
*
* @package mod_adaptivequiz
*/
final class individual_user_attempt_action implements renderable, templatable {
/**
* @var moodle_url $url;
*/
private $url;
/**
* @var pix_icon $icon
*/
private $icon;
/**
* @var string $title
*/
private $title;
/**
* The constructor.
*
* @param moodle_url $url
* @param pix_icon $icon
* @param string $title
*/
public function __construct(moodle_url $url, pix_icon $icon, string $title) {
$this->url = $url;
$this->icon = $icon;
$this->title = $title;
}
/**
* Exports the renderer data in a format that is suitable for a Mustache template.
*
* @param renderer_base $output
*/
public function export_for_template(renderer_base $output): array {
return [
'url' => $this->url->out(false),
'icon' => $output->render($this->icon),
'title' => $this->title,
];
}
}
<?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/>.
/**
* Contains definition of a renderable for actions available for an individual user attempt in the report.
*
* @package mod_adaptivequiz
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\output\report\individual_user_attempts;
use renderable;
use renderer_base;
use templatable;
/**
* Definition of a renderable for actions available for an individual user attempt in the report.
*
* @package mod_adaptivequiz
*/
final class individual_user_attempt_actions implements renderable, templatable {
/**
* @var individual_user_attempt_action[] $actions
*/
private $actions = [];
/**
* An interface to add an action object to the actions set.
*
* @param individual_user_attempt_action $action
*/
public function add(individual_user_attempt_action $action): void {
$this->actions[] = $action;
}
/**
* Exports the renderer data in a format that is suitable for a Mustache template.
*
* @param renderer_base $output
*/
public function export_for_template(renderer_base $output): array {
$actions = [];
foreach ($this->actions as $action) {
$actions[] = $action->export_for_template($output);
}
return [
'actions' => $actions,
];
}
}
<?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 display a table with user's own attempts on the activity's view page.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\output;
use renderable;
use stdClass;
final class user_attempt_summary implements renderable {
/**
* @var string $attemptstate
*/
public $attemptstate;
/**
* @var int $timefinished
*/
public $timefinished;
/**
* @var float $abilitymeasure
*/
public $abilitymeasure;
/**
* @var int $lowestquestiondifficulty
*/
public $lowestquestiondifficulty;
/**
* @var int $highestquestiondifficulty
*/
public $highestquestiondifficulty;
/**
* @param stdClass $attempt A record from {adaptivequiz_attempt}. attemptstate, timemodified, measure are
* the expected fields.
* @param stdClass $adaptivequiz A record from {adaptivequiz}. lowestlevel, highestlevel, showabilitymeasure are
* the expected fields.
*/
public static function from_db_records(stdClass $attempt, stdClass $adaptivequiz): self {
$return = new self();
$return->attemptstate = !empty($attempt->attemptstate) ? $attempt->attemptstate : '';
$return->timefinished = !empty($attempt->timemodified) ? $attempt->timemodified : 0;
$return->abilitymeasure = !empty($attempt->measure) && $adaptivequiz->showabilitymeasure
? $attempt->measure
: 0;
$return->lowestquestiondifficulty = !empty($adaptivequiz->lowestlevel) ? $adaptivequiz->lowestlevel : 0;
$return->highestquestiondifficulty = !empty($adaptivequiz->highestlevel) ? $adaptivequiz->highestlevel : 0;
return $return;
}
}
<?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/>.
/**
* Confirmation page to close a student attempt.
*
* @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
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/adaptivequiz/locallib.php');
use mod_adaptivequiz\local\attempt\attempt_state;
$attemptid = required_param('attempt', PARAM_INT);
$confirm = optional_param('confirm', 0, PARAM_INT);
$attempt = $DB->get_record('adaptivequiz_attempt', ['id' => $attemptid], '*', MUST_EXIST);
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $attempt->instance], '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('adaptivequiz', $adaptivequiz->id, $adaptivequiz->course, false, MUST_EXIST);
$course = $DB->get_record('course', ['id' => $adaptivequiz->course], '*', MUST_EXIST);
require_login($course, true, $cm);
$context = context_module::instance($cm->id);
require_capability('mod/adaptivequiz:viewreport', $context);
$returnurl = new moodle_url('/mod/adaptivequiz/viewattemptreport.php', ['cmid' => $cm->id, 'userid' => $attempt->userid]);
if ($attempt->attemptstate == attempt_state::COMPLETED) {
throw new moodle_exception('errorclosingattempt_alreadycomplete', 'adaptivequiz', $returnurl);
}
$user = $DB->get_record('user', ['id' => $attempt->userid], '*', MUST_EXIST);
$PAGE->set_url('/mod/adaptivequiz/closeattempt.php', ['attempt' => $attempt->id]);
$PAGE->set_title(format_string($adaptivequiz->name));
$PAGE->set_heading(format_string($course->fullname));
$PAGE->set_context($context);
$renderer = $PAGE->get_renderer('mod_adaptivequiz');
$performancecalculation = new stdClass();
$performancecalculation->measure = $attempt->measure;
$performancecalculation->stderror = $attempt->standarderror;
$performancecalculation->lowestlevel = $adaptivequiz->lowestlevel;
$performancecalculation->highestlevel = $adaptivequiz->highestlevel;
$a = new stdClass();
$a->name = fullname($user);
$a->started = userdate($attempt->timecreated);
$a->modified = userdate($attempt->timemodified);
$a->num_questions = format_string($attempt->questionsattempted);
$a->measure = $renderer->format_measure($performancecalculation);
$a->standarderror = $renderer->format_standard_error($performancecalculation);
$a->current_user_name = fullname($USER);
$a->current_user_id = format_string($USER->id);
$a->now = userdate(time());
if ($confirm) {
$statusmessage = get_string('attemptclosedstatus', 'adaptivequiz', $a);
$closemessage = get_string('attemptclosed', 'adaptivequiz', $a);
adaptivequiz_complete_attempt($attempt->uniqueid, $adaptivequiz, $context, $attempt->userid, $attempt->standarderror,
$statusmessage);
redirect($returnurl, $closemessage, 4);
}
$message = html_writer::tag('p', get_string('confirmcloseattempt', 'adaptivequiz', $a)) .
html_writer::tag('p', get_string('confirmcloseattemptstats', 'adaptivequiz', $a)) .
html_writer::tag('p', get_string('confirmcloseattemptscore', 'adaptivequiz', $a));
$confirm = new moodle_url('/mod/adaptivequiz/closeattempt.php', ['attempt' => $attempt->id, 'confirm' => 1]);
echo $renderer->header();
echo $renderer->confirm($message, $confirm, $returnurl);
echo $renderer->footer();
<?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/>.
/**
* Adaptive testing capabilities definition
*
* @package mod
* @subpackage adaptivequiz
* @category access
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
// Ability to add a new adaptivequiz to the course.
'mod/adaptivequiz:addinstance' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/course:manageactivities'
),
// Ability to view adaptivequiz report.
'mod/adaptivequiz:viewreport' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
),
// Ability to view review pervious attempts.
'mod/adaptivequiz:reviewattempts' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
// KNIGHT: Added 'student' role to allow viewing own attempts.
'student' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
),
// Ability to attempt the activity.
'mod/adaptivequiz:attempt' => array(
'riskbitmask' => RISK_SPAM,
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW
)
),
);
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = [
[
'eventname' => '\mod_adaptivequiz\event\attempt_completed',
'callback' => '\mod_adaptivequiz\attempt_state_change_observers::attempt_completed'
]
];
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/adaptivequiz/db" VERSION="20221102" COMMENT="XMLDB file for Moodle mod/adaptivequiz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="adaptivequiz" COMMENT="Adaptive quiz instances table" NEXT="adaptivequiz_question">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="course id foreign key"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Name of the activity instance"/>
<FIELD NAME="intro" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Description of activity"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="activity intro text format"/>
<FIELD NAME="attempts" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Number of allowed attempts"/>
<FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="A password that the student must enter before starting or continuing an adaptive quiz attempt."/>
<FIELD NAME="browsersecurity" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Restriciton on the browser the student must use. E.g. securewindow."/>
<FIELD NAME="attemptfeedback" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Feedback given to students when their attempt has been completed."/>
<FIELD NAME="attemptfeedbackformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Format of attempt feedback"/>
<FIELD NAME="showabilitymeasure" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether the ability measure should be presented to a test-taker in attempt summary."/>
<FIELD NAME="showattemptprogress" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether quiz progress info should be presented to a test-taker during attempting a quiz."/>
<FIELD NAME="showownattemptresult" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether attempt result info should be presented to a test-taker."/>
<FIELD NAME="highestlevel" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The highest difficulty level the adaptive quiz will go to."/>
<FIELD NAME="lowestlevel" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The lowest difficulty level the adaptive quiz will go to."/>
<FIELD NAME="acceptancethreshold" TYPE="number" LENGTH="4" NOTNULL="true" DEFAULT="0.5" SEQUENCE="false" DECIMALS="2" COMMENT="The value that determines the acceptable level of correctness"/>
<FIELD NAME="questionschecked" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="notification to prompt user to review questions"/>
<FIELD NAME="questionchecktrigger" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="number of attempts that need to be completed before question analysis is advised'"/>
<FIELD NAME="minimumquestions" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The minimum number of questions that mus be attempted by the user"/>
<FIELD NAME="maximumquestions" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The maximum number of question that can be attempted by the user"/>
<FIELD NAME="standarderror" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0.0" SEQUENCE="false" DECIMALS="5" COMMENT="The standard error that must be met before ending the attempt."/>
<FIELD NAME="startinglevel" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The level of difficult all attempts will start with"/>
<FIELD NAME="grademethod" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Which of multiple attempts should be reported to the grade book. 1=highest, 3=first, 4=last."/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time created timestamp"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time modified timesampt"/>
<FIELD NAME="completionattemptcompleted" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Stores whether a custom completion rule based on whether a user has a completed attempt enabled."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="course_foreign" TYPE="foreign" FIELDS="course" REFTABLE="course" REFFIELDS="id" COMMENT="Foreign key to the course table."/>
</KEYS>
</TABLE>
<TABLE NAME="adaptivequiz_question" COMMENT="An association table for activity instance and question categories.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="instance" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Activity instance id. Foreign key to activityquiz."/>
<FIELD NAME="questioncategory" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Question category id. Foreign key to questions."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="instance_foreign" TYPE="foreign" FIELDS="instance" REFTABLE="adaptivequiz" REFFIELDS="id" COMMENT="Foreign key to adaptivequiz table."/>
<KEY NAME="questioncategory_foreign" TYPE="foreign" FIELDS="questioncategory" REFTABLE="question_categories" REFFIELDS="id" COMMENT="Foreign key to questino_categories table."/>
</KEYS>
</TABLE>
<TABLE NAME="adaptivequiz_attempt" COMMENT="Logging of attempts">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="instance"/>
<FIELD NAME="instance" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Activity instance the attempt was for"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="User id. Foreign key from user."/>
<FIELD NAME="uniqueid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Question usage id. Foreign key to question_usages."/>
<FIELD NAME="attemptstate" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false" COMMENT="The state of the attempt"/>
<FIELD NAME="attemptstopcriteria" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The reason why the attempt was stopped"/>
<FIELD NAME="questionsattempted" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The number of question attempted"/>
<FIELD NAME="difficultysum" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0.0" SEQUENCE="false" DECIMALS="7" COMMENT="The sum of difficulty levels attempted measured in logits"/>
<FIELD NAME="standarderror" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0.0" SEQUENCE="false" DECIMALS="5" COMMENT="The standard error that was achieved during the attempt"/>
<FIELD NAME="measure" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0.0" SEQUENCE="false" DECIMALS="5" COMMENT="The attempt ability measure in logits"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time created timestamp"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time modified timestamp"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="instance_foreign"/>
<KEY NAME="instance_foreign" TYPE="foreign" FIELDS="instance" REFTABLE="adaptivequiz" REFFIELDS="id" COMMENT="Foreign key to adaptivequiz table."/>
<KEY NAME="userid_foreign" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id" COMMENT="Foreign key to user table."/>
<KEY NAME="uniqueid_foreign" TYPE="foreign" FIELDS="uniqueid" REFTABLE="question_usages" REFFIELDS="id" COMMENT="Foreign key to question_usages table."/>
</KEYS>
<INDEXES>
<INDEX NAME="instance_userid_idx" UNIQUE="false" FIELDS="instance, userid" COMMENT="Instance and user id index"/>
</INDEXES>
</TABLE>
<TABLE NAME="adaptivequiz_competencydesc" COMMENT="Stores competency descriptions for difficulty levels">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="adaptivequizid" TYPE="int" LENGTH="10" NOTNULL="true"/>
<FIELD NAME="level" TYPE="int" LENGTH="5" NOTNULL="true"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="adaptivequiz_fk" TYPE="foreign" FIELDS="adaptivequizid" REFTABLE="adaptivequiz" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
<?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/>.
/**
* Definition of log events for the adaptive quiz module.
*
* @package mod_adaptivequiz
* @category log
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $DB;
$logs = array(
array('module' => 'adaptivequiz', 'action' => 'view', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'add', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'update', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'report', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'attempt', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'submit', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'review', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'start attempt', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'close attempt', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'start attempt', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'continue attempt', 'mtable' => 'adaptivequiz', 'field' => 'name'),
array('module' => 'adaptivequiz', 'action' => 'start attempt', 'mtable' => 'adaptivequiz', 'field' => 'name'),
);
<?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/>.
/**
* Adaptive testing uninstall script
*
* This module was created as a collaborative effort between Middlebury College
* and Remote Learner.
*
* @package mod_adaptivequiz
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Custom uninstallation procedure
* @return bool: only returns truel
*/
function xmldb_adaptivequiz_uninstall() {
return true;
}
<?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/>.
/**
* Contains function with the definition of upgrade steps for the plugin.
*
* @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
*/
/**
* Defines upgrade steps for the plugin.
*
* @param mixed $oldversion
*/
function xmldb_adaptivequiz_upgrade($oldversion) {
global $CFG, $DB;
$dbman = $DB->get_manager();
if ($oldversion < 2014020400) {
// Define field grademethod.
$table = new xmldb_table('adaptivequiz');
$field = new xmldb_field('grademethod', XMLDB_TYPE_INTEGER, '3', null, XMLDB_NOTNULL, null, 1, 'startinglevel');
// Conditionally add field grademethod.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014020400, 'adaptivequiz');
}
if ($oldversion < 2022012600) {
$table = new xmldb_table('adaptivequiz');
$field = new xmldb_field('showabilitymeasure', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, false, '0',
'attemptfeedbackformat');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2022012600, 'adaptivequiz');
}
if ($oldversion < 2022092600) {
$table = new xmldb_table('adaptivequiz');
$field = new xmldb_field('completionattemptcompleted', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, false, 0);
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2022092600, 'adaptivequiz');
}
if ($oldversion < 2022110200) {
$table = new xmldb_table('adaptivequiz');
$field = new xmldb_field('showattemptprogress', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, 0,
'showabilitymeasure');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2022110200, 'adaptivequiz');
}
//KNGIHT: Handle the upgrade for the new 'CompetencyDescription' fields.
// Step 1: Create new dynamic table for competency descriptions.
if ($oldversion < 2025042300) {
$table = new xmldb_table('adaptivequiz_competencydesc');
if (!$dbman->table_exists($table)) {
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('adaptivequizid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('level', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, null);
$table->add_field('description', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('adaptivequiz_fk', XMLDB_KEY_FOREIGN, ['adaptivequizid'], 'adaptivequiz', ['id']);
$dbman->create_table($table);
}
upgrade_mod_savepoint(true, 2025042300, 'adaptivequiz');
}
// Step 2: Drop old fixed competencydesc1–4 fields.
if ($oldversion < 2025042301) {
$table = new xmldb_table('adaptivequiz');
for ($i = 1; $i <= 4; $i++) {
$field = new xmldb_field("competencydesc$i");
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
}
upgrade_mod_savepoint(true, 2025042301, 'adaptivequiz');
}
// KNIGHT: Handle the upgrade for the new 'showownattemptresult', partial credit 'acceptancethreshold' and 'questioncheck' fields and assign capabilities.
if ($oldversion < 2025062200) {
$table = new xmldb_table('adaptivequiz');
$field = new xmldb_field(
'showownattemptresult', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, 0, 'showabilitymeasure'
);
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$acceptance_threshold_field = new xmldb_field(
'acceptancethreshold', XMLDB_TYPE_NUMBER, '3,2', null, XMLDB_NOTNULL, null, '0', 'highestlevel'
);
if (!$dbman->field_exists($table, $acceptance_threshold_field)) {
$dbman->add_field($table, $acceptance_threshold_field);
}
$questionschecked_field = new xmldb_field(
'questionschecked', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'acceptancethreshold'
);
if (!$dbman->field_exists($table, $questionschecked_field)) {
$dbman->add_field($table, $questionschecked_field);
}
$questionchecktrigger_field = new xmldb_field(
'questionchecktrigger', XMLDB_TYPE_INTEGER, '3', null, XMLDB_NOTNULL, null, '0', 'questionschecked'
);
if (!$dbman->field_exists($table, $questionchecktrigger_field)) {
$dbman->add_field($table, $questionchecktrigger_field);
}
$studentroles = get_archetype_roles('student');
if (!empty($studentroles)) {
$syscontext = context_system::instance();
foreach ($studentroles as $role) {
assign_capability('mod/adaptivequiz:reviewattempts', CAP_ALLOW, $role->id, $syscontext->id, true);
}
}
upgrade_mod_savepoint(true, 2025062200, 'adaptivequiz');
}
return true;
}
<?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/>.
/**
* Confirmation page to remove student attempts.
*
* @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
*/
require_once(__DIR__ . '/../../config.php');
$attemptid = required_param('attempt', PARAM_INT);
$confirm = optional_param('confirm', 0, PARAM_INT);
$attempt = $DB->get_record('adaptivequiz_attempt', ['id' => $attemptid], '*', MUST_EXIST);
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $attempt->instance], '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('adaptivequiz', $adaptivequiz->id, $adaptivequiz->course, false, MUST_EXIST);
$course = $DB->get_record('course', ['id' => $adaptivequiz->course], '*', MUST_EXIST);
require_login($course, true, $cm);
$context = context_module::instance($cm->id);
require_capability('mod/adaptivequiz:viewreport', $context);
$user = $DB->get_record('user', ['id' => $attempt->userid], '*', MUST_EXIST);
$PAGE->set_url('/mod/adaptivequiz/delattempt.php', ['attempt' => $attempt->id]);
$PAGE->set_title(format_string($adaptivequiz->name));
$PAGE->set_heading(format_string($course->fullname));
$PAGE->set_context($context);
$returnurl = new moodle_url('/mod/adaptivequiz/viewattemptreport.php', ['cmid' => $cm->id, 'userid' => $user->id]);
$a = new stdClass();
$a->name = fullname($user);
$a->timecompleted = userdate($attempt->timemodified);
if ($confirm) {
question_engine::delete_questions_usage_by_activity($attempt->uniqueid);
$DB->delete_records('adaptivequiz_attempt', ['id' => $attempt->id]);
adaptivequiz_update_grades($adaptivequiz, $user->id);
$message = get_string('attemptdeleted', 'adaptivequiz', $a);
redirect($returnurl, $message, 4);
}
$message = get_string('confirmdeleteattempt', 'adaptivequiz', $a);
$confirm = new moodle_url('/mod/adaptivequiz/delattempt.php', ['attempt' => $attempt->id, 'confirm' => 1]);
echo $OUTPUT->header();
echo $OUTPUT->confirm($message, $confirm, $returnurl);
echo $OUTPUT->footer();
<?php
//KNIGHT Adaptive Quiz Feedback Page
// * Adaptive quiz feedback script
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/gradelib.php');
use mod_adaptivequiz\output\attempt_feedback;
use mod_adaptivequiz\local\attempt\attempt_state;
$id = required_param('id', PARAM_INT); // cmid
$attemptid = required_param('attempt', PARAM_INT); // attempt id
//KNIGHT Fetch DB records safely.
$cm = get_coursemodule_from_id('adaptivequiz', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $cm->instance], '*', MUST_EXIST);
$attempt = $DB->get_record('adaptivequiz_attempt', ['id' => $attemptid], '*', MUST_EXIST);
//KNIGHT Check that attempt belongs to this adaptive quiz.
if ($attempt->instance != $adaptivequiz->id) {
throw new moodle_exception('invalidattemptid', 'adaptivequiz');
}
//KNGIHT Setup page.
require_login($course, true, $cm);
$context = context_module::instance($cm->id);
$PAGE->set_url(new moodle_url('/mod/adaptivequiz/feedback.php', ['id' => $id, 'attempt' => $attemptid]));
$PAGE->set_title(format_string($adaptivequiz->name));
$PAGE->set_heading(format_string($course->fullname));
$PAGE->add_body_class('limitedwidth');
//KNIGHT Confirm that only completed attempts can view feedback.
if (strtolower($attempt->attemptstate) !== strtolower(attempt_state::COMPLETED)) {
print_error('Attempt not completed yet. Feedback unavailable.');
}
echo $OUTPUT->header();
//KNIGHT Use the official attempt feedback renderable.
$feedbackrenderable = new attempt_feedback($adaptivequiz, $attempt);
echo $OUTPUT->render($feedbackrenderable);
// KNIGHT Add back button to return to view.php
$backurl = new moodle_url('/mod/adaptivequiz/view.php', ['id' => $id, 'forceview' => 1]);
echo html_writer::div(
html_writer::link($backurl, '← Back to Quiz Summary', [
'class' => 'btn btn-primary mb-3'
]),
'text-center'
);
echo $OUTPUT->footer();
Supports Markdown
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